dorfl 0.14.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advance-ci-template.d.ts +17 -16
- package/dist/advance-ci-template.d.ts.map +1 -1
- package/dist/advance-ci-template.js +42 -53
- package/dist/advance-ci-template.js.map +1 -1
- package/dist/advance-drivers.d.ts +8 -0
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +28 -4
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-isolated.d.ts.map +1 -1
- package/dist/advance-isolated.js +3 -2
- package/dist/advance-isolated.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts +41 -40
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +309 -489
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-loop-driver.d.ts.map +1 -1
- package/dist/advance-loop-driver.js +3 -2
- package/dist/advance-loop-driver.js.map +1 -1
- package/dist/advance-treeless-publish.d.ts +31 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -1
- package/dist/advance-treeless-publish.js +40 -0
- package/dist/advance-treeless-publish.js.map +1 -1
- package/dist/advance.d.ts +63 -3
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +80 -12
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +9 -0
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +21 -1
- package/dist/advancing-lock.js.map +1 -1
- package/dist/agent-env.d.ts +61 -0
- package/dist/agent-env.d.ts.map +1 -0
- package/dist/agent-env.js +144 -0
- package/dist/agent-env.js.map +1 -0
- package/dist/agent-stop.d.ts +35 -0
- package/dist/agent-stop.d.ts.map +1 -1
- package/dist/agent-stop.js +62 -0
- package/dist/agent-stop.js.map +1 -1
- package/dist/apply-merge-action.d.ts +76 -34
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +339 -129
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-stuck-action.js +2 -2
- package/dist/apply-stuck-action.js.map +1 -1
- package/dist/ci-agent-result.d.ts +151 -0
- package/dist/ci-agent-result.d.ts.map +1 -0
- package/dist/ci-agent-result.js +314 -0
- package/dist/ci-agent-result.js.map +1 -0
- package/dist/ci-handoff-apply.d.ts +162 -0
- package/dist/ci-handoff-apply.d.ts.map +1 -0
- package/dist/ci-handoff-apply.js +497 -0
- package/dist/ci-handoff-apply.js.map +1 -0
- package/dist/ci-handoff-format.d.ts +369 -0
- package/dist/ci-handoff-format.d.ts.map +1 -0
- package/dist/ci-handoff-format.js +646 -0
- package/dist/ci-handoff-format.js.map +1 -0
- package/dist/ci-handoff-lfs.d.ts +146 -0
- package/dist/ci-handoff-lfs.d.ts.map +1 -0
- package/dist/ci-handoff-lfs.js +397 -0
- package/dist/ci-handoff-lfs.js.map +1 -0
- package/dist/ci-handoff.d.ts +99 -0
- package/dist/ci-handoff.d.ts.map +1 -0
- package/dist/ci-handoff.js +277 -0
- package/dist/ci-handoff.js.map +1 -0
- package/dist/ci-lock-outputs.d.ts +93 -0
- package/dist/ci-lock-outputs.d.ts.map +1 -0
- package/dist/ci-lock-outputs.js +223 -0
- package/dist/ci-lock-outputs.js.map +1 -0
- package/dist/ci-phase-build.d.ts +178 -0
- package/dist/ci-phase-build.d.ts.map +1 -0
- package/dist/ci-phase-build.js +989 -0
- package/dist/ci-phase-build.js.map +1 -0
- package/dist/ci-phase-driver.d.ts +247 -0
- package/dist/ci-phase-driver.d.ts.map +1 -0
- package/dist/ci-phase-driver.js +370 -0
- package/dist/ci-phase-driver.js.map +1 -0
- package/dist/ci-phase-intake.d.ts +172 -0
- package/dist/ci-phase-intake.d.ts.map +1 -0
- package/dist/ci-phase-intake.js +555 -0
- package/dist/ci-phase-intake.js.map +1 -0
- package/dist/ci-phase-tasking.d.ts +205 -0
- package/dist/ci-phase-tasking.d.ts.map +1 -0
- package/dist/ci-phase-tasking.js +906 -0
- package/dist/ci-phase-tasking.js.map +1 -0
- package/dist/ci-phase-treeless.d.ts +224 -0
- package/dist/ci-phase-treeless.d.ts.map +1 -0
- package/dist/ci-phase-treeless.js +1482 -0
- package/dist/ci-phase-treeless.js.map +1 -0
- package/dist/ci-read-token.d.ts +47 -0
- package/dist/ci-read-token.d.ts.map +1 -0
- package/dist/ci-read-token.js +80 -0
- package/dist/ci-read-token.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +447 -22
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.d.ts +10 -1
- package/dist/close-job-template.d.ts.map +1 -1
- package/dist/close-job-template.js +46 -14
- package/dist/close-job-template.js.map +1 -1
- package/dist/close-job.d.ts +20 -5
- package/dist/close-job.d.ts.map +1 -1
- package/dist/close-job.js +65 -12
- package/dist/close-job.js.map +1 -1
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +20 -1
- package/dist/complete.js.map +1 -1
- package/dist/config.d.ts +41 -44
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -4
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts +63 -15
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +75 -8
- package/dist/continue-branch.js.map +1 -1
- package/dist/crashed-run-save.d.ts +73 -0
- package/dist/crashed-run-save.d.ts.map +1 -0
- package/dist/crashed-run-save.js +86 -0
- package/dist/crashed-run-save.js.map +1 -0
- package/dist/cwd-section.d.ts.map +1 -1
- package/dist/cwd-section.js +10 -2
- package/dist/cwd-section.js.map +1 -1
- package/dist/decision-engine.d.ts +10 -0
- package/dist/decision-engine.d.ts.map +1 -1
- package/dist/decision-engine.js.map +1 -1
- package/dist/do-config.d.ts +2 -24
- package/dist/do-config.d.ts.map +1 -1
- package/dist/do-config.js +1 -33
- package/dist/do-config.js.map +1 -1
- package/dist/do.d.ts +149 -1
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +115 -25
- package/dist/do.js.map +1 -1
- package/dist/dorfl-item-template.d.ts +98 -0
- package/dist/dorfl-item-template.d.ts.map +1 -0
- package/dist/dorfl-item-template.js +504 -0
- package/dist/dorfl-item-template.js.map +1 -0
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +7 -5
- package/dist/env-config.js.map +1 -1
- package/dist/frontmatter.d.ts +52 -0
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +136 -2
- package/dist/frontmatter.js.map +1 -1
- package/dist/gc.d.ts +13 -0
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +16 -2
- package/dist/gc.js.map +1 -1
- package/dist/git.d.ts +18 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +70 -5
- package/dist/git.js.map +1 -1
- package/dist/harness.d.ts +44 -5
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +24 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/install-ci-action-pins.d.ts +123 -0
- package/dist/install-ci-action-pins.d.ts.map +1 -0
- package/dist/install-ci-action-pins.js +190 -0
- package/dist/install-ci-action-pins.js.map +1 -0
- package/dist/install-ci-actions-pr-creation.d.ts +70 -0
- package/dist/install-ci-actions-pr-creation.d.ts.map +1 -0
- package/dist/install-ci-actions-pr-creation.js +106 -0
- package/dist/install-ci-actions-pr-creation.js.map +1 -0
- package/dist/install-ci-capabilities/dorfl-item.d.ts +12 -0
- package/dist/install-ci-capabilities/dorfl-item.d.ts.map +1 -0
- package/dist/install-ci-capabilities/dorfl-item.js +26 -0
- package/dist/install-ci-capabilities/dorfl-item.js.map +1 -0
- package/dist/install-ci-core.d.ts +155 -37
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +304 -200
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +47 -2
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +113 -1
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-ci.d.ts +25 -6
- package/dist/install-ci.d.ts.map +1 -1
- package/dist/install-ci.js +72 -36
- package/dist/install-ci.js.map +1 -1
- package/dist/intake-marker.d.ts +6 -1
- package/dist/intake-marker.d.ts.map +1 -1
- package/dist/intake-marker.js.map +1 -1
- package/dist/intake-triage.d.ts.map +1 -1
- package/dist/intake-triage.js +6 -2
- package/dist/intake-triage.js.map +1 -1
- package/dist/intake-trigger-template.d.ts +31 -25
- package/dist/intake-trigger-template.d.ts.map +1 -1
- package/dist/intake-trigger-template.js +107 -292
- package/dist/intake-trigger-template.js.map +1 -1
- package/dist/intake.d.ts +186 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +533 -416
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +154 -1
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +759 -486
- package/dist/integration-core.js.map +1 -1
- package/dist/integrator.d.ts +12 -0
- package/dist/integrator.d.ts.map +1 -1
- package/dist/integrator.js +21 -0
- package/dist/integrator.js.map +1 -1
- package/dist/isolation.d.ts +12 -2
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +19 -8
- package/dist/isolation.js.map +1 -1
- package/dist/item-lock.d.ts +93 -11
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +233 -65
- package/dist/item-lock.js.map +1 -1
- package/dist/land-question-residue.d.ts +58 -0
- package/dist/land-question-residue.d.ts.map +1 -0
- package/dist/land-question-residue.js +95 -0
- package/dist/land-question-residue.js.map +1 -0
- package/dist/lifecycle-gather.d.ts +16 -0
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +54 -6
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/merge-answer.d.ts +41 -0
- package/dist/merge-answer.d.ts.map +1 -0
- package/dist/merge-answer.js +60 -0
- package/dist/merge-answer.js.map +1 -0
- package/dist/merge-question-surfacer.d.ts +105 -30
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +202 -91
- package/dist/merge-question-surfacer.js.map +1 -1
- package/dist/merge-question-tick.d.ts +64 -0
- package/dist/merge-question-tick.d.ts.map +1 -0
- package/dist/merge-question-tick.js +96 -0
- package/dist/merge-question-tick.js.map +1 -0
- package/dist/mint-adr.d.ts.map +1 -1
- package/dist/mint-adr.js +20 -2
- package/dist/mint-adr.js.map +1 -1
- package/dist/needs-attention.d.ts +72 -2
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +275 -38
- package/dist/needs-attention.js.map +1 -1
- package/dist/phase-recorder.d.ts +168 -0
- package/dist/phase-recorder.d.ts.map +1 -0
- package/dist/phase-recorder.js +218 -0
- package/dist/phase-recorder.js.map +1 -0
- package/dist/phase.d.ts +94 -0
- package/dist/phase.d.ts.map +1 -0
- package/dist/phase.js +148 -0
- package/dist/phase.js.map +1 -0
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +19 -6
- package/dist/pi-harness.js.map +1 -1
- package/dist/prepare.d.ts.map +1 -1
- package/dist/prepare.js +4 -0
- package/dist/prepare.js.map +1 -1
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +48 -1
- package/dist/prompt.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +3 -1
- package/dist/protocol/WORK-CONTRACT.md +1 -1
- package/dist/reap-branches.d.ts +48 -0
- package/dist/reap-branches.d.ts.map +1 -1
- package/dist/reap-branches.js +64 -0
- package/dist/reap-branches.js.map +1 -1
- package/dist/ref-write.d.ts +198 -0
- package/dist/ref-write.d.ts.map +1 -0
- package/dist/ref-write.js +79 -0
- package/dist/ref-write.js.map +1 -0
- package/dist/repo-config.js +8 -8
- package/dist/repo-config.js.map +1 -1
- package/dist/repo-mirror.d.ts +36 -0
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +91 -1
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-verdict.d.ts +1 -1
- package/dist/review-verdict.js +1 -1
- package/dist/run.d.ts +7 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +28 -4
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +19 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +21 -8
- package/dist/scan.js.map +1 -1
- package/dist/sidecar.d.ts +14 -0
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +21 -0
- package/dist/sidecar.js.map +1 -1
- package/dist/single-job-warning.d.ts +63 -0
- package/dist/single-job-warning.d.ts.map +1 -0
- package/dist/single-job-warning.js +110 -0
- package/dist/single-job-warning.js.map +1 -0
- package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +3 -1
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +1 -1
- package/dist/spec-complete.d.ts +13 -6
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +23 -11
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +47 -14
- package/dist/start.js.map +1 -1
- package/dist/status.d.ts +12 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +11 -3
- package/dist/status.js.map +1 -1
- package/dist/tasking.d.ts +246 -1
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +452 -358
- package/dist/tasking.js.map +1 -1
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +21 -2
- package/dist/triage-persist.js.map +1 -1
- package/dist/verify-workflow-template.d.ts.map +1 -1
- package/dist/verify-workflow-template.js +8 -4
- package/dist/verify-workflow-template.js.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +4 -0
- package/dist/verify.js.map +1 -1
- package/dist/watch-session.d.ts +42 -13
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +52 -23
- package/dist/watch-session.js.map +1 -1
- package/dist/work-layout.d.ts +10 -4
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +11 -3
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +65 -4
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +159 -11
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
- package/src/advance-ci-template.ts +80 -81
- package/src/advance-drivers.ts +45 -6
- package/src/advance-isolated.ts +3 -5
- package/src/advance-lifecycle-template.ts +375 -538
- package/src/advance-loop-driver.ts +3 -5
- package/src/advance-treeless-publish.ts +61 -0
- package/src/advance.ts +145 -15
- package/src/advancing-lock.ts +36 -1
- package/src/agent-env.ts +174 -0
- package/src/agent-stop.ts +81 -0
- package/src/apply-merge-action.ts +401 -154
- package/src/apply-stuck-action.ts +2 -2
- package/src/ci-agent-result.ts +421 -0
- package/src/ci-handoff-apply.ts +737 -0
- package/src/ci-handoff-format.ts +926 -0
- package/src/ci-handoff-lfs.ts +519 -0
- package/src/ci-handoff.ts +392 -0
- package/src/ci-lock-outputs.ts +273 -0
- package/src/ci-phase-build.ts +1328 -0
- package/src/ci-phase-driver.ts +548 -0
- package/src/ci-phase-intake.ts +861 -0
- package/src/ci-phase-tasking.ts +1255 -0
- package/src/ci-phase-treeless.ts +2028 -0
- package/src/ci-read-token.ts +90 -0
- package/src/cli.ts +591 -29
- package/src/close-job-template.ts +52 -15
- package/src/close-job.ts +77 -12
- package/src/complete.ts +19 -1
- package/src/config.ts +43 -48
- package/src/continue-branch.ts +121 -16
- package/src/crashed-run-save.ts +162 -0
- package/src/cwd-section.ts +10 -0
- package/src/decision-engine.ts +10 -0
- package/src/do-config.ts +2 -57
- package/src/do.ts +170 -28
- package/src/dorfl-item-template.ts +556 -0
- package/src/env-config.ts +7 -5
- package/src/frontmatter.ts +155 -2
- package/src/gc.ts +27 -2
- package/src/git.ts +83 -5
- package/src/harness.ts +58 -6
- package/src/index.ts +50 -1
- package/src/install-ci-action-pins.ts +209 -0
- package/src/install-ci-actions-pr-creation.ts +186 -0
- package/src/install-ci-capabilities/dorfl-item.ts +33 -0
- package/src/install-ci-core.ts +388 -233
- package/src/install-ci-github.ts +149 -1
- package/src/install-ci.ts +83 -37
- package/src/intake-marker.ts +6 -1
- package/src/intake-triage.ts +7 -3
- package/src/intake-trigger-template.ts +126 -327
- package/src/intake.ts +725 -544
- package/src/integration-core.ts +975 -523
- package/src/integrator.ts +28 -0
- package/src/isolation.ts +29 -10
- package/src/item-lock.ts +308 -92
- package/src/land-question-residue.ts +122 -0
- package/src/lifecycle-gather.ts +82 -10
- package/src/merge-answer.ts +82 -0
- package/src/merge-question-surfacer.ts +320 -112
- package/src/merge-question-tick.ts +182 -0
- package/src/mint-adr.ts +28 -2
- package/src/needs-attention.ts +350 -42
- package/src/phase-recorder.ts +350 -0
- package/src/phase.ts +177 -0
- package/src/pi-harness.ts +25 -5
- package/src/prepare.ts +4 -0
- package/src/prompt.ts +68 -1
- package/src/reap-branches.ts +107 -0
- package/src/ref-write.ts +324 -0
- package/src/repo-config.ts +8 -8
- package/src/repo-mirror.ts +107 -1
- package/src/review-verdict.ts +1 -1
- package/src/run.ts +47 -6
- package/src/scan.ts +37 -7
- package/src/sidecar.ts +36 -0
- package/src/single-job-warning.ts +135 -0
- package/src/spec-complete.ts +36 -14
- package/src/start.ts +55 -16
- package/src/status.ts +29 -3
- package/src/tasking.ts +593 -378
- package/src/triage-persist.ts +24 -2
- package/src/verify-workflow-template.ts +8 -4
- package/src/verify.ts +4 -0
- package/src/watch-session.ts +86 -28
- package/src/work-layout.ts +11 -3
- package/src/workspace.ts +210 -14
package/src/needs-attention.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {run, runAsync, type RunResult} from './git.js';
|
|
|
12
12
|
import {
|
|
13
13
|
branchAheadOf,
|
|
14
14
|
rebaseContinuedBranchOntoMain,
|
|
15
|
+
describeContinueRebaseFailure,
|
|
15
16
|
} from './continue-branch.js';
|
|
16
17
|
import {
|
|
17
18
|
acquireItemLock,
|
|
@@ -45,6 +46,10 @@ import {
|
|
|
45
46
|
type BackoffOptions,
|
|
46
47
|
type Sleep,
|
|
47
48
|
} from './retry-backoff.js';
|
|
49
|
+
import {
|
|
50
|
+
saveCrashedRunWork,
|
|
51
|
+
type CrashedRunSaveOutcome,
|
|
52
|
+
} from './crashed-run-save.js';
|
|
48
53
|
|
|
49
54
|
/**
|
|
50
55
|
* The **needs-attention mechanism** (ADR `ledger-status-on-per-item-lock-refs`;
|
|
@@ -221,13 +226,29 @@ export interface ReturnToBacklogOptions {
|
|
|
221
226
|
reconcile?: boolean;
|
|
222
227
|
/**
|
|
223
228
|
* `requeue -m "<note>"` (the handoff note): an optional human steer for the
|
|
224
|
-
* NEXT agent.
|
|
225
|
-
* section to the item BODY before
|
|
229
|
+
* NEXT agent. ADDED (never overwritten) as a dated `## Requeue YYYY-MM-DD`
|
|
230
|
+
* section to the item BODY, just before `## Acceptance criteria` (so it never
|
|
231
|
+
* collides with a kept branch's done-move tail; see `insertRequeueNoteText`),
|
|
232
|
+
* before the move — the ledger file is the durable,
|
|
226
233
|
* conflict-safe, cross-machine home (same place the needs-attention reason
|
|
227
234
|
* lives). Repeated requeues ACCUMULATE a handoff log. Applies to BOTH modes
|
|
228
235
|
* (a steer is relevant even on `--reset`).
|
|
229
236
|
*/
|
|
230
237
|
message?: string;
|
|
238
|
+
/**
|
|
239
|
+
* The execution working area (config `workspacesDir`) where `do --isolated`
|
|
240
|
+
* retains job worktrees. When given, a HELD lock's retained job worktree is
|
|
241
|
+
* checked BEFORE the release (task
|
|
242
|
+
* `a-crashed-runs-local-work-is-saved-before-its-lock-is-released`): a crashed
|
|
243
|
+
* run's local-only commits / uncommitted residue are committed + pushed to
|
|
244
|
+
* `work/task-<slug>` first (see {@link saveCrashedRunWork}), and a failed save
|
|
245
|
+
* KEEPS the lock. Not consulted on `--reset` (which discards the work by
|
|
246
|
+
* design). Omitted ⇒ no worktree is looked at (the in-process callers, e.g.
|
|
247
|
+
* the deadline checkpoint, which saved its own branch already).
|
|
248
|
+
*/
|
|
249
|
+
workspacesDir?: string;
|
|
250
|
+
/** No-wait sleep for the crashed-run save push's backoff (tests). */
|
|
251
|
+
sleep?: Sleep;
|
|
231
252
|
/** Environment for child git processes. */
|
|
232
253
|
env?: NodeJS.ProcessEnv;
|
|
233
254
|
/** Sink for human-readable progress notes. */
|
|
@@ -251,6 +272,12 @@ export interface ReturnToBacklogResult {
|
|
|
251
272
|
reconciled?: boolean;
|
|
252
273
|
/** When NOT moved, why (e.g. the slug held no recoverable per-item lock on the arbiter, or a failed --reset delete). */
|
|
253
274
|
reasonNotMoved?: string;
|
|
275
|
+
/**
|
|
276
|
+
* What the crashed-run save found/did in the slug's retained job worktree
|
|
277
|
+
* (only when {@link ReturnToBacklogOptions.workspacesDir} was given and a lock
|
|
278
|
+
* was held; absent otherwise).
|
|
279
|
+
*/
|
|
280
|
+
crashedRunSave?: CrashedRunSaveOutcome;
|
|
254
281
|
/**
|
|
255
282
|
* **The ONE resolved continue-branch state** this requeue decided from — so a
|
|
256
283
|
* CALLER reports the same reality the requeue acted on instead of running its
|
|
@@ -383,18 +410,7 @@ export async function routeToNeedsAttention(
|
|
|
383
410
|
// tip. Skip when the tree is clean (no aborted work to save). NOTE this no
|
|
384
411
|
// longer needs a folder source — the body rests in `backlog/` (task 9a) and
|
|
385
412
|
// never moves on a bounce.
|
|
386
|
-
|
|
387
|
-
const hadWip = !nothingStaged(cwd, env);
|
|
388
|
-
if (hadWip) {
|
|
389
|
-
// Save the agent's uncommitted work as a plain wip commit on the work-branch
|
|
390
|
-
// tip (the RECOVERABLE half — a `requeue` continues from it). No bookkeeping
|
|
391
|
-
// trailer: after the per-item-lock cut-over (tasks 9a–9d) NO transient status
|
|
392
|
-
// (no `needs-attention/` move-only commit) lands on a branch, so a branch cut
|
|
393
|
-
// from `main` rebases PLAINLY with nothing to drop — the `drop-bookkeeping-rebase`
|
|
394
|
-
// machinery and its `Dorfl-Bookkeeping` trailer are gone (9d).
|
|
395
|
-
const wipBody = `chore(${slug}): save aborted work (wip)`;
|
|
396
|
-
gitHard(['commit', '-q', '-m', wipBody], cwd, env);
|
|
397
|
-
}
|
|
413
|
+
const hadWip = commitAbortedWork({cwd, slug, env});
|
|
398
414
|
const commitMessage = `chore(${slug}): bounce to stuck; ${options.reason}`;
|
|
399
415
|
const moveCommit = hadWip ? revParseHead(cwd, env) : undefined;
|
|
400
416
|
note(`Bounced '${slug}' to stuck (lock): ${options.reason}`);
|
|
@@ -417,6 +433,9 @@ export async function routeToNeedsAttention(
|
|
|
417
433
|
const arbiter = options.arbiter;
|
|
418
434
|
const result = await retryWithBackoff(
|
|
419
435
|
async () => {
|
|
436
|
+
// Write-seam implementation: `routeToNeedsAttention` is reached only
|
|
437
|
+
// through `ledgerWrite.applyNeedsAttentionTransition` or
|
|
438
|
+
// `refWrite.saveWorkBranch` (guard: `write-sites-through-seams.test.ts`).
|
|
420
439
|
const r = gitSoftRun(
|
|
421
440
|
['push', arbiter, `${branch}:${branch}`],
|
|
422
441
|
cwd,
|
|
@@ -451,6 +470,40 @@ export async function routeToNeedsAttention(
|
|
|
451
470
|
return {moved: true, commitMessage, moveCommit, branchPush, pushError};
|
|
452
471
|
}
|
|
453
472
|
|
|
473
|
+
/**
|
|
474
|
+
* The LOCAL half of {@link routeToNeedsAttention}'s save: commit whatever the
|
|
475
|
+
* agent left uncommitted (`git add -A`) as a plain wip commit on the current
|
|
476
|
+
* branch tip. Returns whether a commit was made (a clean tree makes none).
|
|
477
|
+
*
|
|
478
|
+
* No bookkeeping trailer: after the per-item-lock cut-over (tasks 9a–9d) NO
|
|
479
|
+
* transient status (no `needs-attention/` move-only commit) lands on a branch,
|
|
480
|
+
* so a branch cut from `main` rebases PLAINLY with nothing to drop: the
|
|
481
|
+
* `drop-bookkeeping-rebase` machinery and its `Dorfl-Bookkeeping` trailer are
|
|
482
|
+
* gone (9d).
|
|
483
|
+
*
|
|
484
|
+
* The CI agent phase (task `ci-split-build-path-non-integrate-intents`) runs
|
|
485
|
+
* this on its own when its pipeline halts at a needs-attention bounce, so the
|
|
486
|
+
* wip travels in the handoff bundle; the apply phase then re-runs the whole
|
|
487
|
+
* route on the bundle's tip, where this finds a clean tree and commits nothing.
|
|
488
|
+
*/
|
|
489
|
+
export function commitAbortedWork(params: {
|
|
490
|
+
cwd: string;
|
|
491
|
+
slug: string;
|
|
492
|
+
env?: NodeJS.ProcessEnv;
|
|
493
|
+
}): boolean {
|
|
494
|
+
const {cwd, slug, env} = params;
|
|
495
|
+
gitHard(['add', '-A'], cwd, env);
|
|
496
|
+
if (nothingStaged(cwd, env)) {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
gitHard(
|
|
500
|
+
['commit', '-q', '-m', `chore(${slug}): save aborted work (wip)`],
|
|
501
|
+
cwd,
|
|
502
|
+
env,
|
|
503
|
+
);
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
|
|
454
507
|
/**
|
|
455
508
|
* The clean re-queue (ADR §12 / WORK-CONTRACT return path): once the human has
|
|
456
509
|
* resolved the cause, move the stuck item back to `work/tasks/ready/<slug>.md` and
|
|
@@ -489,9 +542,10 @@ export async function routeToNeedsAttention(
|
|
|
489
542
|
* closes the claim-race window; a FAILED delete ABORTS (no backlog move)
|
|
490
543
|
* so the item stays in needs-attention. The next claim then finds NO
|
|
491
544
|
* arbiter branch and cuts fresh — no special claim-time logic.
|
|
492
|
-
* - **`-m "<note>"` = HANDOFF NOTE.** When `message` is set,
|
|
493
|
-
* `## Requeue YYYY-MM-DD` section to the item BODY
|
|
494
|
-
*
|
|
545
|
+
* - **`-m "<note>"` = HANDOFF NOTE.** When `message` is set, ADD a dated
|
|
546
|
+
* `## Requeue YYYY-MM-DD` section to the item BODY, just before
|
|
547
|
+
* `## Acceptance criteria` (additive; accumulates over repeated requeues,
|
|
548
|
+
* oldest first) for the next agent. Applies to BOTH modes.
|
|
495
549
|
*
|
|
496
550
|
* Like the move, NEVER throws for the expected "not in needs-attention" case.
|
|
497
551
|
*/
|
|
@@ -586,6 +640,10 @@ export async function deleteRemoteWorkBranchIfPresent(
|
|
|
586
640
|
env,
|
|
587
641
|
);
|
|
588
642
|
await gitSoftAsync(['branch', '-D', branch], cwd, env);
|
|
643
|
+
// Write-seam implementation: callers reach this through
|
|
644
|
+
// `refWrite.deleteRemoteWorkBranch`, or through `returnToBacklog` behind
|
|
645
|
+
// `ledgerWrite.applyReturnToBacklogTransition` (guard:
|
|
646
|
+
// `write-sites-through-seams.test.ts`).
|
|
589
647
|
const del = await gitSoftAsync(
|
|
590
648
|
['push', arbiter, '--delete', branch],
|
|
591
649
|
cwd,
|
|
@@ -636,6 +694,19 @@ export async function returnToBacklog(
|
|
|
636
694
|
};
|
|
637
695
|
}
|
|
638
696
|
|
|
697
|
+
// Destructive vs non-destructive are exclusive verbs on the same escalation.
|
|
698
|
+
// Checked up front (before any lock is read or taken) so the refusal is the
|
|
699
|
+
// same whether the item's lock is held or already released by a surface.
|
|
700
|
+
if (options.reconcile && options.reset) {
|
|
701
|
+
return {
|
|
702
|
+
moved: false,
|
|
703
|
+
reasonNotMoved:
|
|
704
|
+
`requeue for '${slug}': --reconcile and --reset are mutually ` +
|
|
705
|
+
'exclusive (non-destructive recovery vs destructive last resort). ' +
|
|
706
|
+
'Pick one.',
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
639
710
|
// Refresh the remote-tracking refs so every check below (the item's residence,
|
|
640
711
|
// the continue-branch guard, the CAS base) sees the arbiter's TRUTH, not a stale
|
|
641
712
|
// local copy. This is a fetch, not a checkout — the working tree is untouched.
|
|
@@ -676,7 +747,58 @@ export async function returnToBacklog(
|
|
|
676
747
|
} catch {
|
|
677
748
|
held = await readLocalItemLock(slug, cwd, env);
|
|
678
749
|
}
|
|
750
|
+
// RELEASED-LOCK RECOVERY (task
|
|
751
|
+
// `requeue-reset-and-reconcile-work-after-the-lock-was-released`). A surface
|
|
752
|
+
// (`surfaceStuckToNeedsAttention`) RELEASES the per-item lock after landing the
|
|
753
|
+
// question sidecar, so a surfaced item with a KEPT work branch has no held lock,
|
|
754
|
+
// yet it is exactly what `--reconcile` (re-sync + retry) and `--reset` (discard,
|
|
755
|
+
// start fresh) exist for. When the item's body rests in the pool/staging on
|
|
756
|
+
// `<arbiter>/main` AND its `work/task-<slug>` branch is on the arbiter, take a
|
|
757
|
+
// SHORT per-item lock (create-only, like claim) so no claim can continue from
|
|
758
|
+
// the branch while it is being rebased or deleted, run the ordinary held-lock
|
|
759
|
+
// path, and give the short lock back on every outcome. Plain `requeue` stays a
|
|
760
|
+
// no-op (the next claim already continues from the kept branch).
|
|
761
|
+
let shortLock = false;
|
|
762
|
+
let releasedRecoverable: ReleasedRecoverableItem | undefined;
|
|
679
763
|
if (!held) {
|
|
764
|
+
releasedRecoverable = await probeReleasedRecoverableItem({
|
|
765
|
+
cwd,
|
|
766
|
+
slug,
|
|
767
|
+
arbiter,
|
|
768
|
+
branch: continueBranchName,
|
|
769
|
+
env,
|
|
770
|
+
});
|
|
771
|
+
if (
|
|
772
|
+
releasedRecoverable !== undefined &&
|
|
773
|
+
(options.reset || options.reconcile)
|
|
774
|
+
) {
|
|
775
|
+
const verb = options.reset ? '--reset' : '--reconcile';
|
|
776
|
+
const acquired = await acquireItemLock({
|
|
777
|
+
item: `task:${slug}`,
|
|
778
|
+
action: 'implement',
|
|
779
|
+
cwd,
|
|
780
|
+
arbiter,
|
|
781
|
+
env,
|
|
782
|
+
});
|
|
783
|
+
if (acquired.outcome !== 'acquired') {
|
|
784
|
+
const message =
|
|
785
|
+
`requeue ${verb} for '${slug}': its lock was already released, ` +
|
|
786
|
+
'but the short lock this recovery needs could not be taken ' +
|
|
787
|
+
(acquired.outcome === 'lost'
|
|
788
|
+
? '(another run claimed the item meanwhile); nothing was changed.'
|
|
789
|
+
: `(${acquired.message}); nothing was changed. Try again shortly.`);
|
|
790
|
+
note(message);
|
|
791
|
+
return {moved: false, reasonNotMoved: message};
|
|
792
|
+
}
|
|
793
|
+
shortLock = true;
|
|
794
|
+
note(
|
|
795
|
+
`'${slug}' has no held lock (a surface or earlier requeue already ` +
|
|
796
|
+
`released it); took a short lock to ${options.reset ? 'discard' : 'reconcile'} ` +
|
|
797
|
+
`the kept ${continueBranchName} (${verb}).`,
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
if (!held && !shortLock) {
|
|
680
802
|
// CROSS-NAMESPACE HINT (observation
|
|
681
803
|
// `crashed-do-spec-strands-a-tasking-lock-no-verb-releases`). `requeue` is a
|
|
682
804
|
// TASK-only verb, so a bare `<slug>` resolves to `task:<slug>` and finds no
|
|
@@ -716,6 +838,19 @@ export async function returnToBacklog(
|
|
|
716
838
|
} catch {
|
|
717
839
|
// Best-effort hint only: fall through to the plain refusal.
|
|
718
840
|
}
|
|
841
|
+
// A released item that DOES rest in the pool with a kept branch (plain
|
|
842
|
+
// requeue only; `--reset`/`--reconcile` took the short-lock path above):
|
|
843
|
+
// nothing to release, and the next claim already continues from the branch.
|
|
844
|
+
// Say so, and name the two verbs that DO act on a released item.
|
|
845
|
+
const keptHint =
|
|
846
|
+
releasedRecoverable !== undefined
|
|
847
|
+
? ` NOTE: '${slug}' rests in the pool (${releasedRecoverable.bodyRel}) ` +
|
|
848
|
+
`with a kept work branch ${continueBranchName} on ${arbiter}; the ` +
|
|
849
|
+
'next claim continues from it, so plain requeue has nothing to do. ' +
|
|
850
|
+
'To act on the kept branch: `requeue --reconcile` (non-destructive ' +
|
|
851
|
+
're-sync + rebase retry) or `requeue --reset` (DESTRUCTIVELY discard ' +
|
|
852
|
+
'it and start fresh).'
|
|
853
|
+
: '';
|
|
719
854
|
return {
|
|
720
855
|
moved: false,
|
|
721
856
|
reasonNotMoved:
|
|
@@ -723,10 +858,147 @@ export async function returnToBacklog(
|
|
|
723
858
|
'(wrong slug, or already at rest in backlog/done?). requeue recovers a ' +
|
|
724
859
|
'task whose lock is held stuck (needs-attention) or active (a killed ' +
|
|
725
860
|
'in-progress run).' +
|
|
726
|
-
specHint
|
|
861
|
+
specHint +
|
|
862
|
+
keptHint,
|
|
727
863
|
};
|
|
728
864
|
}
|
|
729
865
|
|
|
866
|
+
// CRASHED-RUN SAVE (task
|
|
867
|
+
// `a-crashed-runs-local-work-is-saved-before-its-lock-is-released`). A run
|
|
868
|
+
// killed mid-agent leaves its lock held and its job worktree retained, possibly
|
|
869
|
+
// with commits (or a wip residue) the arbiter's work branch lacks. Releasing
|
|
870
|
+
// the lock over them hands the item to a next claim (or a `gc --force`) that
|
|
871
|
+
// cuts that worktree away, so SAVE them first: wip-commit + push the work
|
|
872
|
+
// branch. A failed save KEEPS the lock (and the worktree) and says so. Only on
|
|
873
|
+
// a HELD lock (a short recovery lock means the run already surfaced, its lock
|
|
874
|
+
// released) and never on `--reset` (which discards the branch by design).
|
|
875
|
+
let crashedRunSave: CrashedRunSaveOutcome | undefined;
|
|
876
|
+
if (held && !options.reset && options.workspacesDir !== undefined) {
|
|
877
|
+
crashedRunSave = await saveCrashedRunWork({
|
|
878
|
+
cwd,
|
|
879
|
+
arbiter,
|
|
880
|
+
slug,
|
|
881
|
+
workspacesDir: options.workspacesDir,
|
|
882
|
+
env,
|
|
883
|
+
note,
|
|
884
|
+
...(options.sleep !== undefined ? {sleep: options.sleep} : {}),
|
|
885
|
+
});
|
|
886
|
+
if (crashedRunSave.kind === 'not-saved') {
|
|
887
|
+
const message =
|
|
888
|
+
`requeue for '${slug}': its crashed run left work in ` +
|
|
889
|
+
`${crashedRunSave.dir} that the arbiter's ${crashedRunSave.branch} ` +
|
|
890
|
+
`lacks, and it could not be saved: ${crashedRunSave.detail}. The lock ` +
|
|
891
|
+
'is KEPT (not released) and the worktree left in place, so nothing is ' +
|
|
892
|
+
`lost. Push ${crashedRunSave.branch} from that worktree by hand, then ` +
|
|
893
|
+
'`requeue` again.';
|
|
894
|
+
note(message);
|
|
895
|
+
return {moved: false, reasonNotMoved: message, crashedRunSave};
|
|
896
|
+
}
|
|
897
|
+
if (crashedRunSave.kind === 'saved') {
|
|
898
|
+
// The guard below reads the arbiter's branch from THIS clone: bring the
|
|
899
|
+
// just-pushed tip (and its objects) in.
|
|
900
|
+
await refreshArbiterRefs({
|
|
901
|
+
cwd,
|
|
902
|
+
arbiter,
|
|
903
|
+
branches: ['main', continueBranchName],
|
|
904
|
+
env,
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const heldResult = await requeueHeldItem({
|
|
910
|
+
options,
|
|
911
|
+
arbiter,
|
|
912
|
+
continueBranchName,
|
|
913
|
+
note,
|
|
914
|
+
});
|
|
915
|
+
const result =
|
|
916
|
+
crashedRunSave !== undefined ? {...heldResult, crashedRunSave} : heldResult;
|
|
917
|
+
if (shortLock && !result.moved) {
|
|
918
|
+
// Give the SHORT lock back: the item returns to exactly the released,
|
|
919
|
+
// surfaced state it was in (its question sidecar untouched). A failed
|
|
920
|
+
// release here is reported, never thrown.
|
|
921
|
+
const back = await releaseItemLock({
|
|
922
|
+
item: `task:${slug}`,
|
|
923
|
+
cwd,
|
|
924
|
+
arbiter,
|
|
925
|
+
env,
|
|
926
|
+
});
|
|
927
|
+
const tail =
|
|
928
|
+
back.outcome === 'error'
|
|
929
|
+
? ` WARNING: could not release the short recovery lock this requeue ` +
|
|
930
|
+
`took (${back.message}); clear it with \`dorfl release-lock ` +
|
|
931
|
+
`task:${slug}\`.`
|
|
932
|
+
: ' (The short recovery lock this requeue took was released again; ' +
|
|
933
|
+
'the item stays as it was before this requeue.)';
|
|
934
|
+
note(tail.trim());
|
|
935
|
+
return {...result, reasonNotMoved: (result.reasonNotMoved ?? '') + tail};
|
|
936
|
+
}
|
|
937
|
+
return result;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/** A task whose lock is RELEASED but which rests in the pool with a kept branch. */
|
|
941
|
+
interface ReleasedRecoverableItem {
|
|
942
|
+
/** The body's on-`main` path (`work/tasks/ready/<slug>.md` or staging). */
|
|
943
|
+
bodyRel: string;
|
|
944
|
+
/** The kept branch's tip on the arbiter. */
|
|
945
|
+
branchSha: string;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Is `<slug>` a RELEASED-lock task that `requeue --reset`/`--reconcile` can act
|
|
950
|
+
* on? True iff its body rests in `tasks/ready/` or `tasks/backlog/` on
|
|
951
|
+
* `<arbiter>/main` (the D1 probe a surface uses) AND the arbiter HAS its
|
|
952
|
+
* `work/task-<slug>` branch (arbiter-authoritative `ls-remote`). A surfaced item
|
|
953
|
+
* (question sidecar + `needsAnswers:true`, lock released) is the motivating
|
|
954
|
+
* case, but the sidecar is NOT required: a plain requeue that already released
|
|
955
|
+
* the lock leaves the same recoverable shape. An unknown slug (no body) or a
|
|
956
|
+
* pooled item with no kept branch answers `undefined`, so the refusal for those
|
|
957
|
+
* is unchanged. Best-effort: an unreachable arbiter answers `undefined`.
|
|
958
|
+
*/
|
|
959
|
+
async function probeReleasedRecoverableItem(params: {
|
|
960
|
+
cwd: string;
|
|
961
|
+
slug: string;
|
|
962
|
+
arbiter: string;
|
|
963
|
+
branch: string;
|
|
964
|
+
env: NodeJS.ProcessEnv | undefined;
|
|
965
|
+
}): Promise<ReleasedRecoverableItem | undefined> {
|
|
966
|
+
const {cwd, slug, arbiter, branch, env} = params;
|
|
967
|
+
try {
|
|
968
|
+
const bodyRel = await resolveBounceItemBodyPathOnMain({
|
|
969
|
+
cwd,
|
|
970
|
+
item: `task:${slug}`,
|
|
971
|
+
arbiter,
|
|
972
|
+
env,
|
|
973
|
+
});
|
|
974
|
+
if (bodyRel === undefined) {
|
|
975
|
+
return undefined;
|
|
976
|
+
}
|
|
977
|
+
const resolved = await resolveArbiterBranch({cwd, arbiter, branch, env});
|
|
978
|
+
if (!resolved.trustworthy || resolved.sha === undefined) {
|
|
979
|
+
return undefined;
|
|
980
|
+
}
|
|
981
|
+
return {bodyRel, branchSha: resolved.sha};
|
|
982
|
+
} catch {
|
|
983
|
+
return undefined;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* The body of {@link returnToBacklog} once the item's per-item lock is HELD
|
|
989
|
+
* (either a pre-existing stuck/active hold, or the short lock a released-lock
|
|
990
|
+
* `--reset`/`--reconcile` just took): the reconcile / reset / keep+continue
|
|
991
|
+
* guard, the optional handoff note, and the lock release.
|
|
992
|
+
*/
|
|
993
|
+
async function requeueHeldItem(params: {
|
|
994
|
+
options: ReturnToBacklogOptions;
|
|
995
|
+
arbiter: string;
|
|
996
|
+
continueBranchName: string;
|
|
997
|
+
note: (message: string) => void;
|
|
998
|
+
}): Promise<ReturnToBacklogResult> {
|
|
999
|
+
const {options, arbiter, continueBranchName, note} = params;
|
|
1000
|
+
const {cwd, slug, env} = options;
|
|
1001
|
+
|
|
730
1002
|
// `--reconcile`: the NON-DESTRUCTIVE recovery rung (task
|
|
731
1003
|
// `requeue-reconcile-nondestructive-recovery-verb`, parent observation
|
|
732
1004
|
// `rebase-conflict-on-continue-needs-nondestructive-recovery-not-reset`).
|
|
@@ -744,15 +1016,6 @@ export async function returnToBacklog(
|
|
|
744
1016
|
// deletes the remote branch — this verb's contract is "keep the work".
|
|
745
1017
|
let reconciled: boolean | undefined;
|
|
746
1018
|
if (options.reconcile) {
|
|
747
|
-
if (options.reset) {
|
|
748
|
-
return {
|
|
749
|
-
moved: false,
|
|
750
|
-
reasonNotMoved:
|
|
751
|
-
`requeue for '${slug}': --reconcile and --reset are mutually ` +
|
|
752
|
-
'exclusive (non-destructive recovery vs destructive last resort). ' +
|
|
753
|
-
'Pick one.',
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
1019
|
const attempt = await attemptReconcile({
|
|
757
1020
|
cwd,
|
|
758
1021
|
slug,
|
|
@@ -765,7 +1028,9 @@ export async function returnToBacklog(
|
|
|
765
1028
|
const message =
|
|
766
1029
|
`requeue --reconcile for '${slug}': re-synced the ${arbiter} mirror ` +
|
|
767
1030
|
`and RETRIED the rebase of ${branch} onto latest ${arbiter}/main, but ` +
|
|
768
|
-
|
|
1031
|
+
(attempt.genuineConflict === false
|
|
1032
|
+
? `the rebase still fails (${attempt.detail}). The `
|
|
1033
|
+
: `the rebase still conflicts on genuine content (${attempt.detail}). The `) +
|
|
769
1034
|
'kept branch is left UNTOUCHED on the arbiter (nothing deleted) and the ' +
|
|
770
1035
|
'item is left stuck. A supported mirror-side "resolve against latest ' +
|
|
771
1036
|
'main" command that fetches the kept branch into a scratch worktree, ' +
|
|
@@ -914,8 +1179,9 @@ export async function returnToBacklog(
|
|
|
914
1179
|
'tasks-backlog',
|
|
915
1180
|
];
|
|
916
1181
|
|
|
917
|
-
// `-m "<note>"` (the handoff steer):
|
|
918
|
-
// section
|
|
1182
|
+
// `-m "<note>"` (the handoff steer): ADD a dated `## Requeue YYYY-MM-DD`
|
|
1183
|
+
// section (before `## Acceptance criteria`, clear of a kept branch's done-move
|
|
1184
|
+
// tail) to the item BODY where it already rests (pool or staging), via the SAME
|
|
919
1185
|
// tree-less CAS move (same-folder rewrite with the body transform) — it NEVER
|
|
920
1186
|
// stages/commits in the cwd tree. The handoff is OPTIONAL and NON-FATAL: a failed
|
|
921
1187
|
// append degrades to a WARNING and the lock release below STILL runs, because the
|
|
@@ -946,7 +1212,7 @@ export async function returnToBacklog(
|
|
|
946
1212
|
base,
|
|
947
1213
|
sourceRel: bodyRel,
|
|
948
1214
|
destRel: bodyRel,
|
|
949
|
-
transformBody: (body) =>
|
|
1215
|
+
transformBody: (body) => insertRequeueNoteText(body, handoff),
|
|
950
1216
|
commitMessage: `chore(${slug}): requeue handoff note`,
|
|
951
1217
|
refNamespace: 'requeue',
|
|
952
1218
|
env,
|
|
@@ -1019,7 +1285,7 @@ export async function returnToBacklog(
|
|
|
1019
1285
|
type ReconcileAttempt =
|
|
1020
1286
|
| {kind: 'reconciled'}
|
|
1021
1287
|
| {kind: 'no-branch'}
|
|
1022
|
-
| {kind: 'conflict'; detail: string};
|
|
1288
|
+
| {kind: 'conflict'; detail: string; genuineConflict?: boolean};
|
|
1023
1289
|
|
|
1024
1290
|
/**
|
|
1025
1291
|
* The `--reconcile` recovery attempt — the non-destructive middle rung of the
|
|
@@ -1088,8 +1354,12 @@ async function attemptReconcile(params: {
|
|
|
1088
1354
|
}
|
|
1089
1355
|
try {
|
|
1090
1356
|
const rebase = rebaseContinuedBranchOntoMain(worktree, arbMainRef, env);
|
|
1091
|
-
if (rebase.kind
|
|
1092
|
-
return {
|
|
1357
|
+
if (rebase.kind !== 'clean') {
|
|
1358
|
+
return {
|
|
1359
|
+
kind: 'conflict',
|
|
1360
|
+
detail: `rebase after re-sync ${describeContinueRebaseFailure(rebase)}`,
|
|
1361
|
+
genuineConflict: rebase.kind === 'conflict',
|
|
1362
|
+
};
|
|
1093
1363
|
}
|
|
1094
1364
|
// 4. Push the reconciled tip back (`--force-with-lease`, WORK branch only).
|
|
1095
1365
|
const observedTip = gitSoftRun(
|
|
@@ -1099,6 +1369,10 @@ async function attemptReconcile(params: {
|
|
|
1099
1369
|
).stdout.trim();
|
|
1100
1370
|
const lease =
|
|
1101
1371
|
observedTip === '' ? `${branch}:` : `${branch}:${observedTip}`;
|
|
1372
|
+
// WRITE-SEAM EXEMPT: `attemptReconcile` backs the human-only
|
|
1373
|
+
// `requeue --reconcile` verb, never a CI path, so this reconciled
|
|
1374
|
+
// kept-branch push stays direct (task
|
|
1375
|
+
// `ci-split-route-direct-writes-through-seams`).
|
|
1102
1376
|
const push = gitSoftRun(
|
|
1103
1377
|
[
|
|
1104
1378
|
'push',
|
|
@@ -3160,22 +3434,56 @@ const BOUNCE_BODY_PROBE_ORDER: Record<SidecarType, readonly WorkFolderKey[]> = {
|
|
|
3160
3434
|
observation: ['observations'],
|
|
3161
3435
|
};
|
|
3162
3436
|
|
|
3437
|
+
/** The body headings a requeue handoff note is inserted BEFORE, in preference
|
|
3438
|
+
* order (see {@link insertRequeueNoteText}). `## Prompt` is the fallback anchor:
|
|
3439
|
+
* every BUILDABLE task has one (`resolveTask` refuses a body without it), so a
|
|
3440
|
+
* buildable body never falls through to the tail. */
|
|
3441
|
+
const REQUEUE_NOTE_ANCHORS: readonly RegExp[] = [
|
|
3442
|
+
/^##\s+Acceptance criteria\s*$/m,
|
|
3443
|
+
/^##\s+Prompt\s*$/m,
|
|
3444
|
+
];
|
|
3445
|
+
|
|
3163
3446
|
/**
|
|
3164
|
-
*
|
|
3165
|
-
* (
|
|
3166
|
-
* Body prose only (never a frontmatter field — WORK-CONTRACT
|
|
3167
|
-
* UTC `YYYY-MM-DD`; multiple notes on the same day are
|
|
3447
|
+
* Add a dated `## Requeue YYYY-MM-DD` handoff section to an item body's TEXT
|
|
3448
|
+
* (additive — never overwrites; repeated requeues accumulate a handoff log,
|
|
3449
|
+
* oldest first). Body prose only (never a frontmatter field — WORK-CONTRACT
|
|
3450
|
+
* rule 3). The date is UTC `YYYY-MM-DD`; multiple notes on the same day are
|
|
3451
|
+
* distinct blocks.
|
|
3452
|
+
*
|
|
3453
|
+
* PLACEMENT (task `requeue-handoff-note-does-not-conflict-with-the-kept-done-move`):
|
|
3454
|
+
* the section is inserted immediately BEFORE the `## Acceptance criteria`
|
|
3455
|
+
* heading, NOT at the end of the body. A kept work branch the next claim
|
|
3456
|
+
* CONTINUES from may already have done-moved this body AND appended its
|
|
3457
|
+
* `## Decisions` block at the END; a tail-appended note on `main` then collided
|
|
3458
|
+
* with that tail in the continue rebase (which never auto-resolves, ADR §10) and
|
|
3459
|
+
* bounced the item. Mid-body, the two edits are disjoint hunks and the rebase
|
|
3460
|
+
* merges them cleanly. A body with no `## Acceptance criteria` heading anchors on
|
|
3461
|
+
* `## Prompt` instead (a chore task written without acceptance criteria used to
|
|
3462
|
+
* fall back to the tail and still collide); only a body with NEITHER heading
|
|
3463
|
+
* (which is not buildable anyway) falls back to the end. The continue prompt reads every `## Requeue`
|
|
3464
|
+
* section wherever it sits (`extractRequeueNotes`), so the note still reaches
|
|
3465
|
+
* the continuing agent.
|
|
3168
3466
|
*
|
|
3169
3467
|
* A PURE string transform (it operates on the body CONTENT, not a file path) so
|
|
3170
3468
|
* the tree-less requeue can apply it to the blob read from `<arbiter>/main`
|
|
3171
3469
|
* without touching the cwd working tree.
|
|
3172
3470
|
*/
|
|
3173
|
-
function
|
|
3471
|
+
export function insertRequeueNoteText(
|
|
3472
|
+
content: string,
|
|
3473
|
+
message: string,
|
|
3474
|
+
): string {
|
|
3174
3475
|
const date = new Date().toISOString().slice(0, 10);
|
|
3175
|
-
const
|
|
3176
|
-
|
|
3177
|
-
|
|
3476
|
+
const section = `${REQUEUE_HEADING_PREFIX} ${date}\n\n${message}\n`;
|
|
3477
|
+
const anchor = REQUEUE_NOTE_ANCHORS.map((re) => re.exec(content)).find(
|
|
3478
|
+
(m) => m !== null,
|
|
3178
3479
|
);
|
|
3480
|
+
if (anchor === undefined || anchor === null) {
|
|
3481
|
+
const base = content.replace(/\s*$/, '');
|
|
3482
|
+
return `${base}\n\n${section}`;
|
|
3483
|
+
}
|
|
3484
|
+
const before = content.slice(0, anchor.index).replace(/\s*$/, '');
|
|
3485
|
+
const after = content.slice(anchor.index);
|
|
3486
|
+
return `${before}\n\n${section}\n${after}`;
|
|
3179
3487
|
}
|
|
3180
3488
|
|
|
3181
3489
|
/**
|