dorfl 0.0.0 → 0.1.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 +73 -0
- package/dist/advance-ci-template.d.ts.map +1 -0
- package/dist/advance-ci-template.js +104 -0
- package/dist/advance-ci-template.js.map +1 -0
- package/dist/advance-classify.d.ts +132 -0
- package/dist/advance-classify.d.ts.map +1 -0
- package/dist/advance-classify.js +120 -0
- package/dist/advance-classify.js.map +1 -0
- package/dist/advance-drivers.d.ts +182 -0
- package/dist/advance-drivers.d.ts.map +1 -0
- package/dist/advance-drivers.js +231 -0
- package/dist/advance-drivers.js.map +1 -0
- package/dist/advance-isolated.d.ts +156 -0
- package/dist/advance-isolated.d.ts.map +1 -0
- package/dist/advance-isolated.js +256 -0
- package/dist/advance-isolated.js.map +1 -0
- package/dist/advance-lifecycle-template.d.ts +107 -0
- package/dist/advance-lifecycle-template.d.ts.map +1 -0
- package/dist/advance-lifecycle-template.js +668 -0
- package/dist/advance-lifecycle-template.js.map +1 -0
- package/dist/advance-loop-driver.d.ts +325 -0
- package/dist/advance-loop-driver.d.ts.map +1 -0
- package/dist/advance-loop-driver.js +437 -0
- package/dist/advance-loop-driver.js.map +1 -0
- package/dist/advance-treeless-publish.d.ts +108 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -0
- package/dist/advance-treeless-publish.js +71 -0
- package/dist/advance-treeless-publish.js.map +1 -0
- package/dist/advance.d.ts +340 -0
- package/dist/advance.d.ts.map +1 -0
- package/dist/advance.js +1122 -0
- package/dist/advance.js.map +1 -0
- package/dist/advancing-lock.d.ts +294 -0
- package/dist/advancing-lock.d.ts.map +1 -0
- package/dist/advancing-lock.js +594 -0
- package/dist/advancing-lock.js.map +1 -0
- package/dist/agent-launch.d.ts +79 -0
- package/dist/agent-launch.d.ts.map +1 -0
- package/dist/agent-launch.js +61 -0
- package/dist/agent-launch.js.map +1 -0
- package/dist/agent-stop.d.ts +149 -0
- package/dist/agent-stop.d.ts.map +1 -0
- package/dist/agent-stop.js +307 -0
- package/dist/agent-stop.js.map +1 -0
- package/dist/apply-decide.d.ts +127 -0
- package/dist/apply-decide.d.ts.map +1 -0
- package/dist/apply-decide.js +176 -0
- package/dist/apply-decide.js.map +1 -0
- package/dist/apply-merge-action.d.ts +206 -0
- package/dist/apply-merge-action.d.ts.map +1 -0
- package/dist/apply-merge-action.js +307 -0
- package/dist/apply-merge-action.js.map +1 -0
- package/dist/apply-persist.d.ts +174 -0
- package/dist/apply-persist.d.ts.map +1 -0
- package/dist/apply-persist.js +359 -0
- package/dist/apply-persist.js.map +1 -0
- package/dist/arbiter.d.ts +120 -0
- package/dist/arbiter.d.ts.map +1 -0
- package/dist/arbiter.js +255 -0
- package/dist/arbiter.js.map +1 -0
- package/dist/brand.d.ts +70 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +84 -0
- package/dist/brand.js.map +1 -0
- package/dist/buildable-body.d.ts +132 -0
- package/dist/buildable-body.d.ts.map +1 -0
- package/dist/buildable-body.js +131 -0
- package/dist/buildable-body.js.map +1 -0
- package/dist/categorise.d.ts +66 -0
- package/dist/categorise.d.ts.map +1 -0
- package/dist/categorise.js +106 -0
- package/dist/categorise.js.map +1 -0
- package/dist/claim-cas.d.ts +117 -0
- package/dist/claim-cas.d.ts.map +1 -0
- package/dist/claim-cas.js +312 -0
- package/dist/claim-cas.js.map +1 -0
- package/dist/cli-spinner.d.ts +112 -0
- package/dist/cli-spinner.d.ts.map +1 -0
- package/dist/cli-spinner.js +157 -0
- package/dist/cli-spinner.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3094 -0
- package/dist/cli.js.map +1 -0
- package/dist/close-job-template.d.ts +70 -0
- package/dist/close-job-template.d.ts.map +1 -0
- package/dist/close-job-template.js +180 -0
- package/dist/close-job-template.js.map +1 -0
- package/dist/close-job.d.ts +95 -0
- package/dist/close-job.d.ts.map +1 -0
- package/dist/close-job.js +226 -0
- package/dist/close-job.js.map +1 -0
- package/dist/complete.d.ts +361 -0
- package/dist/complete.d.ts.map +1 -0
- package/dist/complete.js +885 -0
- package/dist/complete.js.map +1 -0
- package/dist/concurrency.d.ts +68 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +112 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/config-override.d.ts +76 -0
- package/dist/config-override.d.ts.map +1 -0
- package/dist/config-override.js +50 -0
- package/dist/config-override.js.map +1 -0
- package/dist/config.d.ts +668 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +241 -0
- package/dist/config.js.map +1 -0
- package/dist/continue-branch.d.ts +249 -0
- package/dist/continue-branch.d.ts.map +1 -0
- package/dist/continue-branch.js +389 -0
- package/dist/continue-branch.js.map +1 -0
- package/dist/cwd-section.d.ts +186 -0
- package/dist/cwd-section.d.ts.map +1 -0
- package/dist/cwd-section.js +209 -0
- package/dist/cwd-section.js.map +1 -0
- package/dist/decision-engine.d.ts +170 -0
- package/dist/decision-engine.d.ts.map +1 -0
- package/dist/decision-engine.js +136 -0
- package/dist/decision-engine.js.map +1 -0
- package/dist/detect.d.ts +17 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +118 -0
- package/dist/detect.js.map +1 -0
- package/dist/do-autopick.d.ts +85 -0
- package/dist/do-autopick.d.ts.map +1 -0
- package/dist/do-autopick.js +112 -0
- package/dist/do-autopick.js.map +1 -0
- package/dist/do-config.d.ts +312 -0
- package/dist/do-config.d.ts.map +1 -0
- package/dist/do-config.js +358 -0
- package/dist/do-config.js.map +1 -0
- package/dist/do-remote-auto.d.ts +75 -0
- package/dist/do-remote-auto.d.ts.map +1 -0
- package/dist/do-remote-auto.js +111 -0
- package/dist/do-remote-auto.js.map +1 -0
- package/dist/do.d.ts +621 -0
- package/dist/do.d.ts.map +1 -0
- package/dist/do.js +1882 -0
- package/dist/do.js.map +1 -0
- package/dist/drop-source.d.ts +96 -0
- package/dist/drop-source.d.ts.map +1 -0
- package/dist/drop-source.js +91 -0
- package/dist/drop-source.js.map +1 -0
- package/dist/eligibility.d.ts +46 -0
- package/dist/eligibility.d.ts.map +1 -0
- package/dist/eligibility.js +34 -0
- package/dist/eligibility.js.map +1 -0
- package/dist/env-config.d.ts +51 -0
- package/dist/env-config.d.ts.map +1 -0
- package/dist/env-config.js +272 -0
- package/dist/env-config.js.map +1 -0
- package/dist/failure-cause.d.ts +70 -0
- package/dist/failure-cause.d.ts.map +1 -0
- package/dist/failure-cause.js +126 -0
- package/dist/failure-cause.js.map +1 -0
- package/dist/format.d.ts +43 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +256 -0
- package/dist/format.js.map +1 -0
- package/dist/frontmatter.d.ts +215 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +345 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/gate-readiness.d.ts +84 -0
- package/dist/gate-readiness.d.ts.map +1 -0
- package/dist/gate-readiness.js +103 -0
- package/dist/gate-readiness.js.map +1 -0
- package/dist/gc.d.ts +165 -0
- package/dist/gc.d.ts.map +1 -0
- package/dist/gc.js +313 -0
- package/dist/gc.js.map +1 -0
- package/dist/gh-failure.d.ts +42 -0
- package/dist/gh-failure.d.ts.map +1 -0
- package/dist/gh-failure.js +49 -0
- package/dist/gh-failure.js.map +1 -0
- package/dist/git.d.ts +75 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +130 -0
- package/dist/git.js.map +1 -0
- package/dist/github.d.ts +187 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +343 -0
- package/dist/github.js.map +1 -0
- package/dist/harness.d.ts +242 -0
- package/dist/harness.d.ts.map +1 -0
- package/dist/harness.js +157 -0
- package/dist/harness.js.map +1 -0
- package/dist/identity.d.ts +167 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +231 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/install-ci-branch-protection.d.ts +147 -0
- package/dist/install-ci-branch-protection.d.ts.map +1 -0
- package/dist/install-ci-branch-protection.js +166 -0
- package/dist/install-ci-branch-protection.js.map +1 -0
- package/dist/install-ci-capabilities/advance-lifecycle.d.ts +15 -0
- package/dist/install-ci-capabilities/advance-lifecycle.d.ts.map +1 -0
- package/dist/install-ci-capabilities/advance-lifecycle.js +28 -0
- package/dist/install-ci-capabilities/advance-lifecycle.js.map +1 -0
- package/dist/install-ci-capabilities/close-job.d.ts +13 -0
- package/dist/install-ci-capabilities/close-job.d.ts.map +1 -0
- package/dist/install-ci-capabilities/close-job.js +26 -0
- package/dist/install-ci-capabilities/close-job.js.map +1 -0
- package/dist/install-ci-capabilities/example-noop.d.ts +16 -0
- package/dist/install-ci-capabilities/example-noop.d.ts.map +1 -0
- package/dist/install-ci-capabilities/example-noop.js +23 -0
- package/dist/install-ci-capabilities/example-noop.js.map +1 -0
- package/dist/install-ci-capabilities/intake.d.ts +15 -0
- package/dist/install-ci-capabilities/intake.d.ts.map +1 -0
- package/dist/install-ci-capabilities/intake.js +28 -0
- package/dist/install-ci-capabilities/intake.js.map +1 -0
- package/dist/install-ci-capabilities/verify.d.ts +14 -0
- package/dist/install-ci-capabilities/verify.d.ts.map +1 -0
- package/dist/install-ci-capabilities/verify.js +27 -0
- package/dist/install-ci-capabilities/verify.js.map +1 -0
- package/dist/install-ci-core.d.ts +446 -0
- package/dist/install-ci-core.d.ts.map +1 -0
- package/dist/install-ci-core.js +760 -0
- package/dist/install-ci-core.js.map +1 -0
- package/dist/install-ci-github.d.ts +167 -0
- package/dist/install-ci-github.d.ts.map +1 -0
- package/dist/install-ci-github.js +315 -0
- package/dist/install-ci-github.js.map +1 -0
- package/dist/install-ci.d.ts +105 -0
- package/dist/install-ci.d.ts.map +1 -0
- package/dist/install-ci.js +363 -0
- package/dist/install-ci.js.map +1 -0
- package/dist/intake-event.d.ts +88 -0
- package/dist/intake-event.d.ts.map +1 -0
- package/dist/intake-event.js +66 -0
- package/dist/intake-event.js.map +1 -0
- package/dist/intake-marker.d.ts +95 -0
- package/dist/intake-marker.d.ts.map +1 -0
- package/dist/intake-marker.js +127 -0
- package/dist/intake-marker.js.map +1 -0
- package/dist/intake-triage.d.ts +48 -0
- package/dist/intake-triage.d.ts.map +1 -0
- package/dist/intake-triage.js +95 -0
- package/dist/intake-triage.js.map +1 -0
- package/dist/intake-trigger-template.d.ts +185 -0
- package/dist/intake-trigger-template.d.ts.map +1 -0
- package/dist/intake-trigger-template.js +449 -0
- package/dist/intake-trigger-template.js.map +1 -0
- package/dist/intake.d.ts +569 -0
- package/dist/intake.d.ts.map +1 -0
- package/dist/intake.js +1628 -0
- package/dist/intake.js.map +1 -0
- package/dist/integration-core.d.ts +539 -0
- package/dist/integration-core.d.ts.map +1 -0
- package/dist/integration-core.js +2195 -0
- package/dist/integration-core.js.map +1 -0
- package/dist/integrator.d.ts +343 -0
- package/dist/integrator.d.ts.map +1 -0
- package/dist/integrator.js +400 -0
- package/dist/integrator.js.map +1 -0
- package/dist/isolation.d.ts +219 -0
- package/dist/isolation.d.ts.map +1 -0
- package/dist/isolation.js +261 -0
- package/dist/isolation.js.map +1 -0
- package/dist/issue-provider.d.ts +349 -0
- package/dist/issue-provider.d.ts.map +1 -0
- package/dist/issue-provider.js +360 -0
- package/dist/issue-provider.js.map +1 -0
- package/dist/item-lock.d.ts +626 -0
- package/dist/item-lock.d.ts.map +1 -0
- package/dist/item-lock.js +1381 -0
- package/dist/item-lock.js.map +1 -0
- package/dist/item-path.d.ts +49 -0
- package/dist/item-path.d.ts.map +1 -0
- package/dist/item-path.js +66 -0
- package/dist/item-path.js.map +1 -0
- package/dist/ledger-lint.d.ts +129 -0
- package/dist/ledger-lint.d.ts.map +1 -0
- package/dist/ledger-lint.js +249 -0
- package/dist/ledger-lint.js.map +1 -0
- package/dist/ledger-read.d.ts +357 -0
- package/dist/ledger-read.d.ts.map +1 -0
- package/dist/ledger-read.js +442 -0
- package/dist/ledger-read.js.map +1 -0
- package/dist/ledger-write.d.ts +330 -0
- package/dist/ledger-write.d.ts.map +1 -0
- package/dist/ledger-write.js +411 -0
- package/dist/ledger-write.js.map +1 -0
- package/dist/lifecycle-gather.d.ts +30 -0
- package/dist/lifecycle-gather.d.ts.map +1 -0
- package/dist/lifecycle-gather.js +205 -0
- package/dist/lifecycle-gather.js.map +1 -0
- package/dist/lifecycle-pools.d.ts +180 -0
- package/dist/lifecycle-pools.d.ts.map +1 -0
- package/dist/lifecycle-pools.js +78 -0
- package/dist/lifecycle-pools.js.map +1 -0
- package/dist/merge-question-surfacer.d.ts +166 -0
- package/dist/merge-question-surfacer.d.ts.map +1 -0
- package/dist/merge-question-surfacer.js +297 -0
- package/dist/merge-question-surfacer.js.map +1 -0
- package/dist/mint-adr.d.ts +126 -0
- package/dist/mint-adr.d.ts.map +1 -0
- package/dist/mint-adr.js +257 -0
- package/dist/mint-adr.js.map +1 -0
- package/dist/mirror-pool-scan.d.ts +125 -0
- package/dist/mirror-pool-scan.d.ts.map +1 -0
- package/dist/mirror-pool-scan.js +104 -0
- package/dist/mirror-pool-scan.js.map +1 -0
- package/dist/needs-attention.d.ts +341 -0
- package/dist/needs-attention.d.ts.map +1 -0
- package/dist/needs-attention.js +900 -0
- package/dist/needs-attention.js.map +1 -0
- package/dist/orphan-sidecar.d.ts +79 -0
- package/dist/orphan-sidecar.d.ts.map +1 -0
- package/dist/orphan-sidecar.js +71 -0
- package/dist/orphan-sidecar.js.map +1 -0
- package/dist/output.d.ts +48 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +66 -0
- package/dist/output.js.map +1 -0
- package/dist/pi-harness.d.ts +179 -0
- package/dist/pi-harness.d.ts.map +1 -0
- package/dist/pi-harness.js +342 -0
- package/dist/pi-harness.js.map +1 -0
- package/dist/placement.d.ts +99 -0
- package/dist/placement.d.ts.map +1 -0
- package/dist/placement.js +67 -0
- package/dist/placement.js.map +1 -0
- package/dist/prd-to-spec.d.ts +315 -0
- package/dist/prd-to-spec.d.ts.map +1 -0
- package/dist/prd-to-spec.js +684 -0
- package/dist/prd-to-spec.js.map +1 -0
- package/dist/prepare.d.ts +121 -0
- package/dist/prepare.d.ts.map +1 -0
- package/dist/prepare.js +140 -0
- package/dist/prepare.js.map +1 -0
- package/dist/prompt.d.ts +360 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +499 -0
- package/dist/prompt.js.map +1 -0
- package/dist/protocol/ADR-FORMAT.md +47 -0
- package/dist/protocol/CLAIM-PROTOCOL.md +217 -0
- package/dist/protocol/REVIEW-PROTOCOL.md +119 -0
- package/dist/protocol/SURFACE-PROTOCOL.md +121 -0
- package/dist/protocol/TASKING-PROTOCOL.md +122 -0
- package/dist/protocol/WORK-CONTRACT.md +276 -0
- package/dist/protocol/spec-template.md +71 -0
- package/dist/protocol/task-template.md +65 -0
- package/dist/readiness.d.ts +66 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +36 -0
- package/dist/readiness.js.map +1 -0
- package/dist/reap-branches.d.ts +102 -0
- package/dist/reap-branches.d.ts.map +1 -0
- package/dist/reap-branches.js +149 -0
- package/dist/reap-branches.js.map +1 -0
- package/dist/recover-isolated.d.ts +72 -0
- package/dist/recover-isolated.d.ts.map +1 -0
- package/dist/recover-isolated.js +188 -0
- package/dist/recover-isolated.js.map +1 -0
- package/dist/registry.d.ts +172 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +296 -0
- package/dist/registry.js.map +1 -0
- package/dist/repo-config.d.ts +201 -0
- package/dist/repo-config.d.ts.map +1 -0
- package/dist/repo-config.js +414 -0
- package/dist/repo-config.js.map +1 -0
- package/dist/repo-key.d.ts +20 -0
- package/dist/repo-key.d.ts.map +1 -0
- package/dist/repo-key.js +68 -0
- package/dist/repo-key.js.map +1 -0
- package/dist/repo-mirror.d.ts +177 -0
- package/dist/repo-mirror.d.ts.map +1 -0
- package/dist/repo-mirror.js +271 -0
- package/dist/repo-mirror.js.map +1 -0
- package/dist/retry-backoff.d.ts +90 -0
- package/dist/retry-backoff.d.ts.map +1 -0
- package/dist/retry-backoff.js +98 -0
- package/dist/retry-backoff.js.map +1 -0
- package/dist/review-gate.d.ts +173 -0
- package/dist/review-gate.d.ts.map +1 -0
- package/dist/review-gate.js +261 -0
- package/dist/review-gate.js.map +1 -0
- package/dist/review-verdict.d.ts +149 -0
- package/dist/review-verdict.d.ts.map +1 -0
- package/dist/review-verdict.js +332 -0
- package/dist/review-verdict.js.map +1 -0
- package/dist/run.d.ts +221 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +963 -0
- package/dist/run.js.map +1 -0
- package/dist/scan.d.ts +308 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +374 -0
- package/dist/scan.js.map +1 -0
- package/dist/select-order.d.ts +75 -0
- package/dist/select-order.d.ts.map +1 -0
- package/dist/select-order.js +108 -0
- package/dist/select-order.js.map +1 -0
- package/dist/select-priority.d.ts +188 -0
- package/dist/select-priority.d.ts.map +1 -0
- package/dist/select-priority.js +80 -0
- package/dist/select-priority.js.map +1 -0
- package/dist/select.d.ts +25 -0
- package/dist/select.d.ts.map +1 -0
- package/dist/select.js +43 -0
- package/dist/select.js.map +1 -0
- package/dist/session-path.d.ts +36 -0
- package/dist/session-path.d.ts.map +1 -0
- package/dist/session-path.js +129 -0
- package/dist/session-path.js.map +1 -0
- package/dist/sidecar-apply.d.ts +83 -0
- package/dist/sidecar-apply.d.ts.map +1 -0
- package/dist/sidecar-apply.js +111 -0
- package/dist/sidecar-apply.js.map +1 -0
- package/dist/sidecar.d.ts +245 -0
- package/dist/sidecar.d.ts.map +1 -0
- package/dist/sidecar.js +481 -0
- package/dist/sidecar.js.map +1 -0
- package/dist/slug-namespace.d.ts +204 -0
- package/dist/slug-namespace.d.ts.map +1 -0
- package/dist/slug-namespace.js +229 -0
- package/dist/slug-namespace.js.map +1 -0
- package/dist/spec-complete.d.ts +44 -0
- package/dist/spec-complete.d.ts.map +1 -0
- package/dist/spec-complete.js +69 -0
- package/dist/spec-complete.js.map +1 -0
- package/dist/start.d.ts +97 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +633 -0
- package/dist/start.js.map +1 -0
- package/dist/status.d.ts +199 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +228 -0
- package/dist/status.js.map +1 -0
- package/dist/surface-gate.d.ts +162 -0
- package/dist/surface-gate.d.ts.map +1 -0
- package/dist/surface-gate.js +206 -0
- package/dist/surface-gate.js.map +1 -0
- package/dist/surface-persist.d.ts +86 -0
- package/dist/surface-persist.d.ts.map +1 -0
- package/dist/surface-persist.js +129 -0
- package/dist/surface-persist.js.map +1 -0
- package/dist/tasker-review-loop.d.ts +249 -0
- package/dist/tasker-review-loop.d.ts.map +1 -0
- package/dist/tasker-review-loop.js +369 -0
- package/dist/tasker-review-loop.js.map +1 -0
- package/dist/tasking-eligibility.d.ts +74 -0
- package/dist/tasking-eligibility.d.ts.map +1 -0
- package/dist/tasking-eligibility.js +52 -0
- package/dist/tasking-eligibility.js.map +1 -0
- package/dist/tasking-lock.d.ts +111 -0
- package/dist/tasking-lock.d.ts.map +1 -0
- package/dist/tasking-lock.js +256 -0
- package/dist/tasking-lock.js.map +1 -0
- package/dist/tasking.d.ts +275 -0
- package/dist/tasking.d.ts.map +1 -0
- package/dist/tasking.js +952 -0
- package/dist/tasking.js.map +1 -0
- package/dist/triage-gate.d.ts +127 -0
- package/dist/triage-gate.d.ts.map +1 -0
- package/dist/triage-gate.js +139 -0
- package/dist/triage-gate.js.map +1 -0
- package/dist/triage-persist.d.ts +163 -0
- package/dist/triage-persist.d.ts.map +1 -0
- package/dist/triage-persist.js +387 -0
- package/dist/triage-persist.js.map +1 -0
- package/dist/verdict-json.d.ts +32 -0
- package/dist/verdict-json.d.ts.map +1 -0
- package/dist/verdict-json.js +74 -0
- package/dist/verdict-json.js.map +1 -0
- package/dist/verify-workflow-template.d.ts +60 -0
- package/dist/verify-workflow-template.d.ts.map +1 -0
- package/dist/verify-workflow-template.js +126 -0
- package/dist/verify-workflow-template.js.map +1 -0
- package/dist/verify.d.ts +60 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +62 -0
- package/dist/verify.js.map +1 -0
- package/dist/watch-session.d.ts +112 -0
- package/dist/watch-session.d.ts.map +1 -0
- package/dist/watch-session.js +347 -0
- package/dist/watch-session.js.map +1 -0
- package/dist/work-layout.d.ts +198 -0
- package/dist/work-layout.d.ts.map +1 -0
- package/dist/work-layout.js +217 -0
- package/dist/work-layout.js.map +1 -0
- package/dist/work-on.d.ts +154 -0
- package/dist/work-on.d.ts.map +1 -0
- package/dist/work-on.js +387 -0
- package/dist/work-on.js.map +1 -0
- package/dist/workspace.d.ts +224 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +325 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +46 -2
- package/src/advance-ci-template.ts +203 -0
- package/src/advance-classify.ts +197 -0
- package/src/advance-drivers.ts +414 -0
- package/src/advance-isolated.ts +432 -0
- package/src/advance-lifecycle-template.ts +791 -0
- package/src/advance-loop-driver.ts +745 -0
- package/src/advance-treeless-publish.ts +177 -0
- package/src/advance.ts +1564 -0
- package/src/advancing-lock.ts +988 -0
- package/src/agent-launch.ts +137 -0
- package/src/agent-stop.ts +361 -0
- package/src/apply-decide.ts +242 -0
- package/src/apply-merge-action.ts +502 -0
- package/src/apply-persist.ts +518 -0
- package/src/arbiter.ts +372 -0
- package/src/brand.ts +111 -0
- package/src/buildable-body.ts +196 -0
- package/src/categorise.ts +158 -0
- package/src/claim-cas.ts +513 -0
- package/src/cli-spinner.ts +225 -0
- package/src/cli.ts +4369 -0
- package/src/close-job-template.ts +236 -0
- package/src/close-job.ts +319 -0
- package/src/complete.ts +1379 -0
- package/src/concurrency.ts +151 -0
- package/src/config-override.ts +116 -0
- package/src/config.ts +883 -0
- package/src/continue-branch.ts +542 -0
- package/src/cwd-section.ts +392 -0
- package/src/decision-engine.ts +272 -0
- package/src/detect.ts +124 -0
- package/src/do-autopick.ts +223 -0
- package/src/do-config.ts +589 -0
- package/src/do-remote-auto.ts +197 -0
- package/src/do.ts +2623 -0
- package/src/drop-source.ts +194 -0
- package/src/eligibility.ts +79 -0
- package/src/env-config.ts +305 -0
- package/src/failure-cause.ts +142 -0
- package/src/format.ts +313 -0
- package/src/frontmatter.ts +485 -0
- package/src/gate-readiness.ts +147 -0
- package/src/gc.ts +510 -0
- package/src/gh-failure.ts +53 -0
- package/src/git.ts +186 -0
- package/src/github.ts +468 -0
- package/src/harness.ts +355 -0
- package/src/identity.ts +322 -0
- package/src/index.ts +785 -0
- package/src/install-ci-branch-protection.ts +255 -0
- package/src/install-ci-capabilities/advance-lifecycle.ts +34 -0
- package/src/install-ci-capabilities/close-job.ts +32 -0
- package/src/install-ci-capabilities/example-noop.ts +24 -0
- package/src/install-ci-capabilities/intake.ts +34 -0
- package/src/install-ci-capabilities/verify.ts +33 -0
- package/src/install-ci-core.ts +1088 -0
- package/src/install-ci-github.ts +376 -0
- package/src/install-ci.ts +552 -0
- package/src/intake-event.ts +102 -0
- package/src/intake-marker.ts +195 -0
- package/src/intake-triage.ts +138 -0
- package/src/intake-trigger-template.ts +591 -0
- package/src/intake.ts +2445 -0
- package/src/integration-core.ts +3065 -0
- package/src/integrator.ts +771 -0
- package/src/isolation.ts +484 -0
- package/src/issue-provider.ts +733 -0
- package/src/item-lock.ts +1858 -0
- package/src/item-path.ts +75 -0
- package/src/ledger-lint.ts +332 -0
- package/src/ledger-read.ts +924 -0
- package/src/ledger-write.ts +865 -0
- package/src/lifecycle-gather.ts +298 -0
- package/src/lifecycle-pools.ts +250 -0
- package/src/merge-question-surfacer.ts +496 -0
- package/src/mint-adr.ts +362 -0
- package/src/mirror-pool-scan.ts +240 -0
- package/src/needs-attention.ts +1506 -0
- package/src/orphan-sidecar.ts +150 -0
- package/src/output.ts +89 -0
- package/src/pi-harness.ts +403 -0
- package/src/placement.ts +131 -0
- package/src/prd-to-spec.ts +1023 -0
- package/src/prepare.ts +230 -0
- package/src/prompt.ts +760 -0
- package/src/readiness.ts +98 -0
- package/src/reap-branches.ts +278 -0
- package/src/recover-isolated.ts +276 -0
- package/src/registry.ts +475 -0
- package/src/repo-config.ts +550 -0
- package/src/repo-key.ts +74 -0
- package/src/repo-mirror.ts +367 -0
- package/src/retry-backoff.ts +130 -0
- package/src/review-gate.ts +389 -0
- package/src/review-verdict.ts +422 -0
- package/src/run.ts +1430 -0
- package/src/scan.ts +611 -0
- package/src/select-order.ts +143 -0
- package/src/select-priority.ts +266 -0
- package/src/select.ts +62 -0
- package/src/session-path.ts +153 -0
- package/src/sidecar-apply.ts +216 -0
- package/src/sidecar.ts +700 -0
- package/src/slug-namespace.ts +367 -0
- package/src/spec-complete.ts +118 -0
- package/src/start.ts +974 -0
- package/src/status.ts +441 -0
- package/src/surface-gate.ts +337 -0
- package/src/surface-persist.ts +241 -0
- package/src/tasker-review-loop.ts +671 -0
- package/src/tasking-eligibility.ts +114 -0
- package/src/tasking-lock.ts +416 -0
- package/src/tasking.ts +1438 -0
- package/src/triage-gate.ts +248 -0
- package/src/triage-persist.ts +570 -0
- package/src/verdict-json.ts +73 -0
- package/src/verify-workflow-template.ts +159 -0
- package/src/verify.ts +123 -0
- package/src/watch-session.ts +397 -0
- package/src/work-layout.ts +262 -0
- package/src/work-on.ts +660 -0
- package/src/workspace.ts +502 -0
package/dist/complete.js
ADDED
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { workItemPath, WORK_ROOT } from './work-layout.js';
|
|
4
|
+
import { performIntegration, IntegrationNothingStaged, synthesiseProposeTitle, composeProposeBody, PR_TITLE_MAX, } from './integration-core.js';
|
|
5
|
+
import { ledgerWrite } from './ledger-write.js';
|
|
6
|
+
import { releaseItemLock } from './item-lock.js';
|
|
7
|
+
import { workBranchRef, parseWorkBranchRef } from './slug-namespace.js';
|
|
8
|
+
import { runAsync, localMainAheadCount } from './git.js';
|
|
9
|
+
import { hasUncommittedSourceChanges } from './agent-stop.js';
|
|
10
|
+
import { formatProposeNextStep, shouldUseColor } from './output.js';
|
|
11
|
+
import { checkGatePreconditions, detectLockfileOnDisk, } from './gate-readiness.js';
|
|
12
|
+
// Re-export the propose-mode title/body helpers from their new home (the shared
|
|
13
|
+
// integration core) so existing importers (`test/propose-pr-body.test.ts`) keep
|
|
14
|
+
// working unchanged after the gate→integrate band moved out of this file.
|
|
15
|
+
export { synthesiseProposeTitle, composeProposeBody, PR_TITLE_MAX };
|
|
16
|
+
const DEFAULT_ARBITER = 'origin';
|
|
17
|
+
const DEFAULT_INTEGRATION = 'propose';
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the integration mode the human asked for ON THIS invocation from the
|
|
20
|
+
* mutually-exclusive `--merge` / `--propose` flags. Returns the explicit mode,
|
|
21
|
+
* or `undefined` when neither flag was given (per-repo > global > default then
|
|
22
|
+
* decides). Throws when BOTH are given (they are mutually exclusive). This is
|
|
23
|
+
* the TOP of the complete-time precedence chain (flag > per-repo > global >
|
|
24
|
+
* default); the autonomous runner uses no flag and so resolves the same
|
|
25
|
+
* underlying order.
|
|
26
|
+
*/
|
|
27
|
+
export function integrationFromFlags(flags) {
|
|
28
|
+
if (flags.merge && flags.propose) {
|
|
29
|
+
throw new Error('--merge and --propose are mutually exclusive; pass at most one.');
|
|
30
|
+
}
|
|
31
|
+
if (flags.merge) {
|
|
32
|
+
return 'merge';
|
|
33
|
+
}
|
|
34
|
+
if (flags.propose) {
|
|
35
|
+
return 'propose';
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Raised for usage/environment errors (exit 1, outcome 'usage-error'). */
|
|
40
|
+
class CompleteUsageError extends Error {
|
|
41
|
+
}
|
|
42
|
+
/** Raised for a deliberate REFUSAL (exit 1, outcome 'refused'). */
|
|
43
|
+
class CompleteRefusal extends Error {
|
|
44
|
+
kind;
|
|
45
|
+
slug;
|
|
46
|
+
constructor(message, kind, slug) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.kind = kind;
|
|
49
|
+
this.slug = slug;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Run the complete ritual. Never throws for the expected gate-failed /
|
|
54
|
+
* rebase-conflict / refused cases — those are returned with exit 1 and a
|
|
55
|
+
* specific outcome. Usage/environment problems also surface as exit 1.
|
|
56
|
+
*/
|
|
57
|
+
export async function performComplete(options) {
|
|
58
|
+
const note = options.note ?? (() => { });
|
|
59
|
+
try {
|
|
60
|
+
return await runComplete(options, note);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
// AUTONOMOUS-STRAND SURFACE (task
|
|
64
|
+
// `autonomous-integration-refusal-surfaces-not-strands-in-progress`, prd
|
|
65
|
+
// `ledger-integrity` story 7). On the AUTONOMOUS path (`surfaceArbiter` set
|
|
66
|
+
// — the human-vs-autonomous gate the core's other failures use), a SOURCE-
|
|
67
|
+
// STRAND `CompleteRefusal` (the slug-is-stuck `nothing to complete`) and the
|
|
68
|
+
// core's empty-commit `IntegrationNothingStaged` would otherwise return a
|
|
69
|
+
// bare `refused` and silently strand the item in `work/in-progress/` on the
|
|
70
|
+
// arbiter — so the next autonomous tick re-claims it and re-crashes the
|
|
71
|
+
// SAME way forever. Mirror `run`'s posture (an autonomous failure NEVER
|
|
72
|
+
// silently strands the item in in-progress/) by surfacing it to
|
|
73
|
+
// `needs-attention/` via the TREE-LESS arbiter-truth seam
|
|
74
|
+
// (`applyTreelessNeedsAttentionTransition`, the same mechanism `requeue`
|
|
75
|
+
// uses in reverse): there is no working-tree `.md` to `git mv` here (the
|
|
76
|
+
// strand IS that the source file is missing on the branch), so the
|
|
77
|
+
// cwd-bound `applyNeedsAttentionTransition` cannot be used — the tree-less
|
|
78
|
+
// seam resolves the source folder on the arbiter and CAS-publishes the
|
|
79
|
+
// move-only `.md` relocation. The DIVERGED-MAIN refusal is an env/operator
|
|
80
|
+
// condition (NOT a stuck task), so it is excluded — leave it `refused`.
|
|
81
|
+
// When the tree-less surface cannot land (CAS contention exhausted, or no
|
|
82
|
+
// arbiter), report the HONEST still-in-progress signal
|
|
83
|
+
// (`outcome: 'surface-unmoved'`) the gate-fail path's `moved:false` mirrors
|
|
84
|
+
// in `do.ts`/`run.ts` — never a fake success.
|
|
85
|
+
const strandSlug = strandRefusalSlug(err);
|
|
86
|
+
if (strandSlug && options.surfaceArbiter) {
|
|
87
|
+
return await surfaceAutonomousStrand({
|
|
88
|
+
cwd: options.cwd,
|
|
89
|
+
slug: strandSlug,
|
|
90
|
+
reason: err.message,
|
|
91
|
+
arbiter: options.surfaceArbiter,
|
|
92
|
+
env: options.env,
|
|
93
|
+
note,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (err instanceof CompleteRefusal ||
|
|
97
|
+
err instanceof IntegrationNothingStaged) {
|
|
98
|
+
// `IntegrationNothingStaged` is the core's empty-commit refusal — the same
|
|
99
|
+
// `refused` outcome the inline band raised before the extraction. The
|
|
100
|
+
// diverged-main `CompleteRefusal` (env/operator condition) also lands here
|
|
101
|
+
// on the autonomous path, deliberately UNCHANGED: it is NOT a stuck task.
|
|
102
|
+
return { exitCode: 1, outcome: 'refused', message: err.message };
|
|
103
|
+
}
|
|
104
|
+
if (err instanceof CompleteUsageError) {
|
|
105
|
+
return { exitCode: 1, outcome: 'usage-error', message: err.message };
|
|
106
|
+
}
|
|
107
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
108
|
+
return { exitCode: 1, outcome: 'usage-error', message };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The SLUG of an autonomous-bounceable strand refusal, or `undefined` when the
|
|
113
|
+
* caught error is NOT a strand class (a `diverged-main` refusal, a usage error,
|
|
114
|
+
* a usual throw). The two strand classes — a `source-strand` {@link
|
|
115
|
+
* CompleteRefusal} and the core's {@link IntegrationNothingStaged} — BOTH leave
|
|
116
|
+
* the slug stuck in `work/in-progress/` on the arbiter (the claim landed and
|
|
117
|
+
* nothing has moved it since), so the autonomous bounce-set is exactly those
|
|
118
|
+
* two. The `diverged-main` refusal is deliberately EXCLUDED: it is an
|
|
119
|
+
* env/operator condition (local main ahead of `<arbiter>/main`), not a stuck
|
|
120
|
+
* task; bouncing it would mis-attribute an env problem to the work.
|
|
121
|
+
*/
|
|
122
|
+
function strandRefusalSlug(err) {
|
|
123
|
+
if (err instanceof CompleteRefusal && err.kind === 'source-strand') {
|
|
124
|
+
return err.slug;
|
|
125
|
+
}
|
|
126
|
+
if (err instanceof IntegrationNothingStaged) {
|
|
127
|
+
return err.slug;
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Surface an autonomous-path strand refusal to `needs-attention/` on the
|
|
133
|
+
* arbiter via the TREE-LESS seam ({@link
|
|
134
|
+
* ledgerWrite.applyTreelessNeedsAttentionTransition}). The mechanism is the
|
|
135
|
+
* one `requeue`/`continue` already use in reverse: fetch `<arbiter>/main`,
|
|
136
|
+
* resolve the slug's actual current folder ON THE ARBITER (arbiter-is-truth),
|
|
137
|
+
* build the one-file `work/<src>/<slug>.md → work/needs-attention/<slug>.md`
|
|
138
|
+
* move on a scratch index, and CAS-publish it. It NEVER touches the caller's
|
|
139
|
+
* working tree / HEAD / index — important here because the refusal was raised
|
|
140
|
+
* precisely because the working tree does NOT hold an `.md` to `git mv`. It is
|
|
141
|
+
* idempotent (a re-surface of an already-surfaced slug is a no-op) and reports
|
|
142
|
+
* `moved: false` honestly when it cannot land (CAS contention exhausted, or no
|
|
143
|
+
* arbiter). The HONEST still-in-progress signal is then `outcome:
|
|
144
|
+
* 'surface-unmoved'` — the SAME vocabulary `do`/`run`/`start` expose for the
|
|
145
|
+
* after-commit tree-less surface that did not land; never a fake success.
|
|
146
|
+
*/
|
|
147
|
+
async function surfaceAutonomousStrand(params) {
|
|
148
|
+
const { cwd, slug, reason, arbiter, env, note } = params;
|
|
149
|
+
const surfaced = await ledgerWrite.applyTreelessNeedsAttentionTransition({
|
|
150
|
+
cwd,
|
|
151
|
+
slug,
|
|
152
|
+
reason,
|
|
153
|
+
arbiter,
|
|
154
|
+
env,
|
|
155
|
+
note,
|
|
156
|
+
});
|
|
157
|
+
if (surfaced.moved) {
|
|
158
|
+
const message = `'${slug}' refused (${reason}); surfaced to work/needs-attention/ on ` +
|
|
159
|
+
`${arbiter}/main so the next autonomous tick does NOT re-claim it.`;
|
|
160
|
+
note(message);
|
|
161
|
+
return {
|
|
162
|
+
exitCode: 1,
|
|
163
|
+
outcome: 'strand-surfaced',
|
|
164
|
+
routedToNeedsAttention: true,
|
|
165
|
+
message,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const message = `'${slug}' refused (${reason}); the tree-less surface to needs-attention ` +
|
|
169
|
+
`did NOT reach ${arbiter}/main — the item is HONESTLY still IN-PROGRESS on ` +
|
|
170
|
+
`the arbiter (retry/resolve). ${surfaced.reasonNotMoved ?? ''}`.trim();
|
|
171
|
+
note(message);
|
|
172
|
+
return {
|
|
173
|
+
exitCode: 1,
|
|
174
|
+
outcome: 'surface-unmoved',
|
|
175
|
+
routedToNeedsAttention: false,
|
|
176
|
+
message,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Locate the slug's ledger record at a RENAMED done-position on the branch tree, to
|
|
181
|
+
* survive a SELF-RENAMING FOLDER task (the `folder-taxonomy-reorg-and-rename`
|
|
182
|
+
* migration).
|
|
183
|
+
*
|
|
184
|
+
* The trap: a task whose job is to `git mv` the ledger folders themselves (e.g.
|
|
185
|
+
* `done/ -> tasks/done/`) runs through the runner's INSTALLED (pre-rename) binary,
|
|
186
|
+
* whose compiled-in {@link workItemPath} still resolves `done` to `work/done/`.
|
|
187
|
+
* When the agent has placed its OWN record at the NEW done-position
|
|
188
|
+
* (`work/tasks/done/<slug>.md`) as part of the migration, the binary-known
|
|
189
|
+
* `work/done/<slug>.md` does not exist, and the resolver below would crash with
|
|
190
|
+
* `nothing to complete`, reaping the build.
|
|
191
|
+
*
|
|
192
|
+
* This scan is the layout-agnostic backstop: when the record is at NONE of the
|
|
193
|
+
* binary-known ledger folders, walk `work/` and return true iff `<slug>.md` exists
|
|
194
|
+
* directly inside a folder whose LEAF name is `done` (covering BOTH `work/done/`
|
|
195
|
+
* and a renamed `work/tasks/done/`). It deliberately matches ONLY a `done` leaf:
|
|
196
|
+
* a record left in a renamed POOL (`tasks/ready/`) is NOT a finished task and must
|
|
197
|
+
* still refuse, not be mis-integrated as done. When this fires, the slug is treated
|
|
198
|
+
* as already-done-moved by the agent into its terminal position, so the runner
|
|
199
|
+
* SKIPS its own `git mv` (the existing `source: 'done'` / stranded-done path) and
|
|
200
|
+
* just integrates the agent's tree as-is — no binary-vs-branch folder-name
|
|
201
|
+
* reconciliation is needed.
|
|
202
|
+
*/
|
|
203
|
+
function recordAtRenamedDonePosition(cwd, slug) {
|
|
204
|
+
const file = `${slug}.md`;
|
|
205
|
+
const workRoot = join(cwd, WORK_ROOT);
|
|
206
|
+
const found = [];
|
|
207
|
+
const walk = (dir) => {
|
|
208
|
+
let entries;
|
|
209
|
+
try {
|
|
210
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
for (const entry of entries) {
|
|
216
|
+
const full = join(dir, entry.name);
|
|
217
|
+
if (entry.isDirectory()) {
|
|
218
|
+
walk(full);
|
|
219
|
+
}
|
|
220
|
+
else if (entry.name === file && basename(dir) === 'done') {
|
|
221
|
+
found.push(full);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
walk(workRoot);
|
|
226
|
+
return found.length > 0;
|
|
227
|
+
}
|
|
228
|
+
async function runComplete(options, note) {
|
|
229
|
+
const cwd = options.cwd;
|
|
230
|
+
const env = options.env;
|
|
231
|
+
const arbiter = options.arbiter ?? DEFAULT_ARBITER;
|
|
232
|
+
// The integration mode. The shared core integrates a `merge` automatically on a
|
|
233
|
+
// green gate (and an `approve` when review is on) and leaves a `propose` to a
|
|
234
|
+
// human; there is no downgrade, so the mode it resolves always equals this one.
|
|
235
|
+
// The tail below still reads the resolved mode from `core.integration.mode`.
|
|
236
|
+
const requestedMode = options.integration ?? DEFAULT_INTEGRATION;
|
|
237
|
+
if ((await gitSoft(['rev-parse', '--git-dir'], cwd, env)).status !== 0) {
|
|
238
|
+
throw new CompleteUsageError('not inside a git repository');
|
|
239
|
+
}
|
|
240
|
+
if ((await gitSoft(['remote', 'get-url', arbiter], cwd, env)).status !== 0) {
|
|
241
|
+
throw new CompleteUsageError(`no git remote named '${arbiter}' (set one, or pass --arbiter)`);
|
|
242
|
+
}
|
|
243
|
+
// Resolve the slug + work branch. `complete` runs ON the work branch (it
|
|
244
|
+
// rebases + pushes it), and the branch carries the namespaced `work/<type>-
|
|
245
|
+
// <slug>` identity. So PREFER the branch HEAD is on (recovering BOTH the type
|
|
246
|
+
// and the slug from it); only when an explicit slug is given AND HEAD is not on
|
|
247
|
+
// a work branch do we synthesise the task-namespaced branch (`complete` is a
|
|
248
|
+
// task command).
|
|
249
|
+
const headBranch = await currentBranch(cwd, env);
|
|
250
|
+
const headParsed = parseWorkBranchRef(headBranch);
|
|
251
|
+
const slug = options.slug || headParsed?.slug || '';
|
|
252
|
+
if (!slug) {
|
|
253
|
+
throw new CompleteUsageError('missing <slug> and the current branch is not a work/<type>-<slug> ' +
|
|
254
|
+
'branch. usage: dorfl complete [<slug>] [--skip-verify] ' +
|
|
255
|
+
'[--type t] [--message s] [--arbiter remote]');
|
|
256
|
+
}
|
|
257
|
+
// The branch HEAD is on when it IS a work branch for this slug (so an explicit
|
|
258
|
+
// `prd:`-style recovery still completes the branch it is standing on); else
|
|
259
|
+
// synthesise the task branch.
|
|
260
|
+
const branch = headParsed && headParsed.slug === slug
|
|
261
|
+
? headBranch
|
|
262
|
+
: workBranchRef('task', slug);
|
|
263
|
+
// We must be ON the work branch — that is where the agent's work lives and
|
|
264
|
+
// what we rebase + push. (Unlike `start`, `complete` mutates the checkout.)
|
|
265
|
+
const head = headBranch;
|
|
266
|
+
if (head !== branch) {
|
|
267
|
+
throw new CompleteUsageError(`not on ${branch} (HEAD is '${head}'). ` +
|
|
268
|
+
`Check out the work branch before completing '${slug}'.`);
|
|
269
|
+
}
|
|
270
|
+
// The task is normally in `tasks/ready/` (claim leaves the body there — task
|
|
271
|
+
// `cutover-claim-body-stays-and-complete-sources-from-backlog`). The legacy
|
|
272
|
+
// runner-owned recovery path used to also probe `work/needs-attention/<slug>.md`,
|
|
273
|
+
// but the per-item-lock cutover (task
|
|
274
|
+
// `cutover-needs-attention-becomes-lock-stuck-recovery-surface`) retired that
|
|
275
|
+
// folder: a stuck item is now the lock `state: stuck`, the body stays in
|
|
276
|
+
// `tasks/ready/` while claimed, and a recovery `complete` sources from there
|
|
277
|
+
// like any other build. The `needs-attention/` folder probe + the `recovering`
|
|
278
|
+
// re-gate branch are GONE (this task,
|
|
279
|
+
// `finish-needs-attention-folder-cutover-remove-legacy-recovery-readers`):
|
|
280
|
+
// nothing writes that folder anymore so the arm could not fire.
|
|
281
|
+
//
|
|
282
|
+
// This LOCAL source resolution is now only a PRE-FLIGHT (which folder the
|
|
283
|
+
// checkout holds) and the content FALLBACK: the integration core's done-move
|
|
284
|
+
// RESOLVES THE ACTUAL SOURCE FOLDER FROM THE ARBITER (arbiter-is-truth) and
|
|
285
|
+
// removes it from THERE, so the move can never disagree with what the arbiter
|
|
286
|
+
// holds even when the local tree diverges (ledger-integrity defect 1). The
|
|
287
|
+
// `source` we pass is the arbiter-content fallback for the degenerate "arbiter
|
|
288
|
+
// holds nothing" case, not the authority. `in-progress/` is RETAINED below for
|
|
289
|
+
// the legacy/bounce surfaces that may still source from it until its folder
|
|
290
|
+
// removal (9c, OUT of this task's scope).
|
|
291
|
+
const backlog = workItemPath(cwd, 'tasks-ready', slug);
|
|
292
|
+
const staged = workItemPath(cwd, 'tasks-backlog', slug);
|
|
293
|
+
const inProgress = workItemPath(cwd, 'in-progress', slug);
|
|
294
|
+
const done = workItemPath(cwd, 'done', slug);
|
|
295
|
+
const onBacklog = existsSync(backlog);
|
|
296
|
+
// `--allow-backlog` drive (prd
|
|
297
|
+
// `do-allow-backlog-drive-staged-tasks-without-promotion`): a staged task driven
|
|
298
|
+
// in place RESTS in `tasks/backlog/` (claim never moved it). Detect it as a build
|
|
299
|
+
// source so the done-move goes `tasks/backlog/ → tasks/done/` DIRECTLY (the
|
|
300
|
+
// human's explicit drive IS the promotion; it never bounces through the pool).
|
|
301
|
+
// LOWER priority than `tasks/ready/` so a same-slug pool copy wins the resolution.
|
|
302
|
+
// GATED on the explicit flag: WITHOUT it, a body in staging is a corrupt/wip
|
|
303
|
+
// strand `complete` must REFUSE (the honest "not finished, not done" position),
|
|
304
|
+
// NOT silently integrate — only the deliberate drive promotes from staging.
|
|
305
|
+
const onPreBacklog = options.allowBacklog === true && existsSync(staged);
|
|
306
|
+
const onInProgress = existsSync(inProgress);
|
|
307
|
+
// SELF-RENAMING FOLDER task backstop: the binary-known `work/done/<slug>.md`,
|
|
308
|
+
// OR — when none of the binary-known ledger folders hold the record — the slug
|
|
309
|
+
// at a RENAMED done-position the migration agent placed it in (e.g.
|
|
310
|
+
// `work/tasks/done/<slug>.md`). See {@link recordAtRenamedDonePosition}. The
|
|
311
|
+
// scan runs ONLY in the all-binary-folders-empty case, so a normal task's
|
|
312
|
+
// resolution is byte-for-byte unchanged.
|
|
313
|
+
const onDone = existsSync(done) ||
|
|
314
|
+
(!onBacklog &&
|
|
315
|
+
!onPreBacklog &&
|
|
316
|
+
!onInProgress &&
|
|
317
|
+
recordAtRenamedDonePosition(cwd, slug));
|
|
318
|
+
// STRANDED-DONE AUTO-RECOVER (prd `ledger-integrity` story 6, the autonomous
|
|
319
|
+
// half of `finish-already-committed-branch`). When neither in-progress/ nor
|
|
320
|
+
// needs-attention/ holds the slug on the BRANCH tree BUT done/ does, the work
|
|
321
|
+
// branch was already built + done-moved + committed by a prior run that never
|
|
322
|
+
// landed on the arbiter (a terminal push failed, or the PR never merged). The
|
|
323
|
+
// autonomous `do`/`advance`/plain-`complete` path used to refuse this with
|
|
324
|
+
// `nothing to complete`; instead route into the SHARED recover tail (the SAME
|
|
325
|
+
// `committedRecovery: true` path `complete --isolated` drives), reusing the
|
|
326
|
+
// existing capability. Detection here is the FRONT-GATE only — folder shape;
|
|
327
|
+
// `recoverAlreadyCommitted` owns the unspoofable tip-vs-arbiter `isAncestor`
|
|
328
|
+
// decision (one reachability check, not two), so a tip ALREADY on
|
|
329
|
+
// `<arbiter>/main` returns `already-integrated` (clean no-op, NEVER a
|
|
330
|
+
// re-push/double-integrate). Mutually exclusive with the build-path source +
|
|
331
|
+
// `recovering` flags — the core ignores them when `committedRecovery` is set.
|
|
332
|
+
const folderShapeStranded = !onBacklog && !onPreBacklog && !onInProgress && onDone;
|
|
333
|
+
// DIRTY-CONTINUE GATE (task `recover-autodetect-gated-on-nothing-to-commit`).
|
|
334
|
+
// The folder-shape stranded-done auto-detect is necessary but NOT sufficient on
|
|
335
|
+
// a CONTINUE: a requeued task whose prior attempt already done-moved the slug
|
|
336
|
+
// into `done/` on the kept branch looks IDENTICAL by folder shape to a genuine
|
|
337
|
+
// finished strand, even when THIS run's agent produced NEW uncommitted edits.
|
|
338
|
+
// The recover path skips the build/commit/done-move steps and only rebases the
|
|
339
|
+
// ALREADY-committed kept tip, so firing the recover on a dirty continue would
|
|
340
|
+
// SILENTLY DISCARD the new work. Disambiguate on the WORKING-TREE-DIRTY check
|
|
341
|
+
// (the porcelain half of `isWorkBranchDiffEmpty`, extracted as
|
|
342
|
+
// `hasUncommittedSourceChanges`). NOT the core's `nothingStaged`: that is
|
|
343
|
+
// INDEX-only and reads empty BEFORE the core's later `git add -A`, so it would
|
|
344
|
+
// miss the agent's UNSTAGED edits. NOT the FULL `isWorkBranchDiffEmpty`: its
|
|
345
|
+
// commits-ahead half is true for a GENUINE STRAND too (the kept tip carries
|
|
346
|
+
// source commits ahead of main), so it would WRONGLY BLOCK the legitimate
|
|
347
|
+
// recover and break the task-1 / finished-strand behaviour.
|
|
348
|
+
const dirtyContinue = folderShapeStranded && (await hasUncommittedSourceChanges({ cwd, env }));
|
|
349
|
+
const committedRecovery = folderShapeStranded && !dirtyContinue;
|
|
350
|
+
// CONTINUE-BUILD (task `complete-builds-on-already-done-moved-continue`,
|
|
351
|
+
// scope option A — the explicit `source: 'done'` contract). On a DIRTY
|
|
352
|
+
// continue whose kept branch already holds the slug in `work/done/`, route
|
|
353
|
+
// through the build path's NEW continue-build state: the integration core
|
|
354
|
+
// SKIPS the step-2 `git mv` (the slug is already in done/) and is exempted
|
|
355
|
+
// from the originTrust read + the divergent-done-move reconcile (there is no
|
|
356
|
+
// first-time move on this commit), but still runs prepare → gate → `git add
|
|
357
|
+
// -A` → commit → rebase → integrate on the NEW work. Replaces the blocker
|
|
358
|
+
// task's needs-attention BOUNCE (the dirty continue now AUTO-LANDS instead
|
|
359
|
+
// of surfacing). See `docs/adr/continue-build-already-done-moved.md`.
|
|
360
|
+
// Default branch (post `needs-attention/`-folder cutover, this task): when
|
|
361
|
+
// NONE of the recognised source folders holds the slug, the build cannot
|
|
362
|
+
// proceed — refuse via the `existsSync(sourcePath)` check below (the explicit
|
|
363
|
+
// "nothing to complete" CompleteRefusal). We default `source` to `'tasks-ready'`
|
|
364
|
+
// so `sourcePath` resolves to the canonical pool path the refusal message
|
|
365
|
+
// names; the refusal fires because that file does not exist on disk.
|
|
366
|
+
const source = dirtyContinue
|
|
367
|
+
? 'done'
|
|
368
|
+
: onBacklog
|
|
369
|
+
? 'tasks-ready'
|
|
370
|
+
: onInProgress
|
|
371
|
+
? 'in-progress'
|
|
372
|
+
: onPreBacklog
|
|
373
|
+
? 'tasks-backlog'
|
|
374
|
+
: 'tasks-ready';
|
|
375
|
+
const sourcePath = source === 'tasks-ready'
|
|
376
|
+
? backlog
|
|
377
|
+
: source === 'tasks-backlog'
|
|
378
|
+
? staged
|
|
379
|
+
: source === 'in-progress'
|
|
380
|
+
? inProgress
|
|
381
|
+
: done;
|
|
382
|
+
if (dirtyContinue) {
|
|
383
|
+
// Announce LOUDLY (parallel to the `committedRecovery` recovery note above):
|
|
384
|
+
// the autonomous integrate path took the CONTINUE-BUILD branch, not the
|
|
385
|
+
// clean-strand recover and not a refusal — the CI/job log must record that
|
|
386
|
+
// the new work was built + integrated on top of the kept already-done-moved
|
|
387
|
+
// tip (no second `git mv`).
|
|
388
|
+
note(`>> continue-build on '${slug}': the task is already in work/done/ on the ` +
|
|
389
|
+
'kept branch (a prior attempt done-moved it); building + integrating the ' +
|
|
390
|
+
'new uncommitted work on top of the kept tip (no second git mv).');
|
|
391
|
+
}
|
|
392
|
+
// The presence check guards the genuine-strand case (no record anywhere). It is
|
|
393
|
+
// DELIBERATELY skipped for `source === 'done'` (the continue-build / stranded-done
|
|
394
|
+
// state): there the record is ALREADY in its terminal done-position — presence
|
|
395
|
+
// was established by `folderShapeStranded`/`onDone`, which is LAYOUT-AWARE (it
|
|
396
|
+
// accepts a renamed `work/tasks/done/<slug>.md` via `recordAtRenamedDonePosition`).
|
|
397
|
+
// The binary's `sourcePath` (= `done` = `work/done/<slug>.md`) would NOT exist for
|
|
398
|
+
// a self-renaming-folder task, so re-checking it here would wrongly refuse a
|
|
399
|
+
// build the done-detection already accepted. `source: 'done'` skips the `git mv`
|
|
400
|
+
// anyway, so there is nothing this check needs to protect for that branch.
|
|
401
|
+
if (!committedRecovery && source !== 'done' && !existsSync(sourcePath)) {
|
|
402
|
+
throw new CompleteRefusal(`work/tasks/ready/${slug}.md not found — ` +
|
|
403
|
+
'nothing to complete (already done, or wrong slug?).', 'source-strand', slug);
|
|
404
|
+
}
|
|
405
|
+
// `source: 'done'` is the CONTINUE-BUILD state — NOT a recover (the agent
|
|
406
|
+
// produced new work this run) and NOT the clean-strand recover
|
|
407
|
+
// (`committedRecovery` is mutually exclusive with `dirtyContinue` by
|
|
408
|
+
// construction above). It falls through to `performIntegration` like a
|
|
409
|
+
// normal build, where the core skips the step-2 `git mv` for this source.
|
|
410
|
+
// Post `needs-attention/`-folder cutover (task
|
|
411
|
+
// `finish-needs-attention-folder-cutover-remove-legacy-recovery-readers`):
|
|
412
|
+
// `source === 'needs-attention'` is no longer reachable here (the folder is
|
|
413
|
+
// retired; a stuck item is the lock `state: stuck` and the body rests in
|
|
414
|
+
// `tasks/ready/`). A recovery `complete` of a stuck item now sources from
|
|
415
|
+
// `tasks/ready/` like a normal build, so `recovering` is always `false`. The
|
|
416
|
+
// integration core still accepts the flag (vestigial, ignored) — every caller
|
|
417
|
+
// passes `false` and the re-gate branches that read it are dead.
|
|
418
|
+
const recovering = false;
|
|
419
|
+
if (committedRecovery) {
|
|
420
|
+
// Announce LOUDLY: a stranded already-complete branch signals an EARLIER
|
|
421
|
+
// un-merged PR — the CI/job log must record that the autonomous path took
|
|
422
|
+
// the recovery branch, not a normal completion. The `already-integrated`
|
|
423
|
+
// no-op gets its own clear note from the core itself.
|
|
424
|
+
note(`>> recovered a stranded already-complete branch for '${slug}' — ` +
|
|
425
|
+
'integrating the kept commit (no rebuild). This signals an earlier ' +
|
|
426
|
+
'un-merged PR.');
|
|
427
|
+
}
|
|
428
|
+
// Pre-flight DIVERGENCE GUARD (merge mode only) — the SAME class of refusal `do`
|
|
429
|
+
// raises up front: a local `main` AHEAD of `<arbiter>/main` (unpushed commits)
|
|
430
|
+
// cannot be fast-forwarded by the merge-back, so refuse BEFORE the gate so no
|
|
431
|
+
// work is wasted. Only merge mode ff's local `main`; propose only switches to it
|
|
432
|
+
// (no ff), so the guard is irrelevant there. `--ignore-diverged-main` overrides
|
|
433
|
+
// (mirrors `--ignore-not-ready`); when overridden, the now-NON-FATAL
|
|
434
|
+
// `syncLocalMain` handles the persisting divergence honestly at integrate-time.
|
|
435
|
+
if (requestedMode === 'merge' && options.ignoreDivergedMain !== true) {
|
|
436
|
+
await gitHard(['fetch', '--quiet', arbiter], cwd, env);
|
|
437
|
+
const ahead = await localMainAheadCount(cwd, arbiter, env);
|
|
438
|
+
if (ahead > 0) {
|
|
439
|
+
throw new CompleteRefusal(`local main is ahead of ${arbiter}/main by ${ahead} commit` +
|
|
440
|
+
`${ahead === 1 ? '' : 's'} (unpushed); a merge completion ff's local ` +
|
|
441
|
+
"main, which can't fast-forward against a diverged main — push or " +
|
|
442
|
+
'reconcile main first (or re-run with --ignore-diverged-main to ' +
|
|
443
|
+
'proceed anyway).', 'diverged-main', slug);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// STATIC fresh-worktree-gate readiness guard — the human/recovery mirror of
|
|
447
|
+
// `performDo`'s step 3d (task
|
|
448
|
+
// `do-fails-fast-when-acceptance-gate-statically-unrunnable`). When the fresh-
|
|
449
|
+
// worktree gate is ON for THIS invocation AND `prepare` resolves to no commands
|
|
450
|
+
// AND a lockfile is present, the throwaway worktree the gate runs in will have
|
|
451
|
+
// no installed deps, so the gate cannot run. Surface the precise error BEFORE
|
|
452
|
+
// `performIntegration` does any work (skip-verify bypasses the gate entirely
|
|
453
|
+
// and so bypasses this guard too — if there is no gate to run, there is no
|
|
454
|
+
// precondition to check). Gated on `freshWorktreeGate === true` for THIS
|
|
455
|
+
// invocation: when OFF, the gate runs in the CURRENT checkout (which carries
|
|
456
|
+
// its deps), so the throwaway-worktree reasoning does not apply.
|
|
457
|
+
if (options.skipVerify !== true) {
|
|
458
|
+
const guard = checkGatePreconditions({
|
|
459
|
+
freshWorktreeGate: options.freshWorktreeGate,
|
|
460
|
+
prepare: options.prepare,
|
|
461
|
+
lockfile: detectLockfileOnDisk(cwd),
|
|
462
|
+
});
|
|
463
|
+
if (guard !== undefined) {
|
|
464
|
+
throw new CompleteRefusal(guard.message, 'gate-unrunnable');
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// CORE: run the SHARED gate→integrate band (verify → review → effective-mode
|
|
468
|
+
// decision → done-move → commit → rebase → integrate → needs-attention routing).
|
|
469
|
+
// It returns DATA (the routing + the effective-mode decision already happened
|
|
470
|
+
// inside it); the TAIL below does only `complete`'s caller-specific post-step
|
|
471
|
+
// (switch-to-main / ff / delete-branch / `--no-switch` / the propose next-step
|
|
472
|
+
// block). The human-vs-autonomous difference rides on `surfaceArbiter` (DATA).
|
|
473
|
+
// `IntegrationNothingStaged` is the one refusal the core raises (empty commit);
|
|
474
|
+
// `performComplete`'s try/catch maps it to `refused`, unchanged.
|
|
475
|
+
const core = await performIntegration({
|
|
476
|
+
cwd,
|
|
477
|
+
arbiter,
|
|
478
|
+
slug,
|
|
479
|
+
// The namespaced work branch HEAD is on (resolved above) — pass it through
|
|
480
|
+
// so the integrate core pushes the EXACT branch, not a re-synthesised default.
|
|
481
|
+
branch,
|
|
482
|
+
source,
|
|
483
|
+
recovering,
|
|
484
|
+
// Route stranded-done into the shared recover tail (front-gate detection;
|
|
485
|
+
// the core does the unspoofable tip-vs-arbiter ancestry check).
|
|
486
|
+
committedRecovery,
|
|
487
|
+
prepare: options.prepare,
|
|
488
|
+
verify: options.verify,
|
|
489
|
+
freshWorktreeGate: options.freshWorktreeGate,
|
|
490
|
+
// The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) — the
|
|
491
|
+
// git-alone FLOOR of the cross-job land-queue, resolved through the gate-family
|
|
492
|
+
// precedence chain (prd `land-time-reverify-and-parallel-merge-ceiling` Story
|
|
493
|
+
// 5). Threaded so a wide-matrix CI's raised cap actually reaches the merge loop.
|
|
494
|
+
mergeRetries: options.mergeRetries,
|
|
495
|
+
skipVerify: options.skipVerify,
|
|
496
|
+
// The untrusted-origin build-propose rule's override (task
|
|
497
|
+
// `untrusted-origin-forces-build-propose`): an explicit `--merge` lets the
|
|
498
|
+
// operator land an untrusted-origin task on main; the autonomous path leaves
|
|
499
|
+
// it unset so untrusted-origin reliably forces propose.
|
|
500
|
+
explicitMerge: options.explicitMerge,
|
|
501
|
+
review: options.review,
|
|
502
|
+
reviewGate: options.reviewGate,
|
|
503
|
+
reviewModel: options.reviewModel,
|
|
504
|
+
reviewMaxRounds: options.reviewMaxRounds,
|
|
505
|
+
mode: requestedMode,
|
|
506
|
+
noPR: options.noPR,
|
|
507
|
+
providerInstance: options.providerInstance,
|
|
508
|
+
openPr: options.openPr,
|
|
509
|
+
body: options.body,
|
|
510
|
+
type: options.type,
|
|
511
|
+
message: options.message,
|
|
512
|
+
surfaceArbiter: options.surfaceArbiter,
|
|
513
|
+
watch: options.watch,
|
|
514
|
+
watchSink: options.watchSink,
|
|
515
|
+
color: options.color,
|
|
516
|
+
sessionsDir: options.sessionsDir,
|
|
517
|
+
env,
|
|
518
|
+
agentEnv: options.agentEnv,
|
|
519
|
+
note,
|
|
520
|
+
});
|
|
521
|
+
// The FAILURE outcomes map 1:1 onto `complete`'s — the core already note()'d
|
|
522
|
+
// the reason and did any routing; the tail never runs for them.
|
|
523
|
+
if (core.outcome === 'prepare-failed') {
|
|
524
|
+
return {
|
|
525
|
+
exitCode: 1,
|
|
526
|
+
outcome: 'prepare-failed',
|
|
527
|
+
routedToNeedsAttention: core.routedToNeedsAttention,
|
|
528
|
+
branch: core.branch,
|
|
529
|
+
message: core.reason ?? '',
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
if (core.outcome === 'gate-failed') {
|
|
533
|
+
return {
|
|
534
|
+
exitCode: 1,
|
|
535
|
+
outcome: 'gate-failed',
|
|
536
|
+
routedToNeedsAttention: core.routedToNeedsAttention,
|
|
537
|
+
branch: core.branch,
|
|
538
|
+
message: core.reason ?? '',
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
if (core.outcome === 'review-blocked') {
|
|
542
|
+
return {
|
|
543
|
+
exitCode: 1,
|
|
544
|
+
outcome: 'review-blocked',
|
|
545
|
+
routedToNeedsAttention: core.routedToNeedsAttention,
|
|
546
|
+
branch: core.branch,
|
|
547
|
+
message: core.reason ?? '',
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
if (core.outcome === 'review-unparseable') {
|
|
551
|
+
// Gate 2 RAN but its verdict was UNPARSEABLE (malformed JSON). The core already
|
|
552
|
+
// routed it work-preservingly via `applyNeedsAttentionTransition` (branch pushed +
|
|
553
|
+
// surfaced on the autonomous path). Map it 1:1 like the other routed failures; the
|
|
554
|
+
// `do`/`run` tail re-labels its cause `transient-infra` (NOT a reviewer block) off
|
|
555
|
+
// the parse-failure phrase in `core.reason`.
|
|
556
|
+
return {
|
|
557
|
+
exitCode: 1,
|
|
558
|
+
outcome: 'review-unparseable',
|
|
559
|
+
routedToNeedsAttention: core.routedToNeedsAttention,
|
|
560
|
+
branch: core.branch,
|
|
561
|
+
message: core.reason ?? '',
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
if (core.outcome === 'rebase-conflict') {
|
|
565
|
+
return {
|
|
566
|
+
exitCode: 1,
|
|
567
|
+
outcome: 'rebase-conflict',
|
|
568
|
+
routedToNeedsAttention: core.routedToNeedsAttention,
|
|
569
|
+
branch: core.branch,
|
|
570
|
+
commitMessage: core.commitMessage,
|
|
571
|
+
message: core.reason ?? '',
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
if (core.outcome === 'already-integrated') {
|
|
575
|
+
// Stranded-done auto-recover: the kept tip is ALREADY on `<arbiter>/main`
|
|
576
|
+
// (the PR merged out-of-band before the re-run). A clean, successful no-op
|
|
577
|
+
// — NEVER a re-push / second PR. The core already emitted its honest note;
|
|
578
|
+
// the caller's tail (switch-to-main / ff / delete-branch) is irrelevant
|
|
579
|
+
// because the work is already integrated and the branch may or may not
|
|
580
|
+
// still exist locally.
|
|
581
|
+
//
|
|
582
|
+
// CROSS-SUBSTRATE RELEASE (prd `ledger-status-per-item-lock-refs` US #9/#10;
|
|
583
|
+
// task `complete-lock-then-durable-main-move-crash-safe`): the durable
|
|
584
|
+
// `main` record is already terminal (the kept tip is provably on
|
|
585
|
+
// `<arbiter>/main`), so the per-item lock claim took is now stale: release it
|
|
586
|
+
// SECOND (the move already landed FIRST). `durablyOnMain: true` unconditionally
|
|
587
|
+
// here (unlike the propose build path, this branch is reached ONLY when the
|
|
588
|
+
// work is already integrated on `main`). Best-effort + idempotent (`not-held`
|
|
589
|
+
// is fine when a prior reconcile/release cleared it).
|
|
590
|
+
await releaseClaimLockAfterDurableMove(slug, cwd, arbiter, env, true, note);
|
|
591
|
+
return {
|
|
592
|
+
exitCode: 0,
|
|
593
|
+
outcome: 'already-integrated',
|
|
594
|
+
branch: core.branch,
|
|
595
|
+
message: core.reason ?? '',
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
if (core.outcome === 'invariant-violation') {
|
|
599
|
+
// The one-slug-one-folder guard FAILED LOUD: the arbiter already holds the
|
|
600
|
+
// slug in >1 status folder (a corrupt ledger). Nothing was committed/moved;
|
|
601
|
+
// refuse rather than publish corruption (the human resolves the duplicate —
|
|
602
|
+
// `scan`/`gc` surfaces it — then re-runs). Exit 1, never routed.
|
|
603
|
+
return {
|
|
604
|
+
exitCode: 1,
|
|
605
|
+
outcome: 'invariant-violation',
|
|
606
|
+
routedToNeedsAttention: false,
|
|
607
|
+
branch: core.branch,
|
|
608
|
+
message: core.reason ?? '',
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
// SUCCESS: the core integrated. `result` is its integration result; `mode` is
|
|
612
|
+
// the mode the core resolved, read from the result (it always equals the
|
|
613
|
+
// requested mode now that there is no downgrade). The tail switches/ffs per the
|
|
614
|
+
// mode that integrated.
|
|
615
|
+
const result = core.integration;
|
|
616
|
+
const commitMessage = core.commitMessage;
|
|
617
|
+
const mode = result.mode;
|
|
618
|
+
// CROSS-SUBSTRATE RELEASE, gated on the work being DURABLY ON `main` (prd
|
|
619
|
+
// `ledger-status-per-item-lock-refs` US #9/#10; ADR
|
|
620
|
+
// `ledger-status-on-per-item-lock-refs`; the trail's Amendment 6; task
|
|
621
|
+
// `complete-lock-then-durable-main-move-crash-safe`). ORDER MATTERS: the
|
|
622
|
+
// per-item lock that `claim` acquired (`action: implement`, keyed
|
|
623
|
+
// `task:<slug>`) is released SECOND, AFTER the durable `main` move landed FIRST
|
|
624
|
+
// inside `performIntegration`.
|
|
625
|
+
//
|
|
626
|
+
// CRITICAL (task `propose-keep-lock-until-pr-merge`): in PROPOSE mode the
|
|
627
|
+
// done-move did NOT land on `main` here. The `git mv tasks/ready -> done` is
|
|
628
|
+
// committed on the WORK BRANCH and pushed as the PR; `<arbiter>/main` still
|
|
629
|
+
// shows the body in `tasks/ready/` until a human MERGES the PR. So releasing
|
|
630
|
+
// the lock now would leave the task BOTH unlocked AND in `tasks/ready/` on
|
|
631
|
+
// `main`, i.e. fully eligible again, for the whole review window: the next
|
|
632
|
+
// advance tick re-claims it (lock absent), rebuilds, the PR meanwhile merges,
|
|
633
|
+
// the diff is empty, and the item is mis-marked `stuck`. So we release ONLY
|
|
634
|
+
// when the work is provably on `main`: merge mode (`mergedToMain`) or an
|
|
635
|
+
// already-landed propose (`alreadyLanded`, the benign already-on-main race
|
|
636
|
+
// tail). A plain open propose PR KEEPS the lock held (the open PR IS the
|
|
637
|
+
// in-flight state); it is released when the work lands on `main`, reconciled by
|
|
638
|
+
// `reconcileItemLockAgainstMain` when the PR merges out-of-band.
|
|
639
|
+
//
|
|
640
|
+
// A crash between the move and the release leaves a `done`-on-`main` item with
|
|
641
|
+
// a still-held lock; `reconcileItemLockAgainstMain` recovers it (the `main`
|
|
642
|
+
// record is authoritative over the stale lock). The release is best-effort +
|
|
643
|
+
// idempotent (`not-held` when the body predates the lock or a reconcile already
|
|
644
|
+
// cleared it).
|
|
645
|
+
const durablyOnMain = result.mergedToMain === true || result.alreadyLanded === true;
|
|
646
|
+
await releaseClaimLockAfterDurableMove(slug, cwd, arbiter, env, durablyOnMain, note);
|
|
647
|
+
// Land back on `main` by default in BOTH modes (the move differs per mode),
|
|
648
|
+
// then delete the local work branch iff its work is provably on the arbiter.
|
|
649
|
+
// `--no-switch` keeps the human on the work branch AND keeps the branch.
|
|
650
|
+
let switchedTo = branch;
|
|
651
|
+
let deletedLocalBranch = false;
|
|
652
|
+
// Merge mode only: whether the local `main` ff'd (the courtesy after the
|
|
653
|
+
// authoritative push). `true` by default; flipped to `false` by `syncLocalMain`
|
|
654
|
+
// on a NON-FATAL diverged-`main` skip. Undefined in propose / `--no-switch`.
|
|
655
|
+
let localMainSynced;
|
|
656
|
+
if (!options.noSwitch) {
|
|
657
|
+
if (mode === 'merge') {
|
|
658
|
+
// merge: the work landed on <arbiter>/main, so switch to main AND ff it.
|
|
659
|
+
// The ff is a COURTESY — if local `main` diverged it cannot apply, which is
|
|
660
|
+
// NON-FATAL (the push already defined success); `syncLocalMain` reports it.
|
|
661
|
+
localMainSynced = await syncLocalMain(cwd, arbiter, env, note);
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
// propose: the work is on a pushed branch awaiting review, NOT on main,
|
|
665
|
+
// so JUST switch to main — do NOT ff (arbiter main has not moved).
|
|
666
|
+
await gitHard(['switch', '--quiet', 'main'], cwd, env);
|
|
667
|
+
}
|
|
668
|
+
switchedTo = 'main';
|
|
669
|
+
// Delete the LOCAL work branch when provably on the arbiter (same predicate
|
|
670
|
+
// as worktree deletion, ADR §4, mode-agnostic). NEVER delete the remote.
|
|
671
|
+
deletedLocalBranch = await deleteLocalBranchIfProvablyOnArbiter(cwd, arbiter, branch, env);
|
|
672
|
+
}
|
|
673
|
+
if (mode === 'merge') {
|
|
674
|
+
// When the local ff was SKIPPED (diverged main, non-fatal), say so honestly —
|
|
675
|
+
// the work IS on the arbiter; only the local courtesy ff was left undone.
|
|
676
|
+
const localState = localMainSynced === false
|
|
677
|
+
? 'local main left diverged (run `git rebase origin/main` to sync)'
|
|
678
|
+
: 'local main updated';
|
|
679
|
+
const landed = options.noSwitch
|
|
680
|
+
? `merged to ${arbiter}/main; left on ${branch} (--no-switch).`
|
|
681
|
+
: `merged to ${arbiter}/main; ${localState}` +
|
|
682
|
+
`${deletedLocalBranch ? ` and ${branch} deleted` : ''}.`;
|
|
683
|
+
const message = `Completed '${slug}': ${landed}`;
|
|
684
|
+
note(message);
|
|
685
|
+
return {
|
|
686
|
+
exitCode: 0,
|
|
687
|
+
outcome: 'completed',
|
|
688
|
+
branch,
|
|
689
|
+
commitMessage,
|
|
690
|
+
mergedToMain: result.mergedToMain,
|
|
691
|
+
localMainSynced,
|
|
692
|
+
switchedTo,
|
|
693
|
+
deletedLocalBranch,
|
|
694
|
+
message,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
// propose: the branch is pushed; report the next step. The next step is the
|
|
698
|
+
// ONE thing the human must act on, so it is emitted as a visually-distinct
|
|
699
|
+
// block (blank lines + heading + TTY-aware color) ON TOP of the plain summary
|
|
700
|
+
// note below — cosmetic only; the structured result is unchanged.
|
|
701
|
+
const color = options.color ?? shouldUseColor(process.stdout);
|
|
702
|
+
const noteBlock = options.noteBlock ?? note;
|
|
703
|
+
noteBlock(formatProposeNextStep({
|
|
704
|
+
branch,
|
|
705
|
+
arbiter,
|
|
706
|
+
requestOpened: result.requestOpened,
|
|
707
|
+
color,
|
|
708
|
+
}));
|
|
709
|
+
const next = result.requestOpened
|
|
710
|
+
? result.url
|
|
711
|
+
? `pushed ${branch} and opened a review (${result.url})`
|
|
712
|
+
: `pushed ${branch} and opened a review`
|
|
713
|
+
: `pushed ${branch} to ${arbiter}. ` + 'Open a PR/MR to land it on main';
|
|
714
|
+
const tail = options.noSwitch
|
|
715
|
+
? `; left on ${branch} (--no-switch).`
|
|
716
|
+
: deletedLocalBranch
|
|
717
|
+
? `; switched to main and deleted ${branch}.`
|
|
718
|
+
: '; switched to main.';
|
|
719
|
+
const message = `Completed '${slug}': ${next}${tail}`;
|
|
720
|
+
note(message);
|
|
721
|
+
return {
|
|
722
|
+
exitCode: 0,
|
|
723
|
+
outcome: 'completed',
|
|
724
|
+
branch,
|
|
725
|
+
commitMessage,
|
|
726
|
+
mergedToMain: false,
|
|
727
|
+
switchedTo,
|
|
728
|
+
deletedLocalBranch,
|
|
729
|
+
prUrl: result.url,
|
|
730
|
+
message,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Release the per-item lock `claim` ALSO acquired (`action: implement`, keyed
|
|
735
|
+
* `task:<slug>`) AFTER the durable `main` move has landed — the SECOND, lock-
|
|
736
|
+
* release half of complete's cross-substrate ordering (prd
|
|
737
|
+
* `ledger-status-per-item-lock-refs` US #9/#10; task
|
|
738
|
+
* `complete-lock-then-durable-main-move-crash-safe`).
|
|
739
|
+
*
|
|
740
|
+
* Called ONLY on the SUCCESS paths (`completed` / `already-integrated`). The lock
|
|
741
|
+
* is released ONLY when the work is DURABLY ON `<arbiter>/main` (`durablyOnMain`):
|
|
742
|
+
* merge mode (`mergedToMain`), an already-landed propose race tail
|
|
743
|
+
* (`alreadyLanded`), or the `already-integrated` recovery (provably on main). In
|
|
744
|
+
* those cases the lock is no longer holding anything in flight and is cleanly
|
|
745
|
+
* released SECOND.
|
|
746
|
+
*
|
|
747
|
+
* When `durablyOnMain` is FALSE (a plain open propose PR: the done-move is on the
|
|
748
|
+
* PR branch, NOT on `main`), the lock is KEPT HELD: the open PR IS the in-flight
|
|
749
|
+
* state, and the held lock is what keeps the still-`tasks/ready/`-on-`main` task
|
|
750
|
+
* out of the eligible pool until the PR merges (task
|
|
751
|
+
* `propose-keep-lock-until-pr-merge`). Releasing here would let the next advance
|
|
752
|
+
* tick re-claim the in-flight task and mis-mark it `stuck` on an empty diff. The
|
|
753
|
+
* lock is released when the work lands on `main`, reconciled by
|
|
754
|
+
* `reconcileItemLockAgainstMain` when the PR merges out-of-band.
|
|
755
|
+
*
|
|
756
|
+
* The FAILURE paths (gate-failed / review-blocked / rebase-conflict /
|
|
757
|
+
* prepare-failed) deliberately do NOT call this: those route to needs-attention
|
|
758
|
+
* (the lock is marked `stuck`, not released) and the item is still in flight.
|
|
759
|
+
*
|
|
760
|
+
* Best-effort + idempotent (a `not-held` is fine: the body may predate the lock,
|
|
761
|
+
* or a crash-recovery `reconcileItemLockAgainstMain` may already have cleared it).
|
|
762
|
+
* A release fault never fails an already-landed completion: the durable `main`
|
|
763
|
+
* record is authoritative, and a stranded lock is exactly what recovery clears.
|
|
764
|
+
*/
|
|
765
|
+
async function releaseClaimLockAfterDurableMove(slug, cwd, arbiter, env, durablyOnMain, note) {
|
|
766
|
+
if (!durablyOnMain) {
|
|
767
|
+
// PROPOSE, PR open: the done-move is on the PR branch, not on `main`. KEEP the
|
|
768
|
+
// lock held so the in-flight task is not re-claimed during the review window.
|
|
769
|
+
note(`'${slug}': keeping the per-item lock HELD (propose PR open; the work is ` +
|
|
770
|
+
'not yet on main). It is released when the PR merges (reconciled against ' +
|
|
771
|
+
'main).');
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
await releaseItemLock({ item: `task:${slug}`, cwd, arbiter, env });
|
|
776
|
+
}
|
|
777
|
+
catch {
|
|
778
|
+
// Best-effort: a release fault leaves a stale lock that recovery
|
|
779
|
+
// (`reconcileItemLockAgainstMain`) clears: the durable move already defined
|
|
780
|
+
// success. Never fail an already-landed completion on the lock release.
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Sync the local `main` to the just-pushed `<arbiter>/main`: switch to main and
|
|
785
|
+
* fast-forward it. We fetch the arbiter's main (we just pushed our rebased work
|
|
786
|
+
* there) then `merge --ff-only` so the user lands on an up-to-date local main
|
|
787
|
+
* without merge noise.
|
|
788
|
+
*
|
|
789
|
+
* The ff is a COURTESY, NOT the safety-bearing step — the authoritative push to
|
|
790
|
+
* `<arbiter>/main` already defined `complete`'s success. So the ff is NON-FATAL:
|
|
791
|
+
* if local `main` has DIVERGED (unpushed commits the arbiter lacks) the ff cannot
|
|
792
|
+
* apply; rather than throw (which would make `complete` exit non-zero even though
|
|
793
|
+
* the merge ALREADY LANDED on the arbiter), we print a clear "rebase to sync"
|
|
794
|
+
* message and return `false` (the caller records `localMainSynced: false` and
|
|
795
|
+
* keeps `outcome: completed` / exit 0). Returns `true` on a normal ff.
|
|
796
|
+
*
|
|
797
|
+
* Only the diverged / ff-cannot-apply case is softened — the `fetch` and `switch`
|
|
798
|
+
* stay `gitHard` (a genuinely different failure is NOT masked, per the task).
|
|
799
|
+
* Exported for direct testing of the softened-vs-fatal boundary in isolation.
|
|
800
|
+
*/
|
|
801
|
+
export async function syncLocalMain(cwd, arbiter, env, note) {
|
|
802
|
+
await gitHard(['fetch', '--quiet', arbiter], cwd, env);
|
|
803
|
+
await gitHard(['switch', '--quiet', 'main'], cwd, env);
|
|
804
|
+
// SOFT: a non-zero here is (almost always) "not possible to fast-forward" —
|
|
805
|
+
// local `main` diverged. That is the one case we make non-fatal.
|
|
806
|
+
const ff = await gitSoft(['merge', '--ff-only', '--quiet', `${arbiter}/main`], cwd, env);
|
|
807
|
+
if (ff.status === 0) {
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
note(`work landed on ${arbiter}/main; your local main couldn't fast-forward (it ` +
|
|
811
|
+
'has diverged) — run `git rebase origin/main` to sync.');
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Delete the LOCAL `work/<slug>` branch iff its work is PROVABLY on the arbiter
|
|
816
|
+
* — the SAME predicate as worktree deletion (ADR §4), mode-agnostic. Must be
|
|
817
|
+
* called AFTER we have switched off the branch (you cannot delete the branch you
|
|
818
|
+
* are on). Returns whether the local branch was deleted.
|
|
819
|
+
*
|
|
820
|
+
* Provably-on-arbiter ⇔ EITHER:
|
|
821
|
+
* - merged: the branch tip is an ancestor of `<arbiter>/main`, OR
|
|
822
|
+
* - pushed & up-to-date: `<arbiter>/<branch>` exists AND its tip == the local
|
|
823
|
+
* branch tip (so a later un-pushed amend is NEVER lost — we verify the
|
|
824
|
+
* remote tip equals the local tip, not merely that "a branch was pushed").
|
|
825
|
+
*
|
|
826
|
+
* Otherwise the branch is KEPT (unmerged / unpushed / diverged = not safe). The
|
|
827
|
+
* REMOTE branch is NEVER touched (a propose PR is built from it). A `git fetch`
|
|
828
|
+
* refreshes the remote-tracking refs so reachability is read against the LIVE
|
|
829
|
+
* arbiter; an unreachable arbiter simply reads as not-provable → keep (safe).
|
|
830
|
+
*/
|
|
831
|
+
async function deleteLocalBranchIfProvablyOnArbiter(cwd, arbiter, branch, env) {
|
|
832
|
+
if (!(await isLocalBranchProvablyOnArbiter(cwd, arbiter, branch, env))) {
|
|
833
|
+
return false; // not provably on the arbiter → KEEP the local branch
|
|
834
|
+
}
|
|
835
|
+
// Provably on the arbiter → delete ONLY the local branch (force-delete: the
|
|
836
|
+
// `-d` safety check uses the upstream/HEAD, which we have already proven via
|
|
837
|
+
// the predicate above; `-D` avoids a false "not fully merged" in propose mode).
|
|
838
|
+
await gitHard(['branch', '-D', branch], cwd, env);
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* The provably-safe predicate (ADR §4), mode-agnostic, applied to a LOCAL
|
|
843
|
+
* `branch` in `cwd`: true iff its tip is an ancestor of `<arbiter>/main` (merged)
|
|
844
|
+
* OR `<arbiter>/<branch>` exists with its tip == the local tip (pushed &
|
|
845
|
+
* up-to-date). False when the local branch is absent, or its work is unmerged /
|
|
846
|
+
* unpushed / a diverged un-pushed amend (remote tip != local tip). A `git fetch`
|
|
847
|
+
* refreshes the remote-tracking refs; an unreachable arbiter reads as not-
|
|
848
|
+
* provable → false (the safe direction). Exported for direct testing of the
|
|
849
|
+
* kept-vs-deleted decision in isolation.
|
|
850
|
+
*/
|
|
851
|
+
export async function isLocalBranchProvablyOnArbiter(cwd, arbiter, branch, env) {
|
|
852
|
+
const localTip = (await gitSoft(['rev-parse', '--verify', '--quiet', branch], cwd, env)).stdout.trim();
|
|
853
|
+
if (localTip === '') {
|
|
854
|
+
return false; // no such local branch
|
|
855
|
+
}
|
|
856
|
+
// Refresh remote-tracking refs against the live arbiter (best-effort).
|
|
857
|
+
await gitSoft(['fetch', '--quiet', arbiter], cwd, env);
|
|
858
|
+
// Merged: tip is an ancestor of <arbiter>/main.
|
|
859
|
+
const merged = (await gitSoft(['merge-base', '--is-ancestor', localTip, `${arbiter}/main`], cwd, env)).status === 0;
|
|
860
|
+
if (merged) {
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
863
|
+
// Pushed & up-to-date: <arbiter>/<branch> exists AND its tip == the local tip
|
|
864
|
+
// (we verify remote-tip == local-tip so a later un-pushed amend is never lost).
|
|
865
|
+
const remoteTip = (await gitSoft(['rev-parse', '--verify', '--quiet', `refs/remotes/${arbiter}/${branch}`], cwd, env)).stdout.trim();
|
|
866
|
+
return remoteTip !== '' && remoteTip === localTip;
|
|
867
|
+
}
|
|
868
|
+
/** The short symbolic name of HEAD, or '' for a detached HEAD. */
|
|
869
|
+
async function currentBranch(cwd, env) {
|
|
870
|
+
const sym = await gitSoft(['symbolic-ref', '--quiet', '--short', 'HEAD'], cwd, env);
|
|
871
|
+
return sym.status === 0 ? sym.stdout.trim() : '';
|
|
872
|
+
}
|
|
873
|
+
/** Run git, returning the raw result (no throw) — for soft checks. */
|
|
874
|
+
function gitSoft(args, cwd, env) {
|
|
875
|
+
return runAsync('git', args, cwd, { env });
|
|
876
|
+
}
|
|
877
|
+
/** Run git; throw on non-zero (genuinely unexpected plumbing failures). */
|
|
878
|
+
async function gitHard(args, cwd, env) {
|
|
879
|
+
const result = await runAsync('git', args, cwd, { env });
|
|
880
|
+
if (result.status !== 0) {
|
|
881
|
+
throw new Error(`git ${args.join(' ')} failed (exit ${result.status}): ${result.stderr.trim()}`);
|
|
882
|
+
}
|
|
883
|
+
return result;
|
|
884
|
+
}
|
|
885
|
+
//# sourceMappingURL=complete.js.map
|