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
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
import {randomUUID} from 'node:crypto';
|
|
2
|
+
import {runAsync, type RunResult} from './git.js';
|
|
3
|
+
import {
|
|
4
|
+
Integrator,
|
|
5
|
+
type IntegrateResult,
|
|
6
|
+
type ReviewProvider,
|
|
7
|
+
} from './integrator.js';
|
|
8
|
+
import type {IntegrationMode} from './config.js';
|
|
9
|
+
import {
|
|
10
|
+
routeToNeedsAttention,
|
|
11
|
+
returnToBacklog,
|
|
12
|
+
type RouteToNeedsAttentionOptions,
|
|
13
|
+
type RouteToNeedsAttentionResult,
|
|
14
|
+
type ReturnToBacklogOptions,
|
|
15
|
+
type ReturnToBacklogResult,
|
|
16
|
+
type SurfaceToNeedsAttentionOptions,
|
|
17
|
+
type SurfaceToNeedsAttentionResult,
|
|
18
|
+
type BranchPushOutcome,
|
|
19
|
+
} from './needs-attention.js';
|
|
20
|
+
import {
|
|
21
|
+
markStuckItemLock,
|
|
22
|
+
resumeItemLock,
|
|
23
|
+
type TransitionOutcome,
|
|
24
|
+
} from './item-lock.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The **write half** of the ledger-transition seam (ADR
|
|
28
|
+
* `docs/adr/claim-ledger-vs-protected-main.md`, status: accepted — the "Write
|
|
29
|
+
* seam"). ONE entry point — "apply this `work/` transition" — that every
|
|
30
|
+
* transition (claim / complete / needs-attention) routes through, so a FUTURE
|
|
31
|
+
* strategy could publish a transition elsewhere (e.g. a dedicated `main`-free
|
|
32
|
+
* ledger ref) without the transition call sites learning a new mechanism.
|
|
33
|
+
*
|
|
34
|
+
* It is a PURE REFACTOR: there is exactly ONE strategy ({@link
|
|
35
|
+
* currentLedgerWrite}) and it does EXACTLY what the code did before — it
|
|
36
|
+
* CAS-publishes the prepared transition commit to the arbiter's `main` with
|
|
37
|
+
* `--force-with-lease` and then verifies `<arbiter>/main` is now that commit. No
|
|
38
|
+
* mode, no config, no `ledgerMode`, no new ref.
|
|
39
|
+
*
|
|
40
|
+
* The seam stays at the SEMANTIC level: the caller hands the seam a *prepared*
|
|
41
|
+
* transition (a local branch carrying the commit, the base the ledger must still
|
|
42
|
+
* be at for the CAS, and the commit it expects to land) plus the transition
|
|
43
|
+
* KIND, and asks the seam to publish it. The public input is storage-agnostic —
|
|
44
|
+
* it does NOT name `main`; that `main` is the publish/verify target is an
|
|
45
|
+
* implementation detail of the sole strategy below.
|
|
46
|
+
*
|
|
47
|
+
* This task routes the CLAIM transition through the seam (see `claim-cas.ts`).
|
|
48
|
+
* The `complete` and `needs-attention` kinds are named here so the companion
|
|
49
|
+
* tasks route through the SAME entry point; they are not yet wired.
|
|
50
|
+
*
|
|
51
|
+
* The NEEDS-ATTENTION transition is wired here too: the abort paths in
|
|
52
|
+
* `complete.ts` (red gate, rebase conflict), the runner's stuck routing in
|
|
53
|
+
* `run.ts`, and the human `return` command all drive the
|
|
54
|
+
* `* → needs-attention` move (and its `needs-attention → backlog` re-queue)
|
|
55
|
+
* through this SAME seam rather than calling the move helpers directly. The
|
|
56
|
+
* sole strategy delegates to the folder-native mechanism in
|
|
57
|
+
* `needs-attention.ts` UNCHANGED (reason-in-the-body — WORK-CONTRACT rule 3,
|
|
58
|
+
* bounce from in-progress OR done, ONE atomic commit, optional branch push); the
|
|
59
|
+
* seam only relocates WHERE the "apply the needs-attention transition" call is
|
|
60
|
+
* expressed, so the later cherry-pick-to-`main` surfacing is built AGAINST the
|
|
61
|
+
* seam, not bolted onto the move code.
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The `work/` lifecycle transitions the write seam can apply.
|
|
66
|
+
*
|
|
67
|
+
* `tasking` is the legacy tasking-lock MARKER transition. It is now VESTIGIAL: the
|
|
68
|
+
* capstone cut-over (task
|
|
69
|
+
* `cutover-retire-slicing-advancing-markers-and-trim-folder-sets`) retired the
|
|
70
|
+
* `git mv work/prd/<slug>.md → work/tasking/<slug>.md` marker (the historical
|
|
71
|
+
* marker paths), so the tasking lock
|
|
72
|
+
* is the unified per-item lock ref (`tasking-lock.ts`) and no longer routes through
|
|
73
|
+
* {@link applyTransition} with this kind. The member is kept so the strategy
|
|
74
|
+
* interface and any historical reference stay valid; nothing publishes it.
|
|
75
|
+
*
|
|
76
|
+
* `advancing` is the kind {@link createItemThroughCas} (`advancing-lock.ts`) uses
|
|
77
|
+
* to publish a NEW `work/` item (the triage observation→promote path) through the
|
|
78
|
+
* SAME CAS the `claim` transition uses, keyed on the new item's identity (its
|
|
79
|
+
* path). The advancing-lock BORROW itself no longer rides this kind: the
|
|
80
|
+
* `work/advancing/<entry>.md` presence-marker is retired (the borrow is the unified
|
|
81
|
+
* `action: advance` lock ref now). So `advancing` survives only as the create-item
|
|
82
|
+
* CAS kind.
|
|
83
|
+
*/
|
|
84
|
+
export type LedgerTransitionKind =
|
|
85
|
+
| 'claim'
|
|
86
|
+
| 'complete'
|
|
87
|
+
| 'needs-attention'
|
|
88
|
+
| 'requeue'
|
|
89
|
+
| 'tasking'
|
|
90
|
+
| 'advancing'
|
|
91
|
+
/**
|
|
92
|
+
* The **promote** transition (prd `staging-pool-position-gate-and-trust-model`,
|
|
93
|
+
* task `pre-backlog-staging-folder-and-promote-step-a`): move a STAGED task
|
|
94
|
+
* `work/pre-backlog/<slug>.md → work/backlog/<slug>.md` to enter the
|
|
95
|
+
* agent-eligible pool. A durable `main` move, the same category as `requeue`
|
|
96
|
+
* (tree-less CAS via {@link applyTransition}). RUNNER/human-owned — no
|
|
97
|
+
* agent-facing path performs it (governing ADR
|
|
98
|
+
* `placement-is-runner-deterministic-humanonly-is-agent-judgement`).
|
|
99
|
+
*/
|
|
100
|
+
| 'promote';
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A *prepared* COMPLETE transition the caller asks the seam to publish: a
|
|
104
|
+
* finished work branch whose code should be integrated back to the arbiter. Like
|
|
105
|
+
* {@link ApplyTransitionInput} it is storage-agnostic — it names the work branch
|
|
106
|
+
* + the integration MODE + the review provider, NOT *where* the integration
|
|
107
|
+
* lands (the sole strategy decides that; today `merge` ff's to the arbiter's
|
|
108
|
+
* `main`, `propose` pushes the branch + requests review). The caller has already
|
|
109
|
+
* done the gate / done-move / commit / rebase-onto-arbiter — the seam only
|
|
110
|
+
* APPLIES the integration of that prepared branch.
|
|
111
|
+
*/
|
|
112
|
+
export interface ApplyCompleteTransitionInput {
|
|
113
|
+
/** Name of the arbiter git remote the integration is published to. */
|
|
114
|
+
arbiter: string;
|
|
115
|
+
/** The prepared (gated, committed, rebased) work branch to integrate. */
|
|
116
|
+
branch: string;
|
|
117
|
+
/** Integration mode: `merge` (ff to the ledger) or `propose` (push + review). */
|
|
118
|
+
mode: IntegrationMode;
|
|
119
|
+
/** The review-request provider (propose mode); push-only `none` otherwise. */
|
|
120
|
+
provider: ReviewProvider;
|
|
121
|
+
/**
|
|
122
|
+
* **The PR-INTENT axis** (config `noPR`, ADR §6): when `true` on the propose
|
|
123
|
+
* path, push the branch but SKIP the review request (the explicit suppress-PR
|
|
124
|
+
* intent). Threaded to {@link Integrator.integrate}. Ignored in `merge` mode.
|
|
125
|
+
*/
|
|
126
|
+
noPR?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Optional single-line review-request TITLE (propose mode), threaded straight
|
|
129
|
+
* to the provider. Absent ⇒ the provider's `--fill` default (no regression).
|
|
130
|
+
*/
|
|
131
|
+
title?: string;
|
|
132
|
+
/**
|
|
133
|
+
* Optional review-request BODY (propose mode) — advisory prose, gates nothing —
|
|
134
|
+
* threaded straight to the provider. Absent ⇒ the provider's `--fill` default.
|
|
135
|
+
*/
|
|
136
|
+
body?: string;
|
|
137
|
+
/**
|
|
138
|
+
* **Reap the remote head branch inline after a merge lands** (the merged-branch
|
|
139
|
+
* hygiene task's part (b)). When `true` on the `merge` path, delete the remote
|
|
140
|
+
* `work/<slug>` head AFTER the work landed on `main` (provably merged, so
|
|
141
|
+
* ancestor-safe; idempotent no-op when no remote head exists). Threaded straight
|
|
142
|
+
* to {@link Integrator.integrate}. Ignored in `propose` mode (the branch is the
|
|
143
|
+
* review surface, reaped later by `gc --remote-branches`).
|
|
144
|
+
*/
|
|
145
|
+
deleteMergedHead?: boolean;
|
|
146
|
+
/** Working clone/worktree the integration runs in. */
|
|
147
|
+
cwd: string;
|
|
148
|
+
/** Environment for child git processes. */
|
|
149
|
+
env?: NodeJS.ProcessEnv;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The outcome of asking the seam to apply (publish) a prepared COMPLETE
|
|
154
|
+
* transition. It is exactly the integration result the {@link Integrator}
|
|
155
|
+
* produces — the seam adds NO interpretation, it only relocates WHERE the
|
|
156
|
+
* "apply the complete transition" call is expressed.
|
|
157
|
+
*/
|
|
158
|
+
export type ApplyCompleteTransitionResult = IntegrateResult;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A *prepared* NEEDS-ATTENTION transition the caller asks the seam to apply: a
|
|
162
|
+
* stuck claimed item to bounce to `work/needs-attention/` with its reason. Like
|
|
163
|
+
* the other inputs it is storage-agnostic — it names the slug, the reason prose,
|
|
164
|
+
* any surfaced questions, and an OPTIONAL arbiter to also push the branch to,
|
|
165
|
+
* NOT *where* the move commits/publishes (the sole strategy decides that: a
|
|
166
|
+
* `git mv` from whichever of in-progress/ or done/ holds the item, the
|
|
167
|
+
* reason-in-the-body, the ONE atomic commit, the optional branch push). This
|
|
168
|
+
* mirrors {@link RouteToNeedsAttentionOptions} so the move mechanism is unchanged.
|
|
169
|
+
*/
|
|
170
|
+
export type ApplyNeedsAttentionTransitionInput = RouteToNeedsAttentionOptions;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The outcome of asking the seam to apply a NEEDS-ATTENTION transition — the
|
|
174
|
+
* move result the folder-native mechanism produces. The RECOVERABLE branch push
|
|
175
|
+
* outcome rides on `branchPush` (the caller reads it rather than assuming
|
|
176
|
+
* "pushed" off the local move). The OBSERVABLE half is now the per-item lock
|
|
177
|
+
* `state: stuck` amend (prd `ledger-status-per-item-lock-refs`); there is no
|
|
178
|
+
* separate on-`main` surface outcome to report.
|
|
179
|
+
*/
|
|
180
|
+
export type ApplyNeedsAttentionTransitionResult = RouteToNeedsAttentionResult;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A *prepared* RETURN-TO-BACKLOG transition: re-queue a STUCK item so it can be
|
|
184
|
+
* re-claimed — recovered from EITHER `needs-attention/` (the resolved-surface
|
|
185
|
+
* path) OR `in-progress/` (a claim that never surfaced), the slug's actual
|
|
186
|
+
* current folder resolved on the arbiter and moved to `backlog/`.
|
|
187
|
+
* Storage-agnostic, mirroring {@link ReturnToBacklogOptions}.
|
|
188
|
+
*/
|
|
189
|
+
export type ApplyReturnToBacklogTransitionInput = ReturnToBacklogOptions;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The outcome of asking the seam to apply a RETURN-TO-BACKLOG transition. It is
|
|
193
|
+
* a Promise: like `claim`, the tree-less strategy fetches + CAS-pushes to the
|
|
194
|
+
* arbiter (async).
|
|
195
|
+
*/
|
|
196
|
+
export type ApplyReturnToBacklogTransitionResult =
|
|
197
|
+
Promise<ReturnToBacklogResult>;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A *prepared* TREE-LESS NEEDS-ATTENTION (surface) transition: surface a stuck
|
|
201
|
+
* AFTER-COMMIT, LEDGER-ONLY item (`in-progress/ → needs-attention/`, with the
|
|
202
|
+
* reason in the body) WITHOUT a checkout — the SURFACE-direction sibling of the
|
|
203
|
+
* tree-less requeue, sharing its exact mechanism. The work is ALREADY committed
|
|
204
|
+
* on the kept `work/<slug>` branch (intact on the arbiter, recoverable), so the
|
|
205
|
+
* surface is purely the one-file `.md` move + reason — no `pushBranch`, no
|
|
206
|
+
* worktree. Storage-agnostic, mirroring {@link SurfaceToNeedsAttentionOptions}.
|
|
207
|
+
* NOT for the wip-save / gate-failed / agent-failed surfaces (which may carry
|
|
208
|
+
* uncommitted work — those keep {@link applyNeedsAttentionTransition}, the
|
|
209
|
+
* cwd-bound path that can commit wip first).
|
|
210
|
+
*/
|
|
211
|
+
export type ApplyTreelessNeedsAttentionTransitionInput =
|
|
212
|
+
SurfaceToNeedsAttentionOptions;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The outcome of asking the seam to apply a TREE-LESS NEEDS-ATTENTION (surface)
|
|
216
|
+
* transition. A Promise: like `requeue`/`claim`, the tree-less strategy fetches +
|
|
217
|
+
* CAS-pushes to the arbiter (async).
|
|
218
|
+
*/
|
|
219
|
+
export type ApplyTreelessNeedsAttentionTransitionResult =
|
|
220
|
+
Promise<SurfaceToNeedsAttentionResult>;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* A *prepared* RESOLVE-NEEDS-ATTENTION transition: a human is picking up a stuck
|
|
224
|
+
* item, so the seam must **clear the stuck state** and restore it to `active`.
|
|
225
|
+
* Storage-agnostic — it names the slug + the working clone (and an OPTIONAL
|
|
226
|
+
* arbiter to amend the lock on), NOT *where* the stuck state lives. The sole
|
|
227
|
+
* strategy clears it by amending the per-item lock `stuck → active` on the
|
|
228
|
+
* arbiter (task `cutover-needs-attention-becomes-lock-stuck-recovery-surface`).
|
|
229
|
+
*/
|
|
230
|
+
export interface ApplyResolveNeedsAttentionTransitionInput {
|
|
231
|
+
/** The working clone the `work/` tree lives in. */
|
|
232
|
+
cwd: string;
|
|
233
|
+
/** The slug of the stuck item to resolve back to active. */
|
|
234
|
+
slug: string;
|
|
235
|
+
/**
|
|
236
|
+
* The arbiter remote whose lock ref to amend (`stuck → active`). Omitted ⇒ a
|
|
237
|
+
* recorded no-op success (no lock ref to amend; the human-local face).
|
|
238
|
+
* Storage-agnostic: it names the remote, not `main`.
|
|
239
|
+
*/
|
|
240
|
+
arbiter?: string;
|
|
241
|
+
/** Environment for child git processes. */
|
|
242
|
+
env?: NodeJS.ProcessEnv;
|
|
243
|
+
/** Sink for human-readable progress notes. */
|
|
244
|
+
note?: (message: string) => void;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** The outcome of asking the seam to apply a RESOLVE-NEEDS-ATTENTION transition. */
|
|
248
|
+
export interface ApplyResolveNeedsAttentionTransitionResult {
|
|
249
|
+
/** True iff the lock was amended back to active (or the no-arbiter no-op). */
|
|
250
|
+
moved: boolean;
|
|
251
|
+
/** When NOT moved, why (no held stuck lock, contention, …). */
|
|
252
|
+
reasonNotMoved?: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* A *prepared* transition the caller asks the seam to publish. Storage-agnostic:
|
|
257
|
+
* it describes the transition semantically (a kind + a prepared local commit +
|
|
258
|
+
* the CAS lease), NOT *where* it should be published. The sole strategy decides
|
|
259
|
+
* that (today: the arbiter's `main`).
|
|
260
|
+
*/
|
|
261
|
+
export interface ApplyTransitionInput {
|
|
262
|
+
/** Which `work/` transition this is (claim / complete / needs-attention). */
|
|
263
|
+
kind: LedgerTransitionKind;
|
|
264
|
+
/** Name of the arbiter git remote the transition is published to. */
|
|
265
|
+
arbiter: string;
|
|
266
|
+
/** Local branch carrying the prepared transition commit (its tip = {@link head}). */
|
|
267
|
+
localBranch: string;
|
|
268
|
+
/**
|
|
269
|
+
* The ledger commit the publish must be a fast-forward FROM — the
|
|
270
|
+
* compare-and-swap lease. If the ledger has moved past this, the publish is
|
|
271
|
+
* rejected (someone else advanced it under us).
|
|
272
|
+
*/
|
|
273
|
+
expectedBase: string;
|
|
274
|
+
/** The commit sha the caller expects to become the ledger tip after publish. */
|
|
275
|
+
head: string;
|
|
276
|
+
/** Working clone/worktree the publish runs in. */
|
|
277
|
+
cwd: string;
|
|
278
|
+
/** Show the intended publish without mutating the arbiter (dry-run). */
|
|
279
|
+
dryRun?: boolean;
|
|
280
|
+
/** Environment for child git processes. */
|
|
281
|
+
env?: NodeJS.ProcessEnv;
|
|
282
|
+
/** Sink for human-readable progress notes. */
|
|
283
|
+
note?: (message: string) => void;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** The outcome of asking the seam to publish a prepared transition. */
|
|
287
|
+
export interface ApplyTransitionResult {
|
|
288
|
+
/**
|
|
289
|
+
* `published` — the transition landed (and was verified to be the ledger tip).
|
|
290
|
+
* `rejected` — the CAS lease failed (the ledger moved under us); the caller
|
|
291
|
+
* decides whether to refetch+retry or give up. The seam never throws for this
|
|
292
|
+
* expected contended case.
|
|
293
|
+
*/
|
|
294
|
+
kind: 'published' | 'rejected';
|
|
295
|
+
/** Human-readable summary of the terminal condition. */
|
|
296
|
+
message: string;
|
|
297
|
+
/**
|
|
298
|
+
* The sha that ACTUALLY landed on the ledger when `published`. The seam stamps
|
|
299
|
+
* each attempt's tip with a fresh `CAS-Nonce` trailer (so the pushed sha is
|
|
300
|
+
* unique), so the landed commit is NOT the caller's pre-nonce `head` — it is
|
|
301
|
+
* this nonce'd descendant-in-content. Callers that branch/track/report off the
|
|
302
|
+
* landed commit (e.g. `claim`'s work-branch hint) MUST use this, not their
|
|
303
|
+
* input `head`. Absent on `rejected` and on `dryRun`.
|
|
304
|
+
*/
|
|
305
|
+
publishedHead?: string;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The write-seam interface: ONE entry point — apply (publish) a prepared `work/`
|
|
310
|
+
* transition. A future strategy implements this same interface to publish the
|
|
311
|
+
* transition elsewhere — without any transition call site changing.
|
|
312
|
+
*/
|
|
313
|
+
export interface LedgerWriteStrategy {
|
|
314
|
+
applyTransition(input: ApplyTransitionInput): Promise<ApplyTransitionResult>;
|
|
315
|
+
/**
|
|
316
|
+
* Apply (publish) a prepared COMPLETE transition: integrate a finished work
|
|
317
|
+
* branch back to the arbiter per its mode. The sole strategy delegates to the
|
|
318
|
+
* integration mechanism unchanged; a future strategy could integrate elsewhere
|
|
319
|
+
* without `complete.ts` changing.
|
|
320
|
+
*/
|
|
321
|
+
applyCompleteTransition(
|
|
322
|
+
input: ApplyCompleteTransitionInput,
|
|
323
|
+
): Promise<ApplyCompleteTransitionResult>;
|
|
324
|
+
/**
|
|
325
|
+
* Apply a NEEDS-ATTENTION transition: bounce a stuck claimed item to
|
|
326
|
+
* `work/needs-attention/` with its reason recorded in the body. The sole
|
|
327
|
+
* strategy delegates to the folder-native move mechanism unchanged; a future
|
|
328
|
+
* strategy could surface the stuck item elsewhere (e.g. the cherry-pick-to-
|
|
329
|
+
* `main` follow-on) without `complete.ts`/`run.ts` changing.
|
|
330
|
+
*/
|
|
331
|
+
applyNeedsAttentionTransition(
|
|
332
|
+
input: ApplyNeedsAttentionTransitionInput,
|
|
333
|
+
): Promise<ApplyNeedsAttentionTransitionResult>;
|
|
334
|
+
/**
|
|
335
|
+
* Apply a TREE-LESS NEEDS-ATTENTION (surface) transition: surface a stuck
|
|
336
|
+
* AFTER-COMMIT, LEDGER-ONLY item (`in-progress/ → needs-attention/`) WITHOUT a
|
|
337
|
+
* checkout, reusing the SAME tree-less mechanism `requeue` uses for the reverse
|
|
338
|
+
* direction. The sole strategy delegates to the tree-less surface (now a pure lock amend)
|
|
339
|
+
* (fetch + scratch-index move + throwaway-ref + leased fast-forward push). NOT
|
|
340
|
+
* for the uncommitted-wip surfaces, which keep {@link
|
|
341
|
+
* applyNeedsAttentionTransition} (the cwd-bound path that can commit wip first).
|
|
342
|
+
*/
|
|
343
|
+
applyTreelessNeedsAttentionTransition(
|
|
344
|
+
input: ApplyTreelessNeedsAttentionTransitionInput,
|
|
345
|
+
): ApplyTreelessNeedsAttentionTransitionResult;
|
|
346
|
+
/**
|
|
347
|
+
* Apply a RETURN-TO-BACKLOG transition: re-queue a STUCK item (in
|
|
348
|
+
* `needs-attention/` OR `in-progress/` — resolved on the arbiter) for
|
|
349
|
+
* re-claiming, routed through the SAME seam. Like `claim`, it is TREE-LESS —
|
|
350
|
+
* the move is a compare-and-swap push to the arbiter ref (it never writes the
|
|
351
|
+
* cwd tree).
|
|
352
|
+
*/
|
|
353
|
+
applyReturnToBacklogTransition(
|
|
354
|
+
input: ApplyReturnToBacklogTransitionInput,
|
|
355
|
+
): ApplyReturnToBacklogTransitionResult;
|
|
356
|
+
/**
|
|
357
|
+
* Apply a RESOLVE-NEEDS-ATTENTION transition: a human is picking up a stuck
|
|
358
|
+
* item, so **clear the stuck surface** and restore it to `in-progress`. The
|
|
359
|
+
* seam carries only that INTENT — "clear the surface" — NOT *how*; the sole
|
|
360
|
+
* (mode-M) strategy clears it by reverse-moving needs-attention → in-progress
|
|
361
|
+
* on the arbiter's `main`, but a future strategy could clear it differently
|
|
362
|
+
* without `start.ts` changing.
|
|
363
|
+
*/
|
|
364
|
+
applyResolveNeedsAttentionTransition(
|
|
365
|
+
input: ApplyResolveNeedsAttentionTransitionInput,
|
|
366
|
+
): Promise<ApplyResolveNeedsAttentionTransitionResult>;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// --- The sole strategy: exactly today's behaviour -------------------------
|
|
370
|
+
|
|
371
|
+
/** Run git, returning the raw result (no throw) — for soft checks. */
|
|
372
|
+
function gitSoft(
|
|
373
|
+
args: string[],
|
|
374
|
+
cwd: string,
|
|
375
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
376
|
+
): Promise<RunResult> {
|
|
377
|
+
return runAsync('git', args, cwd, {env});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Run git; throw on non-zero (genuinely unexpected plumbing failures). */
|
|
381
|
+
async function gitHard(
|
|
382
|
+
args: string[],
|
|
383
|
+
cwd: string,
|
|
384
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
385
|
+
): Promise<RunResult> {
|
|
386
|
+
const result = await runAsync('git', args, cwd, {env});
|
|
387
|
+
if (result.status !== 0) {
|
|
388
|
+
throw new Error(
|
|
389
|
+
`git ${args.join(' ')} failed (exit ${result.status}): ${result.stderr.trim()}`,
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** The git trailer key the per-attempt CAS nonce rides in (greppable, round-trips). */
|
|
396
|
+
export const CAS_NONCE_TRAILER = 'CAS-Nonce';
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Rebuild the transition tip ({@link localBranch}'s commit, whose sha is {@link
|
|
400
|
+
* head}) into a NEW commit object that is byte-for-byte identical EXCEPT for a
|
|
401
|
+
* freshly-appended `CAS-Nonce: <uuid>` trailer, and return the new sha. This is
|
|
402
|
+
* the ONE chokepoint that makes EVERY {@link
|
|
403
|
+
* LedgerWriteStrategy.applyTransition} caller's CAS commit unique — create,
|
|
404
|
+
* claim, tasking-lock, advancing-lock, and the needs-attention/requeue surface
|
|
405
|
+
* all route their publish through `applyTransition`, so stamping HERE (the seam
|
|
406
|
+
* AMENDING the tip's message just before the push) covers all of them WITHOUT a
|
|
407
|
+
* shared commit-building helper and WITHOUT touching each commit site.
|
|
408
|
+
*
|
|
409
|
+
* It is built with `commit-tree` plumbing on the same tree + parent + ambient
|
|
410
|
+
* env identity as the tip, so it NEVER mutates the caller's working tree, index,
|
|
411
|
+
* or HEAD (safe from a job worktree mid-flight), and it preserves WHO/WHAT — it
|
|
412
|
+
* ONLY appends the trailer to the original message. The author/committer
|
|
413
|
+
* identity is pinned from the original commit so the stamp does not silently
|
|
414
|
+
* re-attribute the transition.
|
|
415
|
+
*
|
|
416
|
+
* A FRESH nonce is generated on EACH call — and `applyTransition` calls this once
|
|
417
|
+
* per ATTEMPT (each retry of a caller's outer refetch loop re-enters
|
|
418
|
+
* `applyTransition`), so two concurrent same-identity racers, and any single
|
|
419
|
+
* racer across its own retries, ALWAYS get distinct shas.
|
|
420
|
+
*/
|
|
421
|
+
async function stampNonce(params: {
|
|
422
|
+
localBranch: string;
|
|
423
|
+
head: string;
|
|
424
|
+
cwd: string;
|
|
425
|
+
env: NodeJS.ProcessEnv | undefined;
|
|
426
|
+
}): Promise<string> {
|
|
427
|
+
const {localBranch, head, cwd, env} = params;
|
|
428
|
+
const nonce = randomUUID();
|
|
429
|
+
|
|
430
|
+
// Read the tip's tree + parents + author/committer identity + original message
|
|
431
|
+
// in ONE `git log` (one git subprocess, not four) — the body (%B, multi-line)
|
|
432
|
+
// MUST be last. Fields are NUL-separated (%x00); the trailing field is the raw
|
|
433
|
+
// body so its embedded newlines do not confuse the split.
|
|
434
|
+
const FIELD_SEP = '\u0000';
|
|
435
|
+
const raw = (
|
|
436
|
+
await gitHard(
|
|
437
|
+
[
|
|
438
|
+
'log',
|
|
439
|
+
'-1',
|
|
440
|
+
`--format=%T%x00%P%x00%an%x00%ae%x00%aI%x00%cn%x00%ce%x00%B`,
|
|
441
|
+
head,
|
|
442
|
+
],
|
|
443
|
+
cwd,
|
|
444
|
+
env,
|
|
445
|
+
)
|
|
446
|
+
).stdout;
|
|
447
|
+
const fields = raw.split(FIELD_SEP);
|
|
448
|
+
const tree = (fields[0] ?? '').trim();
|
|
449
|
+
const parents = (fields[1] ?? '')
|
|
450
|
+
.trim()
|
|
451
|
+
.split(/\s+/)
|
|
452
|
+
.filter((p) => p.length > 0); // the tip's parents (usually exactly one)
|
|
453
|
+
const authorName = fields[2] ?? '';
|
|
454
|
+
const authorEmail = fields[3] ?? '';
|
|
455
|
+
const authorDate = fields[4] ?? '';
|
|
456
|
+
const committerName = fields[5] ?? '';
|
|
457
|
+
const committerEmail = fields[6] ?? '';
|
|
458
|
+
// The body is the last field; strip trailing whitespace before we append.
|
|
459
|
+
const message = (fields[7] ?? '').replace(/\s+$/, '');
|
|
460
|
+
|
|
461
|
+
// Pin the original author/committer so the stamp re-attributes nothing; we are
|
|
462
|
+
// only appending a trailer to the message.
|
|
463
|
+
const stampEnv: NodeJS.ProcessEnv = {
|
|
464
|
+
...(env ?? process.env),
|
|
465
|
+
GIT_AUTHOR_NAME: authorName,
|
|
466
|
+
GIT_AUTHOR_EMAIL: authorEmail,
|
|
467
|
+
GIT_AUTHOR_DATE: authorDate,
|
|
468
|
+
GIT_COMMITTER_NAME: committerName,
|
|
469
|
+
GIT_COMMITTER_EMAIL: committerEmail,
|
|
470
|
+
// Deliberately do NOT pin GIT_COMMITTER_DATE: even two stamps with identical
|
|
471
|
+
// everything-else would still differ by the nonce, but leaving the committer
|
|
472
|
+
// date current is the honest "when this attempt was published".
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// Append the trailer as its own block so it round-trips as a real git trailer.
|
|
476
|
+
const noncedMessage = `${message}\n\n${CAS_NONCE_TRAILER}: ${nonce}\n`;
|
|
477
|
+
const parentArgs = parents.flatMap((p) => ['-p', p]);
|
|
478
|
+
const result = await runAsync(
|
|
479
|
+
'git',
|
|
480
|
+
['commit-tree', tree, ...parentArgs, '-m', noncedMessage],
|
|
481
|
+
cwd,
|
|
482
|
+
{env: stampEnv},
|
|
483
|
+
);
|
|
484
|
+
if (result.status !== 0) {
|
|
485
|
+
throw new Error(
|
|
486
|
+
`git commit-tree (CAS nonce stamp) failed (exit ${result.status}): ${result.stderr.trim()} — localBranch=${localBranch}`,
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
return result.stdout.trim();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* The ONLY ledger-write strategy: current behaviour. It CAS-publishes the
|
|
494
|
+
* prepared transition commit to the arbiter's `main` — the `main` push and the
|
|
495
|
+
* `--force-with-lease=main:<base>` lease live HERE, not in the public input — and
|
|
496
|
+
* verifies the arbiter's `main` is now that commit (guarding against an
|
|
497
|
+
* "Everything up-to-date" push masquerading as a successful transition). A future
|
|
498
|
+
* strategy would be a different object implementing the same interface — chosen
|
|
499
|
+
* NOWHERE today (no mode/config selects it).
|
|
500
|
+
*/
|
|
501
|
+
export const currentLedgerWrite: LedgerWriteStrategy = {
|
|
502
|
+
async applyTransition({
|
|
503
|
+
arbiter,
|
|
504
|
+
localBranch,
|
|
505
|
+
expectedBase,
|
|
506
|
+
head,
|
|
507
|
+
cwd,
|
|
508
|
+
dryRun,
|
|
509
|
+
env,
|
|
510
|
+
note,
|
|
511
|
+
}): Promise<ApplyTransitionResult> {
|
|
512
|
+
const emit = note ?? (() => {});
|
|
513
|
+
|
|
514
|
+
if (dryRun) {
|
|
515
|
+
const message = `[dry-run] would: git push ${arbiter} ${localBranch}:main --force-with-lease=main:${expectedBase}`;
|
|
516
|
+
emit(message);
|
|
517
|
+
return {kind: 'published', message};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Stamp this ATTEMPT's transition commit with a FRESH random nonce (a real
|
|
521
|
+
// `CAS-Nonce: <uuid>` git trailer) so the pushed sha is UNIQUE per attempt.
|
|
522
|
+
// This is what makes the lease authoritative even for SAME-IDENTITY,
|
|
523
|
+
// SAME-CONTENT racers: without it, two racers who build an identical tree +
|
|
524
|
+
// message off the same base within git's 1-second timestamp resolution
|
|
525
|
+
// produce an IDENTICAL sha X. The first push ff's main to X; the second
|
|
526
|
+
// push of the SAME X degrades to "Everything up-to-date" (git exits 0, the
|
|
527
|
+
// lease has nothing to reject), and the post-push verify X === X passes — so
|
|
528
|
+
// BOTH would return `published`. The nonce gives each attempt a DISTINCT sha,
|
|
529
|
+
// so the loser's push finds main moved past <base> and is GENUINELY rejected
|
|
530
|
+
// by the lease, and the verify below correctly fails for it. The stamp is
|
|
531
|
+
// built with `commit-tree` plumbing (no checkout/HEAD mutation), reusing the
|
|
532
|
+
// tip's tree + parent + author/committer identity — it ONLY appends the
|
|
533
|
+
// trailer, leaving WHO/WHAT/the original message intact. Called per ATTEMPT
|
|
534
|
+
// (each retry of the caller's outer refetch loop re-enters here), so every
|
|
535
|
+
// attempt gets a fresh nonce, not one per process.
|
|
536
|
+
const nonced = await stampNonce({localBranch, head, cwd, env});
|
|
537
|
+
|
|
538
|
+
// The atomic compare-and-swap. --force-with-lease=main:<base> asserts the
|
|
539
|
+
// arbiter's main is STILL <base> (unchanged since our fetch); the push then
|
|
540
|
+
// fast-forwards main to our (nonce'd, thus unique) commit. If main moved, the
|
|
541
|
+
// lease fails → rejected.
|
|
542
|
+
const push = await gitSoft(
|
|
543
|
+
[
|
|
544
|
+
'push',
|
|
545
|
+
arbiter,
|
|
546
|
+
`${nonced}:main`,
|
|
547
|
+
`--force-with-lease=main:${expectedBase}`,
|
|
548
|
+
],
|
|
549
|
+
cwd,
|
|
550
|
+
env,
|
|
551
|
+
);
|
|
552
|
+
if (push.status === 0) {
|
|
553
|
+
// Verify the arbiter main now points at OUR (nonce'd) commit. INVARIANT:
|
|
554
|
+
// because the nonce makes our sha unique, a successful push can ONLY mean
|
|
555
|
+
// either (a) we genuinely ff'd main to our nonce'd commit (published), or
|
|
556
|
+
// (b) someone else's commit (a DIFFERENT nonce ⇒ a DIFFERENT sha) is already
|
|
557
|
+
// there and ours did NOT land — i.e. an "up-to-date / no change of our
|
|
558
|
+
// making" no-op, which is a LOSS. The nonce makes the two naturally
|
|
559
|
+
// distinguishable: `arbiterHead === nonced` iff WE won. So an up-to-date
|
|
560
|
+
// no-op can never satisfy this and is classified REJECTED, never published.
|
|
561
|
+
await gitHard(['fetch', '--quiet', arbiter], cwd, env);
|
|
562
|
+
const arbiterHead = (
|
|
563
|
+
await gitHard(['rev-parse', `${arbiter}/main`], cwd, env)
|
|
564
|
+
).stdout.trim();
|
|
565
|
+
if (arbiterHead === nonced) {
|
|
566
|
+
return {
|
|
567
|
+
kind: 'published',
|
|
568
|
+
message: 'transition published',
|
|
569
|
+
publishedHead: nonced,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
emit(
|
|
573
|
+
`push reported up-to-date / no change of our making — ${arbiter}/main is not our commit — treating as rejected.`,
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
return {kind: 'rejected', message: 'push rejected / lease failed'};
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* The complete transition under the SAME strategy: integrate the prepared work
|
|
581
|
+
* branch back to the arbiter exactly as `complete.ts` did before — it builds
|
|
582
|
+
* the {@link Integrator} with the chosen provider and calls `integrate` (the
|
|
583
|
+
* branch was already rebased onto the latest arbiter ledger by the caller, so
|
|
584
|
+
* this is the non-rebasing `integrate`, never `--force`). `merge` ff's the
|
|
585
|
+
* branch to the arbiter's `main`; `propose` pushes the branch + asks the
|
|
586
|
+
* provider to request review. That `merge` targets `main` is an implementation
|
|
587
|
+
* detail of THIS strategy — the public input never names it.
|
|
588
|
+
*/
|
|
589
|
+
async applyCompleteTransition({
|
|
590
|
+
arbiter,
|
|
591
|
+
branch,
|
|
592
|
+
mode,
|
|
593
|
+
provider,
|
|
594
|
+
noPR,
|
|
595
|
+
title,
|
|
596
|
+
body,
|
|
597
|
+
deleteMergedHead,
|
|
598
|
+
cwd,
|
|
599
|
+
env,
|
|
600
|
+
}): Promise<ApplyCompleteTransitionResult> {
|
|
601
|
+
const integrator = new Integrator({provider});
|
|
602
|
+
return integrator.integrate({
|
|
603
|
+
cwd,
|
|
604
|
+
arbiter,
|
|
605
|
+
branch,
|
|
606
|
+
mode,
|
|
607
|
+
noPR,
|
|
608
|
+
title,
|
|
609
|
+
body,
|
|
610
|
+
deleteMergedHead,
|
|
611
|
+
env,
|
|
612
|
+
});
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* The needs-attention transition under the SAME strategy. The seam's contract
|
|
617
|
+
* is transition-kind-agnostic: durably record a stuck job =
|
|
618
|
+
*
|
|
619
|
+
* - **OBSERVABLE** — publish the stuck state to the ledger surface so
|
|
620
|
+
* `scan`/`status`/a fresh checkout/another machine can see it. (THIS mode-M
|
|
621
|
+
* strategy does that by CHERRY-PICKING the move-only commit — the reason +
|
|
622
|
+
* the `git mv` — onto the arbiter's `main`, all-or-nothing, never `--force`d,
|
|
623
|
+
* so the half-finished wip below it never lands there. A future mode-P
|
|
624
|
+
* strategy could make it observable WITHOUT writing `main`, e.g. by reading
|
|
625
|
+
* work-branch tips.)
|
|
626
|
+
* - **RECOVERABLE** — push the work branch (when there IS one), so the saved
|
|
627
|
+
* work travels cross-machine and a requeue continues from its tip. (Mode M
|
|
628
|
+
* does that by `git push`ing the branch; the WHICH branch is the caller's,
|
|
629
|
+
* not assumed `work/<slug>` — a build bounce pushes `work/<slug>`, a tasking
|
|
630
|
+
* bounce its `work/prd-<slug>`, a temp-branch caller pushes NOTHING.)
|
|
631
|
+
*
|
|
632
|
+
* Both halves are ONE operation done in ONE place: it delegates to {@link
|
|
633
|
+
* routeToNeedsAttention}, which appends the reason as body prose (never a
|
|
634
|
+
* frontmatter field — WORK-CONTRACT rule 3), saves the aborted work as a
|
|
635
|
+
* **wip** commit, `git mv`s the item to `work/needs-attention/` as the
|
|
636
|
+
* **move-only** commit (the tip), and — when an `arbiter` is given — pushes the
|
|
637
|
+
* work branch (best-effort, branch-parameterised, emptiness-guarded; SURFACE-
|
|
638
|
+
* ONLY when `pushBranch: false`). The seam does NOT strip the arbiter: the same
|
|
639
|
+
* arbiter both publishes the surface (here) AND drives the helper's branch push,
|
|
640
|
+
* so "record stuck" and "save the work" can never drift apart. The human-vs-
|
|
641
|
+
* autonomous gate rides on whether an `arbiter` is given at all (human
|
|
642
|
+
* `complete` passes none → no surface, no push, local-only; autonomous `do`/`run`
|
|
643
|
+
* pass it → both).
|
|
644
|
+
*/
|
|
645
|
+
async applyNeedsAttentionTransition(
|
|
646
|
+
input: ApplyNeedsAttentionTransitionInput,
|
|
647
|
+
): Promise<ApplyNeedsAttentionTransitionResult> {
|
|
648
|
+
// BOUNCE = SAVE WIP + PUSH BRANCH (recoverable) + MARK LOCK STUCK (observable)
|
|
649
|
+
// — task `cutover-needs-attention-becomes-lock-stuck-recovery-surface`
|
|
650
|
+
// (decision i+). The OBSERVABLE half is now the per-item lock `state: stuck`
|
|
651
|
+
// (full reason prose + any agent-surfaced questions on the entry), NOT a
|
|
652
|
+
// `git mv` to `needs-attention/` and NOT an on-`main` surface — so a
|
|
653
|
+
// protected-`main` bounce succeeds and a branch cut from `main` inherits no
|
|
654
|
+
// stuck record. The RECOVERABLE half is UNCHANGED: {@link routeToNeedsAttention}
|
|
655
|
+
// commits the agent's uncommitted wip to the `work/<slug>` branch tip and
|
|
656
|
+
// pushes the branch (best-effort, branch-parameterised, emptiness-guarded,
|
|
657
|
+
// outage-retried), so the partial work travels cross-machine and a `requeue`
|
|
658
|
+
// continues from the tip. The work-branch push is NOT a `main` write.
|
|
659
|
+
const saved = await routeToNeedsAttention(input);
|
|
660
|
+
const stuck = await bounceToStuckLock({
|
|
661
|
+
cwd: input.cwd,
|
|
662
|
+
slug: input.slug,
|
|
663
|
+
reason: input.reason,
|
|
664
|
+
questions: input.questions,
|
|
665
|
+
arbiter: input.arbiter,
|
|
666
|
+
env: input.env,
|
|
667
|
+
note: input.note,
|
|
668
|
+
});
|
|
669
|
+
return {
|
|
670
|
+
// `moved` reflects the OBSERVABLE half (the stuck record): the lock amend.
|
|
671
|
+
moved: stuck.moved,
|
|
672
|
+
reasonNotMoved: stuck.reasonNotMoved,
|
|
673
|
+
// The branch push outcome is the honest RECOVERABLE report (`do`/`run` read
|
|
674
|
+
// it). The on-`main` surface half is GONE (the lock is the surface now).
|
|
675
|
+
branchPush: saved.branchPush,
|
|
676
|
+
pushError: saved.pushError,
|
|
677
|
+
};
|
|
678
|
+
},
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* The return-to-backlog transition under the SAME strategy: re-queue the
|
|
682
|
+
* stuck item by delegating to {@link returnToBacklog}, which moves the slug's
|
|
683
|
+
* current `work/<needs-attention|in-progress>/<slug>.md → work/backlog/<slug>.md`
|
|
684
|
+
* TREE-LESSLY — it
|
|
685
|
+
* builds the move off `<arbiter>/main` with plumbing and CAS-publishes it back
|
|
686
|
+
* THROUGH this same write seam (`applyTransition`, the very push+lease+verify
|
|
687
|
+
* `claim` uses), never staging/committing in the cwd working tree.
|
|
688
|
+
*/
|
|
689
|
+
applyReturnToBacklogTransition(
|
|
690
|
+
input: ApplyReturnToBacklogTransitionInput,
|
|
691
|
+
): ApplyReturnToBacklogTransitionResult {
|
|
692
|
+
return returnToBacklog(input);
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* The tree-less surface transition under the SAME strategy: surface the stuck
|
|
697
|
+
* AFTER-COMMIT, LEDGER-ONLY item by delegating to the tree-less surface (now a pure lock amend),
|
|
698
|
+
* which moves `work/in-progress/<slug>.md → work/needs-attention/<slug>.md`
|
|
699
|
+
* (reason in the body) TREE-LESSLY — it builds the move off `<arbiter>/main` with
|
|
700
|
+
* plumbing and CAS-publishes it THROUGH this same write seam
|
|
701
|
+
* (`applyTransition`, the very push+lease+verify `claim`/`requeue` use), never
|
|
702
|
+
* staging/committing in the cwd working tree. The reverse of
|
|
703
|
+
* {@link applyReturnToBacklogTransition}, the same one mechanism.
|
|
704
|
+
*/
|
|
705
|
+
async applyTreelessNeedsAttentionTransition(
|
|
706
|
+
input: ApplyTreelessNeedsAttentionTransitionInput,
|
|
707
|
+
): ApplyTreelessNeedsAttentionTransitionResult {
|
|
708
|
+
// PURE LOCK AMEND (task
|
|
709
|
+
// `cutover-needs-attention-becomes-lock-stuck-recovery-surface`, decision i+):
|
|
710
|
+
// the after-commit / ledger-only surface (continue-push-failure /
|
|
711
|
+
// continue-rebase-conflict) is now the SAME `active → stuck` lock amend as the
|
|
712
|
+
// cwd-bound bounce — NO `git mv`, NO `main` write. The recoverable work is
|
|
713
|
+
// already committed on the kept `work/<slug>` branch (intact on the arbiter);
|
|
714
|
+
// the stuck reason + questions ride on the lock entry. A re-surface of an
|
|
715
|
+
// already-stuck item is a tolerated idempotent no-op (`wrong-state`).
|
|
716
|
+
const moved = await bounceToStuckLock({
|
|
717
|
+
cwd: input.cwd,
|
|
718
|
+
slug: input.slug,
|
|
719
|
+
reason: input.reason,
|
|
720
|
+
questions: input.questions,
|
|
721
|
+
arbiter: input.arbiter,
|
|
722
|
+
env: input.env,
|
|
723
|
+
note: input.note,
|
|
724
|
+
});
|
|
725
|
+
return {moved: moved.moved, reasonNotMoved: moved.reasonNotMoved};
|
|
726
|
+
},
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* The resolve-needs-attention transition under the SAME strategy — satisfying
|
|
730
|
+
* the INTENT "clear the stuck surface + restore in-progress." It delegates to
|
|
731
|
+
* the resume lock amend (reverse `git mv` needs-attention →
|
|
732
|
+
* in-progress, committed) and, when an `arbiter` is given, publishes that
|
|
733
|
+
* reverse move-only commit to the arbiter's `main` — CLEARING the stuck surface
|
|
734
|
+
* there (the item is back in in-progress on the ledger). Same all-or-nothing,
|
|
735
|
+
* never-`--force` publish. "Reverse-move on `main`" is a detail of THIS
|
|
736
|
+
* strategy; the seam's contract is only the intent "clear the surface."
|
|
737
|
+
*/
|
|
738
|
+
async applyResolveNeedsAttentionTransition(
|
|
739
|
+
input: ApplyResolveNeedsAttentionTransitionInput,
|
|
740
|
+
): Promise<ApplyResolveNeedsAttentionTransitionResult> {
|
|
741
|
+
// PURE LOCK AMEND (task
|
|
742
|
+
// `cutover-needs-attention-becomes-lock-stuck-recovery-surface`, decision i+):
|
|
743
|
+
// resolving a stuck item is `stuck → active` on the per-item lock (a human is
|
|
744
|
+
// picking it up), NOT a `needs-attention/ → in-progress/` folder move. NO `main`
|
|
745
|
+
// write — the body already rests in `backlog/` (task 9a) and the work stays on
|
|
746
|
+
// the kept `work/<slug>` branch. Without an arbiter there is no lock ref to
|
|
747
|
+
// amend (the human-local face), so it is a recorded no-op success.
|
|
748
|
+
if (!input.arbiter) {
|
|
749
|
+
return {moved: true};
|
|
750
|
+
}
|
|
751
|
+
const r = await resumeItemLock({
|
|
752
|
+
item: `task:${input.slug}`,
|
|
753
|
+
cwd: input.cwd,
|
|
754
|
+
arbiter: input.arbiter,
|
|
755
|
+
env: input.env,
|
|
756
|
+
});
|
|
757
|
+
if (r.outcome === 'transitioned') {
|
|
758
|
+
return {moved: true};
|
|
759
|
+
}
|
|
760
|
+
// `wrong-state` (already active — not actually stuck) is tolerated as a no-op
|
|
761
|
+
// success: the item is already in-flight, the caller onboards onto it anyway.
|
|
762
|
+
if (r.outcome === 'wrong-state') {
|
|
763
|
+
return {moved: true};
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
moved: false,
|
|
767
|
+
reasonNotMoved: `could not resume '${input.slug}' (${r.outcome}: ${r.message}).`,
|
|
768
|
+
};
|
|
769
|
+
},
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* The SOLE stuck-state RECORD: amend the item's HELD per-item lock
|
|
774
|
+
* `active → stuck` + the FULL reason prose + any agent-surfaced questions, via the
|
|
775
|
+
* state machine's mark-stuck CAS amend ({@link markStuckItemLock}) — task
|
|
776
|
+
* `cutover-needs-attention-becomes-lock-stuck-recovery-surface` (decision i+; prd
|
|
777
|
+
* `ledger-status-per-item-lock-refs` US #5/#8; ADR
|
|
778
|
+
* `ledger-status-on-per-item-lock-refs`). This REPLACES the `git mv →
|
|
779
|
+
* needs-attention/` folder bounce + its on-`main` surface + branch push: the
|
|
780
|
+
* bounce now touches ONLY the lock ref (NO `main` write — so a protected-`main`
|
|
781
|
+
* bounce succeeds, and a work branch cut from `main` inherits no stuck record).
|
|
782
|
+
*
|
|
783
|
+
* The bounce holds the TASK's `implement` lock that `claim` acquired (task
|
|
784
|
+
* `claim-acquires-unified-lock-no-body-move`), so the normal stuck path is a plain
|
|
785
|
+
* `active → stuck` amend (`transitioned`). The OUTCOME MAPPING onto `{moved}`:
|
|
786
|
+
* - `transitioned` — the lock is now stuck (the stuck state is recorded) ⇒
|
|
787
|
+
* `moved: true`.
|
|
788
|
+
* - `wrong-state` — the lock is ALREADY `stuck` (an idempotent re-surface of a
|
|
789
|
+
* still-stuck item) ⇒ `moved: true` (the stuck record stands).
|
|
790
|
+
* - `not-held` — there is NO held lock to amend (an item that predates the
|
|
791
|
+
* lock, or a flow where claim did not acquire) ⇒ `moved: false` honestly: with
|
|
792
|
+
* the folder retired there is no other substrate to record the stuck state on,
|
|
793
|
+
* so the caller reports the bounce did NOT land (retry/resolve) rather than
|
|
794
|
+
* fake a success.
|
|
795
|
+
* - `lost`/`error` — a concurrent CAS race / environment fault ⇒ `moved: false`.
|
|
796
|
+
* - no arbiter — a human local-only `complete` (no arbiter handle): the lock
|
|
797
|
+
* ref lives on the arbiter, so there is no lock to amend; treat as a recorded
|
|
798
|
+
* no-op (`moved: true`) — a human is right there (the same human-vs-autonomous
|
|
799
|
+
* posture the old local-only folder move took). NOTE this records nothing
|
|
800
|
+
* durable, by design: the human is in the loop.
|
|
801
|
+
*
|
|
802
|
+
* Keyed on `task:<slug>` (the bounce surfaces a TASK; the prd/observation locks
|
|
803
|
+
* are tasking/advance holds whose own bounce paths are separate).
|
|
804
|
+
*/
|
|
805
|
+
async function bounceToStuckLock(params: {
|
|
806
|
+
cwd: string;
|
|
807
|
+
slug: string;
|
|
808
|
+
reason: string;
|
|
809
|
+
questions?: string[];
|
|
810
|
+
arbiter?: string;
|
|
811
|
+
env?: NodeJS.ProcessEnv;
|
|
812
|
+
note?: (message: string) => void;
|
|
813
|
+
}): Promise<{moved: boolean; reasonNotMoved?: string}> {
|
|
814
|
+
const {cwd, slug, reason, questions, arbiter, env} = params;
|
|
815
|
+
const note = params.note ?? (() => {});
|
|
816
|
+
if (!arbiter) {
|
|
817
|
+
// No arbiter handle ⇒ no lock ref to amend. A human local-only `complete`:
|
|
818
|
+
// the human is right there, so this is a recorded no-op (parity with the old
|
|
819
|
+
// local-only folder move that wrote nothing cross-machine).
|
|
820
|
+
note(
|
|
821
|
+
`'${slug}' bounced locally (no arbiter) — the stuck reason is not recorded ` +
|
|
822
|
+
'on a lock ref (a human is right here).',
|
|
823
|
+
);
|
|
824
|
+
return {moved: true};
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
const r = await markStuckItemLock({
|
|
828
|
+
item: `task:${slug}`,
|
|
829
|
+
reason,
|
|
830
|
+
questions,
|
|
831
|
+
cwd,
|
|
832
|
+
arbiter,
|
|
833
|
+
env,
|
|
834
|
+
});
|
|
835
|
+
const movedOutcomes: TransitionOutcome[] = ['transitioned', 'wrong-state'];
|
|
836
|
+
if (movedOutcomes.includes(r.outcome)) {
|
|
837
|
+
if (r.outcome === 'transitioned') {
|
|
838
|
+
note(`Marked the per-item lock for '${slug}' stuck: ${reason}`);
|
|
839
|
+
} else {
|
|
840
|
+
note(`The per-item lock for '${slug}' is already stuck (re-surface).`);
|
|
841
|
+
}
|
|
842
|
+
return {moved: true};
|
|
843
|
+
}
|
|
844
|
+
const reasonNotMoved =
|
|
845
|
+
r.outcome === 'not-held'
|
|
846
|
+
? `'${slug}' has no held lock to mark stuck (the bounce could not record ` +
|
|
847
|
+
'the stuck state — the item is not lock-held).'
|
|
848
|
+
: `could not mark the per-item lock for '${slug}' stuck (${r.outcome}: ` +
|
|
849
|
+
`${r.message}).`;
|
|
850
|
+
note(reasonNotMoved);
|
|
851
|
+
return {moved: false, reasonNotMoved};
|
|
852
|
+
} catch (err) {
|
|
853
|
+
const reasonNotMoved =
|
|
854
|
+
`could not mark the per-item lock for '${slug}' stuck ` +
|
|
855
|
+
`(${err instanceof Error ? err.message : String(err)}).`;
|
|
856
|
+
note(reasonNotMoved);
|
|
857
|
+
return {moved: false, reasonNotMoved};
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* The active ledger-write strategy. There is exactly one (current behaviour);
|
|
863
|
+
* this indirection is the seam's single insertion point — NOT a selectable mode.
|
|
864
|
+
*/
|
|
865
|
+
export const ledgerWrite: LedgerWriteStrategy = currentLedgerWrite;
|