dorfl 0.1.2 → 0.2.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-drivers.d.ts +1 -1
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +15 -3
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +76 -4
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-treeless-publish.d.ts +23 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -1
- package/dist/advance-treeless-publish.js +41 -0
- package/dist/advance-treeless-publish.js.map +1 -1
- package/dist/advance.d.ts +59 -7
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +389 -82
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +30 -2
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +50 -0
- package/dist/advancing-lock.js.map +1 -1
- package/dist/agent-launch.d.ts +12 -0
- package/dist/agent-launch.d.ts.map +1 -1
- package/dist/agent-launch.js +22 -12
- package/dist/agent-launch.js.map +1 -1
- package/dist/agent-stop.d.ts +40 -2
- package/dist/agent-stop.d.ts.map +1 -1
- package/dist/agent-stop.js +30 -2
- package/dist/agent-stop.js.map +1 -1
- package/dist/apply-decide.d.ts +17 -3
- package/dist/apply-decide.d.ts.map +1 -1
- package/dist/apply-decide.js +36 -7
- package/dist/apply-decide.js.map +1 -1
- package/dist/apply-merge-action.d.ts +17 -5
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +43 -8
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-persist.d.ts +57 -26
- package/dist/apply-persist.d.ts.map +1 -1
- package/dist/apply-persist.js +166 -37
- package/dist/apply-persist.js.map +1 -1
- package/dist/apply-stuck-action.d.ts +151 -0
- package/dist/apply-stuck-action.d.ts.map +1 -0
- package/dist/apply-stuck-action.js +125 -0
- package/dist/apply-stuck-action.js.map +1 -0
- package/dist/brand.d.ts +12 -1
- package/dist/brand.d.ts.map +1 -1
- package/dist/brand.js +2 -1
- package/dist/brand.js.map +1 -1
- package/dist/cli-spinner.d.ts +1 -1
- package/dist/cli-spinner.js +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +320 -63
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.js +1 -1
- package/dist/complete.d.ts +6 -3
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +91 -37
- package/dist/complete.js.map +1 -1
- package/dist/config.d.ts +55 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +56 -4
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +22 -0
- package/dist/continue-branch.js.map +1 -1
- package/dist/decision-engine.d.ts +38 -10
- package/dist/decision-engine.d.ts.map +1 -1
- package/dist/decision-engine.js +10 -6
- package/dist/decision-engine.js.map +1 -1
- package/dist/do-autopick.d.ts.map +1 -1
- package/dist/do-autopick.js +11 -1
- package/dist/do-autopick.js.map +1 -1
- package/dist/do-config.d.ts +1 -1
- package/dist/do.d.ts +49 -56
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +419 -30
- package/dist/do.js.map +1 -1
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +12 -4
- package/dist/env-config.js.map +1 -1
- package/dist/failure-cause.d.ts +3 -2
- package/dist/failure-cause.d.ts.map +1 -1
- package/dist/failure-cause.js +28 -1
- package/dist/failure-cause.js.map +1 -1
- package/dist/format.d.ts +6 -6
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +10 -27
- package/dist/format.js.map +1 -1
- package/dist/frontmatter.d.ts +16 -4
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +27 -1
- package/dist/frontmatter.js.map +1 -1
- package/dist/gc.d.ts +65 -6
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +126 -9
- package/dist/gc.js.map +1 -1
- package/dist/github.d.ts +14 -0
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +73 -0
- package/dist/github.js.map +1 -1
- package/dist/harness.d.ts +25 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/install-ci-branch-protection.d.ts +132 -38
- package/dist/install-ci-branch-protection.d.ts.map +1 -1
- package/dist/install-ci-branch-protection.js +190 -48
- package/dist/install-ci-branch-protection.js.map +1 -1
- package/dist/install-ci-core.d.ts +21 -1
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +10 -3
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +32 -0
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +73 -0
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-skills.d.ts +123 -0
- package/dist/install-skills.d.ts.map +1 -0
- package/dist/install-skills.js +100 -0
- package/dist/install-skills.js.map +1 -0
- package/dist/intake-trigger-template.js +1 -1
- package/dist/intake.d.ts +17 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +23 -7
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +10 -7
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +174 -60
- package/dist/integration-core.js.map +1 -1
- package/dist/integrator.d.ts +1 -1
- package/dist/integrator.d.ts.map +1 -1
- package/dist/integrator.js +19 -3
- package/dist/integrator.js.map +1 -1
- package/dist/isolation.d.ts +2 -2
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +9 -0
- package/dist/isolation.js.map +1 -1
- package/dist/item-lock.d.ts +196 -129
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +329 -254
- package/dist/item-lock.js.map +1 -1
- package/dist/ledger-write.d.ts +31 -26
- package/dist/ledger-write.d.ts.map +1 -1
- package/dist/ledger-write.js +100 -122
- package/dist/ledger-write.js.map +1 -1
- package/dist/lifecycle-gather.d.ts +17 -0
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +7 -1
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/lifecycle-pools.d.ts +40 -3
- package/dist/lifecycle-pools.d.ts.map +1 -1
- package/dist/lifecycle-pools.js +26 -6
- package/dist/lifecycle-pools.js.map +1 -1
- package/dist/merge-question-surfacer.d.ts +15 -1
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +18 -3
- package/dist/merge-question-surfacer.js.map +1 -1
- package/dist/migrate-stuck-locks.d.ts +129 -0
- package/dist/migrate-stuck-locks.d.ts.map +1 -0
- package/dist/migrate-stuck-locks.js +355 -0
- package/dist/migrate-stuck-locks.js.map +1 -0
- package/dist/needs-attention.d.ts +293 -16
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +532 -57
- package/dist/needs-attention.js.map +1 -1
- package/dist/orphan-sidecar.d.ts +8 -4
- package/dist/orphan-sidecar.d.ts.map +1 -1
- package/dist/orphan-sidecar.js +35 -2
- package/dist/orphan-sidecar.js.map +1 -1
- package/dist/pi-harness.d.ts +16 -0
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +82 -2
- package/dist/pi-harness.js.map +1 -1
- package/dist/placement.d.ts +3 -3
- package/dist/placement.js +1 -1
- package/dist/prd-to-spec.d.ts.map +1 -1
- package/dist/prd-to-spec.js +9 -5
- package/dist/prd-to-spec.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
- package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
- package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
- package/dist/protocol/TASKING-PROTOCOL.md +3 -1
- package/dist/protocol/WORK-CONTRACT.md +22 -18
- package/dist/protocol/task-template.md +1 -1
- package/dist/reap-branches.d.ts +12 -9
- package/dist/reap-branches.d.ts.map +1 -1
- package/dist/reap-branches.js +25 -7
- package/dist/reap-branches.js.map +1 -1
- package/dist/recover-isolated.d.ts +12 -0
- package/dist/recover-isolated.d.ts.map +1 -1
- package/dist/recover-isolated.js +5 -0
- package/dist/recover-isolated.js.map +1 -1
- package/dist/repo-config.d.ts +23 -2
- package/dist/repo-config.d.ts.map +1 -1
- package/dist/repo-config.js +53 -7
- package/dist/repo-config.js.map +1 -1
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +18 -2
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-verdict.js +1 -1
- package/dist/review-verdict.js.map +1 -1
- package/dist/run.d.ts +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +54 -19
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +17 -8
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +22 -8
- package/dist/scan.js.map +1 -1
- package/dist/select-priority.d.ts +2 -2
- package/dist/select-priority.js +1 -1
- package/dist/select-priority.js.map +1 -1
- package/dist/sidecar-apply.js +1 -1
- package/dist/sidecar-apply.js.map +1 -1
- package/dist/sidecar.d.ts +45 -10
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +82 -2
- package/dist/sidecar.js.map +1 -1
- package/dist/skills/answer-questions/SKILL.md +89 -0
- package/dist/skills/capture-signal/SKILL.md +52 -0
- package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
- package/dist/skills/drive-tasks/SKILL.md +218 -0
- package/dist/skills/from-idea/SKILL.md +83 -0
- package/dist/skills/merge-prs/SKILL.md +70 -0
- package/dist/skills/orchestrate/SKILL.md +101 -0
- package/dist/skills/promote/SKILL.md +35 -0
- package/dist/skills/review/SKILL.md +16 -0
- package/dist/skills/setup/SKILL.md +258 -0
- package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
- package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
- package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
- package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
- package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
- package/dist/skills/setup/protocol/spec-template.md +71 -0
- package/dist/skills/setup/protocol/task-template.md +65 -0
- package/dist/skills/surface-questions/SKILL.md +16 -0
- package/dist/skills/to-spec/SKILL.md +34 -0
- package/dist/skills/to-task/SKILL.md +19 -0
- package/dist/skills/triage-observations/SKILL.md +78 -0
- package/dist/skills/work/SKILL.md +51 -0
- package/dist/slug-namespace.d.ts +5 -5
- package/dist/slug-namespace.js +5 -5
- package/dist/slug-namespace.js.map +1 -1
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +2 -3
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +51 -57
- package/dist/start.js.map +1 -1
- package/dist/surface-gate.d.ts.map +1 -1
- package/dist/surface-gate.js +10 -3
- package/dist/surface-gate.js.map +1 -1
- package/dist/surface-persist.js +1 -1
- package/dist/surface-persist.js.map +1 -1
- package/dist/tasker-review-loop.d.ts +4 -3
- package/dist/tasker-review-loop.d.ts.map +1 -1
- package/dist/tasker-review-loop.js.map +1 -1
- package/dist/tasking-lock.d.ts +2 -2
- package/dist/tasking-lock.d.ts.map +1 -1
- package/dist/tasking-lock.js +53 -32
- package/dist/tasking-lock.js.map +1 -1
- package/dist/tasking.d.ts +40 -6
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +224 -12
- package/dist/tasking.js.map +1 -1
- package/dist/triage-persist.d.ts +10 -6
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +34 -4
- package/dist/triage-persist.js.map +1 -1
- package/dist/vendor/incur/agents.d.ts +58 -0
- package/dist/vendor/incur/agents.d.ts.map +1 -0
- package/dist/vendor/incur/agents.js +343 -0
- package/dist/vendor/incur/agents.js.map +1 -0
- package/dist/verify-workflow-template.js +1 -1
- package/dist/watch-session.d.ts +11 -3
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +94 -7
- package/dist/watch-session.js.map +1 -1
- package/dist/work-layout.d.ts +7 -3
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +6 -3
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/package.json +2 -2
- package/src/advance-drivers.ts +16 -3
- package/src/advance-lifecycle-template.ts +89 -4
- package/src/advance-treeless-publish.ts +45 -0
- package/src/advance.ts +467 -87
- package/src/advancing-lock.ts +96 -1
- package/src/agent-launch.ts +37 -12
- package/src/agent-stop.ts +60 -2
- package/src/apply-decide.ts +36 -7
- package/src/apply-merge-action.ts +45 -7
- package/src/apply-persist.ts +228 -54
- package/src/apply-stuck-action.ts +260 -0
- package/src/brand.ts +14 -2
- package/src/cli-spinner.ts +1 -1
- package/src/cli.ts +401 -70
- package/src/close-job-template.ts +1 -1
- package/src/complete.ts +110 -52
- package/src/config.ts +107 -13
- package/src/continue-branch.ts +22 -0
- package/src/decision-engine.ts +55 -17
- package/src/do-autopick.ts +12 -1
- package/src/do-config.ts +1 -1
- package/src/do.ts +511 -38
- package/src/env-config.ts +12 -4
- package/src/failure-cause.ts +30 -1
- package/src/format.ts +10 -30
- package/src/frontmatter.ts +35 -5
- package/src/gc.ts +172 -9
- package/src/github.ts +78 -0
- package/src/harness.ts +25 -0
- package/src/index.ts +10 -0
- package/src/install-ci-branch-protection.ts +282 -57
- package/src/install-ci-core.ts +30 -3
- package/src/install-ci-github.ts +86 -0
- package/src/install-skills.ts +166 -0
- package/src/intake-trigger-template.ts +1 -1
- package/src/intake.ts +46 -11
- package/src/integration-core.ts +199 -65
- package/src/integrator.ts +22 -9
- package/src/isolation.ts +11 -2
- package/src/item-lock.ts +485 -341
- package/src/ledger-write.ts +135 -150
- package/src/lifecycle-gather.ts +29 -6
- package/src/lifecycle-pools.ts +67 -9
- package/src/merge-question-surfacer.ts +28 -4
- package/src/migrate-stuck-locks.ts +451 -0
- package/src/needs-attention.ts +857 -84
- package/src/orphan-sidecar.ts +47 -6
- package/src/pi-harness.ts +82 -2
- package/src/placement.ts +3 -3
- package/src/prd-to-spec.ts +13 -5
- package/src/reap-branches.ts +38 -14
- package/src/recover-isolated.ts +17 -0
- package/src/repo-config.ts +55 -6
- package/src/repo-mirror.ts +23 -1
- package/src/review-verdict.ts +1 -1
- package/src/run.ts +64 -18
- package/src/scan.ts +22 -8
- package/src/select-priority.ts +3 -3
- package/src/sidecar-apply.ts +1 -1
- package/src/sidecar.ts +138 -11
- package/src/slug-namespace.ts +7 -7
- package/src/spec-complete.ts +2 -3
- package/src/start.ts +53 -59
- package/src/surface-gate.ts +10 -3
- package/src/surface-persist.ts +1 -1
- package/src/tasker-review-loop.ts +4 -3
- package/src/tasking-lock.ts +52 -36
- package/src/tasking.ts +261 -17
- package/src/triage-persist.ts +56 -12
- package/src/vendor/incur/LICENSE +21 -0
- package/src/vendor/incur/README.md +19 -0
- package/src/vendor/incur/agents.ts +392 -0
- package/src/verify-workflow-template.ts +1 -1
- package/src/watch-session.ts +102 -7
- package/src/work-layout.ts +6 -3
- package/src/workspace.ts +1 -1
package/dist/do.js
CHANGED
|
@@ -21,8 +21,159 @@ import { shouldFailProposePrIntent, PROPOSE_PR_INTENT_GH_UNAVAILABLE_MESSAGE, }
|
|
|
21
21
|
import { checkGatePreconditions, detectLockfileOnDisk, detectLockfileOnMirrorMain, } from './gate-readiness.js';
|
|
22
22
|
import { git, run, runAsync, localMainAheadCount } from './git.js';
|
|
23
23
|
import { identityEnv, assertTransportAllowed, } from './identity.js';
|
|
24
|
-
import { parseStopSentinel, isWorkBranchDiffEmpty, emptyDiffStopReason, } from './agent-stop.js';
|
|
24
|
+
import { parseStopSentinel, isWorkBranchDiffEmpty, emptyDiffStopReason, emptyDiffDisposeEnvelope, } from './agent-stop.js';
|
|
25
|
+
import { surfaceStuckToNeedsAttention, routeToNeedsAttention, } from './needs-attention.js';
|
|
26
|
+
import { resolveItemPathByIdentity } from './item-path.js';
|
|
25
27
|
import { classifyFailureCause, failureCauseLabel, } from './failure-cause.js';
|
|
28
|
+
/**
|
|
29
|
+
* `dorfl do <slug>` (in-place form) — the per-repo, in-place WORKER that
|
|
30
|
+
* claims + builds + gates + integrates in ONE checkout, then EXITS (ADR §3).
|
|
31
|
+
* **This is the CI command** (CI has a checkout, is one repo, is one triggered
|
|
32
|
+
* invocation, exits) and it ABSORBS the manual `ar-run.sh` test-driver.
|
|
33
|
+
*
|
|
34
|
+
* In-place `do` is on the ISOLATION SEAM (`do-run-share-isolation-seam`): it is
|
|
35
|
+
* the FIRST production consumer of `selectIsolationStrategy`/`inPlaceStrategy`,
|
|
36
|
+
* so all THREE `do`/`run` forms (in-place `do`, `do --remote`, `run`) share the
|
|
37
|
+
* ONE `IsolatedTree`-handle post-claim shape. The composition mirrors
|
|
38
|
+
* `do --remote`/`run` exactly: keep the two in-place GUARDS (dirty-tree refusal
|
|
39
|
+
* + pre-flight diverged-main guard) in this driver BEFORE onboarding, CLAIM
|
|
40
|
+
* explicitly via the CAS (`performClaim`), then let
|
|
41
|
+
* `selectIsolationStrategy({checkout}).prepare()` do the ONBOARDING half (fetch +
|
|
42
|
+
* continue-detection + fresh-main `work/<slug>` switch, incl. the §14
|
|
43
|
+
* continue/rebase + §10 conflict path) WITHOUT re-claiming. The agent run is the
|
|
44
|
+
* ONLY new middle step (the one `ar-run.sh` shelled out for, `prompt | pi`). The
|
|
45
|
+
* runner owns EVERY git-state transition (claim, done-move, completion commit,
|
|
46
|
+
* integration); the agent only edits code.
|
|
47
|
+
*
|
|
48
|
+
* The in-place ISOLATION (ADR §3): the current checkout / CI container IS the
|
|
49
|
+
* isolation — no hub mirror, no external worktree. `inPlaceStrategy.prepare()`
|
|
50
|
+
* puts the checkout on `work/<slug>` cut from the freshly-fetched
|
|
51
|
+
* `<arbiter>/main`; its handle's `teardown` is a NO-OP (the checkout is left in a
|
|
52
|
+
* defined state on `work/<slug>`, NEVER reaped). `do --remote` (the job-worktree
|
|
53
|
+
* strategy) is the SEPARATE `do-remote` task; auto-pick / multi-arg / `-n` is
|
|
54
|
+
* `do-autopick`.
|
|
55
|
+
*
|
|
56
|
+
* CLAIM SEMANTICS (autonomous, same as `do --remote`/`run`): the claim is
|
|
57
|
+
* explicit and claim-or-lose. An item that is NOT in the pool (`tasks-ready`) on
|
|
58
|
+
* the arbiter (already in-progress / done / absent) is not claimable, so the CAS
|
|
59
|
+
* returns
|
|
60
|
+
* `lost` (exit 2) and the run skips cleanly — `do`, the unattended CI worker,
|
|
61
|
+
* never re-claims an item someone else holds and never silently picks up a
|
|
62
|
+
* needs-attention item (a human does that through the human face).
|
|
63
|
+
*
|
|
64
|
+
* **CRITICAL — `do` is AUTONOMOUS, so its failure path is `run`'s, NOT
|
|
65
|
+
* `complete`'s.** On a red gate / rebase conflict, `performComplete` routes the
|
|
66
|
+
* item to needs-attention via the SAME seam call as `run`'s `runOneItem`, but
|
|
67
|
+
* `complete` calls it WITHOUT an arbiter (the human path: a human is right
|
|
68
|
+
* there). `do` runs UNATTENDED, so it MUST get the autonomous, arbiter-passed
|
|
69
|
+
* surfacing like `run` (the on-`main` cherry-pick that makes a stuck CI run
|
|
70
|
+
* visible to `scan`/`status`/another machine). We achieve this by passing
|
|
71
|
+
* `surfaceArbiter` into `performComplete` (resolution (a) from the task): the
|
|
72
|
+
* success path reuses `complete`'s machinery; only the NEEDS-ATTENTION routing
|
|
73
|
+
* becomes the autonomous variant.
|
|
74
|
+
*
|
|
75
|
+
* `--propose` (default) / `--merge` is resolved at integrate-time exactly like
|
|
76
|
+
* `complete` (the caller threads the resolved mode in as `integration`).
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* The commit-message subject the deadline-checkpoint save writes, and the
|
|
80
|
+
* pattern used to COUNT consecutive checkpoints on the branch (spec
|
|
81
|
+
* `graceful-pre-timeout-wip-checkpoint` — the anti-loop ceiling). Living
|
|
82
|
+
* on-branch means the counter naturally RESETS when the item completes (the
|
|
83
|
+
* branch is discarded on integration) and when a `--reset` requeue discards
|
|
84
|
+
* the branch: a genuinely-progressing task cannot silently accumulate a
|
|
85
|
+
* ceiling breach across generations.
|
|
86
|
+
*/
|
|
87
|
+
const DEADLINE_CHECKPOINT_COMMIT_SUBJECT_PREFIX = 'chore(deadline-checkpoint)';
|
|
88
|
+
/**
|
|
89
|
+
* Distinct reason strings for the two deadline-checkpoint branches (spec
|
|
90
|
+
* `graceful-pre-timeout-wip-checkpoint` — the CI/human report must NEVER
|
|
91
|
+
* collapse to a generic "agent-failed"). Auto-continue and surface carry
|
|
92
|
+
* different phrasing so the counter + ceiling are legible.
|
|
93
|
+
*/
|
|
94
|
+
function deadlineAutoContinueReason(params) {
|
|
95
|
+
return (`deadline checkpoint (auto-continued ${params.count}/${params.max}): ` +
|
|
96
|
+
`'${params.slug}' hit the dorfl-internal deadline with new work-branch ` +
|
|
97
|
+
`progress this session; saved the WIP and released the lock so the next ` +
|
|
98
|
+
`tick continues from the branch tip.`);
|
|
99
|
+
}
|
|
100
|
+
function deadlineSurfaceReason(params) {
|
|
101
|
+
if (params.kind === 'no-progress') {
|
|
102
|
+
return (`deadline checkpoint (no progress / ceiling): '${params.slug}' hit ` +
|
|
103
|
+
`the dorfl-internal deadline WITHOUT any work-branch progress this ` +
|
|
104
|
+
`session — indistinguishable from a wedge; surfaced for a human to ` +
|
|
105
|
+
`decide (continue / split / cancel).`);
|
|
106
|
+
}
|
|
107
|
+
return (`deadline checkpoint (no progress / ceiling): '${params.slug}' has hit ` +
|
|
108
|
+
`the dorfl-internal deadline ${params.count}/${params.max} times ` +
|
|
109
|
+
`consecutively — it may be too big for one CI leg. Split it or run ` +
|
|
110
|
+
`it locally; surfaced for a human to decide.`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Count how many DEADLINE-CHECKPOINT commits (subject prefix
|
|
114
|
+
* `chore(deadline-checkpoint)`) currently sit on the work branch ahead of
|
|
115
|
+
* `<arbiter>/main` — the branch-based anti-loop counter (spec
|
|
116
|
+
* `graceful-pre-timeout-wip-checkpoint`). A best-effort count: an unreachable
|
|
117
|
+
* arbiter / a bare mirror reads as 0 (the SAFE direction — never falsely block
|
|
118
|
+
* an auto-continue) so a genuine plumbing fault degrades to "under the ceiling"
|
|
119
|
+
* rather than surfacing a spurious ceiling breach. Resets naturally when the
|
|
120
|
+
* item completes (the branch is discarded on integration).
|
|
121
|
+
*/
|
|
122
|
+
async function countDeadlineCheckpointsOnBranch(params) {
|
|
123
|
+
const { cwd, arbiter, branch, env } = params;
|
|
124
|
+
const range = `${arbiter}/main..HEAD`;
|
|
125
|
+
void branch;
|
|
126
|
+
const r = await runAsync('git', ['log', '--format=%s', range], cwd, { env });
|
|
127
|
+
if (r.status !== 0) {
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
const lines = r.stdout.split('\n').filter((s) => s.length > 0);
|
|
131
|
+
return lines.filter((s) => s.startsWith(DEADLINE_CHECKPOINT_COMMIT_SUBJECT_PREFIX)).length;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* SAVE the WIP on a deadline stop (spec `graceful-pre-timeout-wip-checkpoint`,
|
|
135
|
+
* build step 3): commit the tree as-is + push `work/<slug>`. Reuses the SAVE
|
|
136
|
+
* HALF of the needs-attention mechanism ({@link routeToNeedsAttention}) but
|
|
137
|
+
* ADDS a distinguishing checkpoint-marker commit on top so
|
|
138
|
+
* {@link countDeadlineCheckpointsOnBranch} can count auto-continues. Distinct
|
|
139
|
+
* from `applyNeedsAttentionTransition` (the whole thing) which ALSO marks the
|
|
140
|
+
* lock stuck — auto-continue must NOT mark stuck; only the surface branch does.
|
|
141
|
+
*/
|
|
142
|
+
async function saveDeadlineCheckpoint(params) {
|
|
143
|
+
const { slug, branch, cwd, arbiter, reason, env, note } = params;
|
|
144
|
+
void reason;
|
|
145
|
+
// Save any uncommitted work first (the routeToNeedsAttention save half also
|
|
146
|
+
// commits + pushes; but we ALSO want a distinct checkpoint-marker commit so
|
|
147
|
+
// countDeadlineCheckpointsOnBranch can enumerate them). Stage everything and
|
|
148
|
+
// commit a MARKER commit if there's anything to record OR if there is not
|
|
149
|
+
// already a marker on the tip — the marker's job is to make the checkpoint
|
|
150
|
+
// COUNTABLE, not to represent new work.
|
|
151
|
+
const addResult = await runAsync('git', ['add', '-A'], cwd, { env });
|
|
152
|
+
void addResult;
|
|
153
|
+
const diffCached = await runAsync('git', ['diff', '--cached', '--quiet'], cwd, { env });
|
|
154
|
+
const hasStaged = diffCached.status !== 0;
|
|
155
|
+
if (hasStaged) {
|
|
156
|
+
await runAsync('git', [
|
|
157
|
+
'commit',
|
|
158
|
+
'-q',
|
|
159
|
+
'-m',
|
|
160
|
+
`${DEADLINE_CHECKPOINT_COMMIT_SUBJECT_PREFIX}: save wip for '${slug}'`,
|
|
161
|
+
], cwd, { env });
|
|
162
|
+
}
|
|
163
|
+
// Push the branch to the arbiter (best-effort). Reuse routeToNeedsAttention's
|
|
164
|
+
// save-half exact contract: it commits any residue + pushes the branch with
|
|
165
|
+
// bounded backoff. We already committed above; the wip commit inside
|
|
166
|
+
// routeToNeedsAttention will be a no-op (clean tree) and the push runs.
|
|
167
|
+
return routeToNeedsAttention({
|
|
168
|
+
cwd,
|
|
169
|
+
slug,
|
|
170
|
+
reason: `deadline-checkpoint save for '${slug}' (see branch)`,
|
|
171
|
+
arbiter,
|
|
172
|
+
branch,
|
|
173
|
+
note,
|
|
174
|
+
env,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
26
177
|
const DEFAULT_ARBITER = 'origin';
|
|
27
178
|
/**
|
|
28
179
|
* Map a `do spec:<slug>` {@link TaskResult} onto the `do` {@link DoResult}
|
|
@@ -176,6 +327,11 @@ export async function performDo(options) {
|
|
|
176
327
|
// `integration:'propose'` + `taskingIntegration:'merge'` lands the task FILES
|
|
177
328
|
// on main here while the BUILD path below still threads plain `integration`.
|
|
178
329
|
integration: options.taskingIntegration ?? options.integration,
|
|
330
|
+
// The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) —
|
|
331
|
+
// threaded so the `do spec:` tasking-transition's land tail respects the
|
|
332
|
+
// same per-repo cap the build path uses (task
|
|
333
|
+
// `thread-merge-retries-cross-task-and-ratify-default`).
|
|
334
|
+
mergeRetries: options.mergeRetries,
|
|
179
335
|
// The per-repo TASK-PLACEMENT default + the operator's explicit
|
|
180
336
|
// override (task `runner-deterministic-slice-placement-policy-and-
|
|
181
337
|
// precedence`). The tasker reads them as the configured-default + the
|
|
@@ -362,14 +518,17 @@ export async function performDo(options) {
|
|
|
362
518
|
// auto-resolved). Surface to needs-attention TREE-LESSLY via the SAME `#89`
|
|
363
519
|
// mechanism `requeue` uses for the reverse direction — the rebase was
|
|
364
520
|
// ABORTED, so the kept `work/<slug>` tip == the arbiter tip (already on the
|
|
365
|
-
// arbiter, after-commit, recoverable). The surface is purely the
|
|
366
|
-
// `
|
|
367
|
-
// worktree mutation) instead of running the agent — the §10
|
|
521
|
+
// arbiter, after-commit, recoverable). The surface is purely the lock amend
|
|
522
|
+
// to `state: stuck` with the reason (post lock-cutover — no folder move, no
|
|
523
|
+
// branch push, no worktree mutation) instead of running the agent — the §10
|
|
524
|
+
// path. The work
|
|
368
525
|
// did NOT onboard; the runner owns the bounce.
|
|
369
526
|
if (tree.continueRebaseConflict) {
|
|
370
527
|
const reason = `continuing the kept ${tree.branch}: rebase onto the latest main ` +
|
|
371
|
-
'conflicted (aborted, never auto-resolved) —
|
|
372
|
-
'
|
|
528
|
+
'conflicted (aborted, never auto-resolved) — run `requeue --reconcile` ' +
|
|
529
|
+
'to non-destructively re-sync the mirror and retry the rebase (keeps ' +
|
|
530
|
+
'the work). Last resort: `requeue --reset` DESTRUCTIVELY discards the ' +
|
|
531
|
+
'branch and starts fresh.';
|
|
373
532
|
const surfaced = await ledgerWrite.applyTreelessNeedsAttentionTransition({
|
|
374
533
|
cwd: tree.dir,
|
|
375
534
|
slug,
|
|
@@ -401,8 +560,10 @@ export async function performDo(options) {
|
|
|
401
560
|
if (tree.continuePushFailure !== undefined) {
|
|
402
561
|
const reason = `continuing the kept ${tree.branch}: publishing the rebased work branch ` +
|
|
403
562
|
`to the arbiter failed terminally (${tree.continuePushFailure}) — the kept ` +
|
|
404
|
-
'branch is left intact on the arbiter (recoverable); `requeue`
|
|
405
|
-
'
|
|
563
|
+
'branch is left intact on the arbiter (recoverable); `requeue --reconcile` ' +
|
|
564
|
+
'to non-destructively re-sync the mirror and retry once the churn ' +
|
|
565
|
+
'settles (keeps the work). Last resort: `requeue --reset` DESTRUCTIVELY ' +
|
|
566
|
+
'discards the branch and starts fresh.';
|
|
406
567
|
const surfaced = await ledgerWrite.applyTreelessNeedsAttentionTransition({
|
|
407
568
|
cwd: tree.dir,
|
|
408
569
|
slug,
|
|
@@ -502,6 +663,23 @@ export async function performDo(options) {
|
|
|
502
663
|
note,
|
|
503
664
|
});
|
|
504
665
|
}
|
|
666
|
+
// DEADLINE CHECKPOINT (spec `graceful-pre-timeout-wip-checkpoint`): the
|
|
667
|
+
// dorfl-internal deadline fired — stop is graceful, NOT a failure. Route
|
|
668
|
+
// through the shared checkpoint path (save WIP + auto-continue on progress
|
|
669
|
+
// under the ceiling, else surface). Distinct from `saveAgentFailure` on
|
|
670
|
+
// purpose — the deadline path must NOT mark the item stuck when it
|
|
671
|
+
// auto-continues.
|
|
672
|
+
if (agent.timedOut) {
|
|
673
|
+
return await routeDeadlineCheckpoint({
|
|
674
|
+
slug,
|
|
675
|
+
branch,
|
|
676
|
+
cwd: tree.dir,
|
|
677
|
+
arbiter: tree.arbiterRemote,
|
|
678
|
+
maxAutoCheckpoints: options.maxAutoCheckpoints ?? 5,
|
|
679
|
+
env,
|
|
680
|
+
note,
|
|
681
|
+
});
|
|
682
|
+
}
|
|
505
683
|
if (!agent.ok) {
|
|
506
684
|
const detail = agent.detail ?? `the agent failed to build '${slug}'.`;
|
|
507
685
|
return await saveAgentFailure({
|
|
@@ -535,7 +713,8 @@ export async function performDo(options) {
|
|
|
535
713
|
branch,
|
|
536
714
|
cwd: tree.dir,
|
|
537
715
|
arbiter: tree.arbiterRemote,
|
|
538
|
-
reason: stopReason,
|
|
716
|
+
reason: stopReason.reason,
|
|
717
|
+
kind: stopReason.kind,
|
|
539
718
|
env,
|
|
540
719
|
note,
|
|
541
720
|
});
|
|
@@ -682,8 +861,10 @@ export async function performDo(options) {
|
|
|
682
861
|
// (`runRemotePipeline`) uses, so `advance task:<slug>` (via the default
|
|
683
862
|
// `doDriver = performDo`) agrees with the remote path on the caller-visible
|
|
684
863
|
// label.
|
|
864
|
+
//
|
|
865
|
+
// PR-2b D3: a clean surface (routedToNeedsAttention:true) is GREEN.
|
|
685
866
|
return {
|
|
686
|
-
exitCode: 1,
|
|
867
|
+
exitCode: completed.routedToNeedsAttention ? 0 : 1,
|
|
687
868
|
outcome: 'needs-attention',
|
|
688
869
|
slug,
|
|
689
870
|
branch,
|
|
@@ -770,7 +951,9 @@ function routeReport(routed, branch) {
|
|
|
770
951
|
// so loudly, the work is saved LOCALLY only, and how to recover.
|
|
771
952
|
return {
|
|
772
953
|
fragment: `push of ${branch} FAILED — the work is saved LOCALLY only; push it ` +
|
|
773
|
-
'when online, then `requeue` (continue)
|
|
954
|
+
'when online, then `requeue` (continue) or `requeue --reconcile` ' +
|
|
955
|
+
'(non-destructive retry). Last resort: `requeue --reset` ' +
|
|
956
|
+
'DESTRUCTIVELY discards.',
|
|
774
957
|
pushFailed: true,
|
|
775
958
|
};
|
|
776
959
|
}
|
|
@@ -787,7 +970,9 @@ function routeReport(routed, branch) {
|
|
|
787
970
|
landed = 'saved locally';
|
|
788
971
|
}
|
|
789
972
|
return {
|
|
790
|
-
fragment: `${landed}. Recover via \`requeue\` (continue) or \`requeue --
|
|
973
|
+
fragment: `${landed}. Recover via \`requeue\` (continue) or \`requeue --reconcile\` ` +
|
|
974
|
+
'(non-destructive retry). Last resort: `requeue --reset` ' +
|
|
975
|
+
'DESTRUCTIVELY discards.',
|
|
791
976
|
pushFailed: false,
|
|
792
977
|
};
|
|
793
978
|
}
|
|
@@ -800,10 +985,12 @@ function routeReport(routed, branch) {
|
|
|
800
985
|
*
|
|
801
986
|
* This routes it through the SAME work-preserving machinery a RED GATE uses: the
|
|
802
987
|
* ledger write seam's needs-attention transition (`git add -A` + a wip commit
|
|
803
|
-
* capturing the agent's work
|
|
804
|
-
* with the failure detail
|
|
805
|
-
*
|
|
806
|
-
*
|
|
988
|
+
* capturing the agent's work, then — post lock-cutover — a LOCK AMEND to
|
|
989
|
+
* `state: stuck` with the failure detail as the reason on the lock entry, in
|
|
990
|
+
* place of the retired `git mv → needs-attention/` move-only commit), surfaced on
|
|
991
|
+
* the arbiter's `main` (the autonomous, cross-machine-visible mode-M surfacing
|
|
992
|
+
* `do` already uses for the gate-fail path) so `scan`/`status`/another machine
|
|
993
|
+
* see it.
|
|
807
994
|
*
|
|
808
995
|
* It ALSO pushes the `work/<slug>` branch to the arbiter so the saved partial
|
|
809
996
|
* commits travel cross-machine and the item is RECOVERABLE via `requeue`
|
|
@@ -826,7 +1013,8 @@ function routeReport(routed, branch) {
|
|
|
826
1013
|
* contract stays coherent). Only the WORK-PRESERVING side-effect (unchanged here)
|
|
827
1014
|
* matches the gate-failure path. We do NOT validate or "fix" the partial work — a broken
|
|
828
1015
|
* tree committed + surfaced (with the reason) is recoverable; the human chooses
|
|
829
|
-
* `requeue` (continue)
|
|
1016
|
+
* `requeue` (continue) / `requeue --reconcile` (non-destructive retry) vs
|
|
1017
|
+
* `requeue --reset` (destructive last resort).
|
|
830
1018
|
*/
|
|
831
1019
|
async function saveAgentFailure(params) {
|
|
832
1020
|
const { slug, cwd, arbiter, detail, env, note } = params;
|
|
@@ -864,8 +1052,10 @@ async function saveAgentFailure(params) {
|
|
|
864
1052
|
: `Agent run failed building '${slug}' [${cause}] (${detail}); could not ` +
|
|
865
1053
|
`mark '${slug}' stuck (${routed.reasonNotMoved ?? 'unknown'}).`;
|
|
866
1054
|
note(message);
|
|
1055
|
+
// PR-2b D3: a cleanly-surfaced bounce is GREEN (exitCode 0); a FAILED surface
|
|
1056
|
+
// stays non-zero.
|
|
867
1057
|
return {
|
|
868
|
-
exitCode: 1,
|
|
1058
|
+
exitCode: routed.moved ? 0 : 1,
|
|
869
1059
|
outcome: failureCauseToDoOutcome(cause),
|
|
870
1060
|
slug,
|
|
871
1061
|
branch,
|
|
@@ -884,7 +1074,9 @@ function failureCauseToDoOutcome(cause) {
|
|
|
884
1074
|
}
|
|
885
1075
|
/**
|
|
886
1076
|
* Build the HONEST result for a CONTINUE-site surface that did NOT land on the
|
|
887
|
-
* arbiter (`{moved: false}`). The tree-less
|
|
1077
|
+
* arbiter (`{moved: false}`). The tree-less stuck-lock amend (post lock-cutover —
|
|
1078
|
+
* `state: stuck` with the reason, in place of the retired `in-progress/ →
|
|
1079
|
+
* needs-attention/` move)
|
|
888
1080
|
* lost the CAS race against a busy arbiter (its contention-retry cap exhausted) or
|
|
889
1081
|
* had no arbiter to publish to, so the item is STILL in-progress on the arbiter —
|
|
890
1082
|
* a clean `needs-attention` would mislead (it claims the surface landed). Distinct
|
|
@@ -920,10 +1112,10 @@ async function resolveStopReason(params) {
|
|
|
920
1112
|
const { output, slug, cwd, arbiter, env } = params;
|
|
921
1113
|
const sentinel = parseStopSentinel(output);
|
|
922
1114
|
if (sentinel !== undefined) {
|
|
923
|
-
return sentinel.reason;
|
|
1115
|
+
return { kind: 'sentinel', reason: sentinel.reason };
|
|
924
1116
|
}
|
|
925
1117
|
if (await isWorkBranchDiffEmpty({ cwd, arbiter, env })) {
|
|
926
|
-
return emptyDiffStopReason(slug);
|
|
1118
|
+
return { kind: 'empty-diff', reason: emptyDiffStopReason(slug) };
|
|
927
1119
|
}
|
|
928
1120
|
return undefined;
|
|
929
1121
|
}
|
|
@@ -936,8 +1128,59 @@ async function resolveStopReason(params) {
|
|
|
936
1128
|
* needs-attention reason. The acceptance gate AND Gate-2 are NEVER reached.
|
|
937
1129
|
*/
|
|
938
1130
|
async function saveAgentStop(params) {
|
|
939
|
-
const { slug, cwd, arbiter, reason, env, note } = params;
|
|
1131
|
+
const { slug, cwd, arbiter, reason, kind, env, note } = params;
|
|
940
1132
|
const branch = params.branch ?? workBranchRef('task', slug);
|
|
1133
|
+
// EMPTY-DIFF branch (spec resolved decision #2, task
|
|
1134
|
+
// `empty-diff-bounce-surfaces-dispose-defaulted-question`): surface a sidecar
|
|
1135
|
+
// on `<arbiter>/main` carrying an engine-authored DISPOSE-DEFAULTED envelope
|
|
1136
|
+
// question, THEN release the lock. "Nothing to do" is a non-deterministic LLM
|
|
1137
|
+
// judgement, so this seam MUST NOT blindly requeue (that would infinite-loop);
|
|
1138
|
+
// the surfaced sidecar + `needsAnswers:true` on `main` parks the item as a
|
|
1139
|
+
// human-visible question a `dispose` answer settles to `tasks/cancelled/`.
|
|
1140
|
+
if (kind === 'empty-diff') {
|
|
1141
|
+
const item = `task:${slug}`;
|
|
1142
|
+
const itemPath = resolveItemPathByIdentity(cwd, item);
|
|
1143
|
+
if (itemPath === undefined) {
|
|
1144
|
+
const message = `The agent STOPPED building '${slug}' (empty diff) but its body could ` +
|
|
1145
|
+
`not be located on the arbiter to surface a sidecar. Reason: ${reason}`;
|
|
1146
|
+
note(message);
|
|
1147
|
+
return {
|
|
1148
|
+
exitCode: 1,
|
|
1149
|
+
outcome: 'agent-stopped',
|
|
1150
|
+
slug,
|
|
1151
|
+
branch,
|
|
1152
|
+
routedToNeedsAttention: false,
|
|
1153
|
+
message,
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
const surfaced = await surfaceStuckToNeedsAttention({
|
|
1157
|
+
cwd,
|
|
1158
|
+
slug,
|
|
1159
|
+
item,
|
|
1160
|
+
itemPath,
|
|
1161
|
+
reason,
|
|
1162
|
+
envelope: emptyDiffDisposeEnvelope({ item, reason }),
|
|
1163
|
+
arbiter,
|
|
1164
|
+
env,
|
|
1165
|
+
note,
|
|
1166
|
+
});
|
|
1167
|
+
const message = surfaced.surfaced
|
|
1168
|
+
? `The agent STOPPED building '${slug}' (empty diff); surfaced a ` +
|
|
1169
|
+
`dispose-defaulted question on ${arbiter}/main and released the lock. ` +
|
|
1170
|
+
`Reason: ${reason}`
|
|
1171
|
+
: `The agent STOPPED building '${slug}' (empty diff) but the surface ` +
|
|
1172
|
+
`did not land (${surfaced.reasonNotSurfaced ?? 'unknown'}). ` +
|
|
1173
|
+
`Reason: ${reason}`;
|
|
1174
|
+
note(message);
|
|
1175
|
+
return {
|
|
1176
|
+
exitCode: 1,
|
|
1177
|
+
outcome: 'agent-stopped',
|
|
1178
|
+
slug,
|
|
1179
|
+
branch,
|
|
1180
|
+
routedToNeedsAttention: surfaced.surfaced,
|
|
1181
|
+
message,
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
941
1184
|
const routed = await ledgerWrite.applyNeedsAttentionTransition({
|
|
942
1185
|
cwd,
|
|
943
1186
|
slug,
|
|
@@ -954,8 +1197,11 @@ async function saveAgentStop(params) {
|
|
|
954
1197
|
: `The agent STOPPED building '${slug}' but it could not be marked stuck ` +
|
|
955
1198
|
`(${routed.reasonNotMoved ?? 'unknown'}). Reason: ${reason}`;
|
|
956
1199
|
note(message);
|
|
1200
|
+
// PR-2b D3: agent-stopped (sentinel) on a clean surface is GREEN (exitCode 0);
|
|
1201
|
+
// a FAILED surface stays non-zero. The empty-diff branch above is UNCHANGED
|
|
1202
|
+
// (owned by `empty-diff-bounce-surfaces-dispose-defaulted-question`).
|
|
957
1203
|
return {
|
|
958
|
-
exitCode: 1,
|
|
1204
|
+
exitCode: routed.moved ? 0 : 1,
|
|
959
1205
|
outcome: 'agent-stopped',
|
|
960
1206
|
slug,
|
|
961
1207
|
branch,
|
|
@@ -986,6 +1232,15 @@ async function runDoAgent(options, cwd, prompt, slug) {
|
|
|
986
1232
|
return options.dorfl({ cwd, prompt, slug, env: options.env });
|
|
987
1233
|
}
|
|
988
1234
|
const harness = options.harness ?? new NullHarness();
|
|
1235
|
+
// Convert the dorfl-internal deadline (minutes) into a wall-clock epoch-ms so
|
|
1236
|
+
// the harness (`launchAsync`) can race the child against it (spec
|
|
1237
|
+
// `graceful-pre-timeout-wip-checkpoint`). Absent ⇒ no deadline; a run that
|
|
1238
|
+
// finishes before it is byte-for-byte unchanged (the harness clears the
|
|
1239
|
+
// timer on normal exit). Forcing the async path when a deadline is set is
|
|
1240
|
+
// {@link launchWithOptionalWatch}'s job.
|
|
1241
|
+
const deadlineMs = options.agentDeadlineMinutes !== undefined
|
|
1242
|
+
? Date.now() + options.agentDeadlineMinutes * 60_000
|
|
1243
|
+
: undefined;
|
|
989
1244
|
const launched = await launchWithOptionalWatch({
|
|
990
1245
|
harness,
|
|
991
1246
|
dir: cwd,
|
|
@@ -997,6 +1252,7 @@ async function runDoAgent(options, cwd, prompt, slug) {
|
|
|
997
1252
|
sessionId: slug,
|
|
998
1253
|
sessionsDir: options.sessionsDir,
|
|
999
1254
|
watch: options.watch,
|
|
1255
|
+
deadlineMs,
|
|
1000
1256
|
watchSink: options.watchSink,
|
|
1001
1257
|
color: options.color,
|
|
1002
1258
|
env: options.env,
|
|
@@ -1004,7 +1260,115 @@ async function runDoAgent(options, cwd, prompt, slug) {
|
|
|
1004
1260
|
// Surface the agent's FINAL SUMMARY (`LaunchResult.output`) — the source channel
|
|
1005
1261
|
// for the propose-mode PR body — instead of dropping it. Absent (no parseable
|
|
1006
1262
|
// assistant text) ⇒ undefined ⇒ the body degrades to `--fill` (no regression).
|
|
1007
|
-
return {
|
|
1263
|
+
return {
|
|
1264
|
+
ok: launched.ok,
|
|
1265
|
+
detail: launched.detail,
|
|
1266
|
+
output: launched.output,
|
|
1267
|
+
timedOut: launched.timedOut,
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Route a DEADLINE STOP (spec `graceful-pre-timeout-wip-checkpoint`) through
|
|
1272
|
+
* the checkpoint machinery: ALWAYS save the WIP (commit + push), then
|
|
1273
|
+
* decide — progress AND under ceiling ⇒ AUTO-CONTINUE (release lock, keep
|
|
1274
|
+
* branch, NO sidecar, exit 0); else ⇒ SURFACE a `needsAnswers:true` question.
|
|
1275
|
+
* Shared by {@link performDo} and {@link runRemotePipeline} so the in-place
|
|
1276
|
+
* and remote forms route a deadline stop identically.
|
|
1277
|
+
*/
|
|
1278
|
+
async function routeDeadlineCheckpoint(params) {
|
|
1279
|
+
const { slug, branch, cwd, arbiter, maxAutoCheckpoints, env, note } = params;
|
|
1280
|
+
// 1. ALWAYS save the WIP first: commit any residue + push the work branch.
|
|
1281
|
+
const savedReason = `deadline checkpoint save for '${slug}'`;
|
|
1282
|
+
const saved = await saveDeadlineCheckpoint({
|
|
1283
|
+
slug,
|
|
1284
|
+
branch,
|
|
1285
|
+
cwd,
|
|
1286
|
+
arbiter,
|
|
1287
|
+
reason: savedReason,
|
|
1288
|
+
env,
|
|
1289
|
+
note,
|
|
1290
|
+
});
|
|
1291
|
+
void saved;
|
|
1292
|
+
// 2. Progress this session? Reuse the SAME primitive the STOP backstop uses.
|
|
1293
|
+
const emptyDiff = await isWorkBranchDiffEmpty({ cwd, arbiter, env });
|
|
1294
|
+
const madeProgressThisSession = !emptyDiff;
|
|
1295
|
+
// 3. Count consecutive deadline checkpoints on the branch (ceiling gate). The
|
|
1296
|
+
// marker commit we just saved is included — so the count is the number
|
|
1297
|
+
// of checkpoints INCLUDING this one.
|
|
1298
|
+
const checkpointCount = await countDeadlineCheckpointsOnBranch({
|
|
1299
|
+
cwd,
|
|
1300
|
+
arbiter,
|
|
1301
|
+
branch,
|
|
1302
|
+
env,
|
|
1303
|
+
});
|
|
1304
|
+
if (madeProgressThisSession && checkpointCount <= maxAutoCheckpoints) {
|
|
1305
|
+
// AUTO-CONTINUE: release the lock via the SAME default keep+continue path
|
|
1306
|
+
// `requeue` uses (no --reset, no --reconcile, no sidecar). The branch is
|
|
1307
|
+
// KEPT on the arbiter so the next claim continues from its tip.
|
|
1308
|
+
const reason = deadlineAutoContinueReason({
|
|
1309
|
+
slug,
|
|
1310
|
+
count: checkpointCount,
|
|
1311
|
+
max: maxAutoCheckpoints,
|
|
1312
|
+
});
|
|
1313
|
+
const returned = await ledgerWrite.applyReturnToBacklogTransition({
|
|
1314
|
+
cwd,
|
|
1315
|
+
slug,
|
|
1316
|
+
arbiter,
|
|
1317
|
+
env,
|
|
1318
|
+
note,
|
|
1319
|
+
});
|
|
1320
|
+
if (returned.moved) {
|
|
1321
|
+
const message = `Auto-continued '${slug}' at the dorfl-internal deadline (checkpoint ` +
|
|
1322
|
+
`${checkpointCount}/${maxAutoCheckpoints}): WIP saved + branch pushed, ` +
|
|
1323
|
+
`lock released so the next tick continues from ${branch}. ${reason}`;
|
|
1324
|
+
note(message);
|
|
1325
|
+
return {
|
|
1326
|
+
exitCode: 0,
|
|
1327
|
+
outcome: 'deadline-auto-continued',
|
|
1328
|
+
slug,
|
|
1329
|
+
branch,
|
|
1330
|
+
message,
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
// The auto-continue lock release did not land — fall through to SURFACE.
|
|
1334
|
+
note(`Auto-continue lock release for '${slug}' did not land ` +
|
|
1335
|
+
`(${returned.reasonNotMoved ?? 'unknown'}); surfacing instead.`);
|
|
1336
|
+
}
|
|
1337
|
+
// SURFACE: mark the lock stuck via the whole applyNeedsAttentionTransition
|
|
1338
|
+
// (save + push + stuck). The WIP was already saved above; a second save is
|
|
1339
|
+
// idempotent (empty commit is skipped inside routeToNeedsAttention).
|
|
1340
|
+
const surfaceReason = madeProgressThisSession
|
|
1341
|
+
? deadlineSurfaceReason({
|
|
1342
|
+
slug,
|
|
1343
|
+
kind: 'ceiling',
|
|
1344
|
+
count: checkpointCount,
|
|
1345
|
+
max: maxAutoCheckpoints,
|
|
1346
|
+
})
|
|
1347
|
+
: deadlineSurfaceReason({ slug, kind: 'no-progress' });
|
|
1348
|
+
const routed = await ledgerWrite.applyNeedsAttentionTransition({
|
|
1349
|
+
cwd,
|
|
1350
|
+
slug,
|
|
1351
|
+
reason: surfaceReason,
|
|
1352
|
+
arbiter,
|
|
1353
|
+
env,
|
|
1354
|
+
note,
|
|
1355
|
+
});
|
|
1356
|
+
const report = routed.moved ? routeReport(routed, branch) : undefined;
|
|
1357
|
+
const message = routed.moved
|
|
1358
|
+
? `Surfaced '${slug}' at the deadline checkpoint (${madeProgressThisSession
|
|
1359
|
+
? `ceiling ${checkpointCount}/${maxAutoCheckpoints}`
|
|
1360
|
+
: 'no progress this session'}); ${report.fragment}. ${surfaceReason}`
|
|
1361
|
+
: `Could not surface '${slug}' at the deadline checkpoint ` +
|
|
1362
|
+
`(${routed.reasonNotMoved ?? 'unknown'}). ${surfaceReason}`;
|
|
1363
|
+
note(message);
|
|
1364
|
+
return {
|
|
1365
|
+
exitCode: 1,
|
|
1366
|
+
outcome: 'deadline-surfaced',
|
|
1367
|
+
slug,
|
|
1368
|
+
branch,
|
|
1369
|
+
routedToNeedsAttention: routed.moved,
|
|
1370
|
+
message,
|
|
1371
|
+
};
|
|
1008
1372
|
}
|
|
1009
1373
|
/**
|
|
1010
1374
|
* True when the working tree has uncommitted (unstaged OR staged) changes — the
|
|
@@ -1247,6 +1611,10 @@ export async function performDoRemote(options) {
|
|
|
1247
1611
|
// `taskingIntegration ?? integration`, so the `--remote spec:` output ALSO
|
|
1248
1612
|
// routes through the shared core with the tasking-resolved mode.
|
|
1249
1613
|
integration: options.taskingIntegration ?? options.integration,
|
|
1614
|
+
// The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) —
|
|
1615
|
+
// threaded on the no-checkout `do --remote spec:` tasking path too,
|
|
1616
|
+
// mirroring the in-place site above.
|
|
1617
|
+
mergeRetries: options.mergeRetries,
|
|
1250
1618
|
// The per-repo TASK-PLACEMENT default + the operator's explicit
|
|
1251
1619
|
// override (task `runner-deterministic-slice-placement-policy-and-
|
|
1252
1620
|
// precedence`). Same threading as the in-place `do spec:` path.
|
|
@@ -1402,8 +1770,10 @@ async function runRemotePipeline(options, tree, slug, note, env) {
|
|
|
1402
1770
|
// agent — the §10 path `run` uses.
|
|
1403
1771
|
if (tree.continueRebaseConflict) {
|
|
1404
1772
|
const reason = `continuing the kept ${tree.branch}: rebase onto the latest main ` +
|
|
1405
|
-
'conflicted (aborted, never auto-resolved) —
|
|
1406
|
-
'
|
|
1773
|
+
'conflicted (aborted, never auto-resolved) — run `requeue --reconcile` ' +
|
|
1774
|
+
'to non-destructively re-sync the mirror and retry the rebase (keeps ' +
|
|
1775
|
+
'the work). Last resort: `requeue --reset` DESTRUCTIVELY discards the ' +
|
|
1776
|
+
'branch and starts fresh.';
|
|
1407
1777
|
await ledgerWrite.applyNeedsAttentionTransition({
|
|
1408
1778
|
cwd,
|
|
1409
1779
|
slug,
|
|
@@ -1433,8 +1803,10 @@ async function runRemotePipeline(options, tree, slug, note, env) {
|
|
|
1433
1803
|
if (tree.continuePushFailure !== undefined) {
|
|
1434
1804
|
const reason = `continuing the kept ${tree.branch}: publishing the rebased work branch ` +
|
|
1435
1805
|
`to the arbiter failed terminally (${tree.continuePushFailure}) — the kept ` +
|
|
1436
|
-
'branch is left intact on the arbiter (recoverable); `requeue`
|
|
1437
|
-
'
|
|
1806
|
+
'branch is left intact on the arbiter (recoverable); `requeue --reconcile` ' +
|
|
1807
|
+
'to non-destructively re-sync the mirror and retry once the churn ' +
|
|
1808
|
+
'settles (keeps the work). Last resort: `requeue --reset` DESTRUCTIVELY ' +
|
|
1809
|
+
'discards the branch and starts fresh.';
|
|
1438
1810
|
await ledgerWrite.applyNeedsAttentionTransition({
|
|
1439
1811
|
cwd,
|
|
1440
1812
|
slug,
|
|
@@ -1574,6 +1946,19 @@ async function runRemotePipeline(options, tree, slug, note, env) {
|
|
|
1574
1946
|
note,
|
|
1575
1947
|
});
|
|
1576
1948
|
}
|
|
1949
|
+
// Deadline checkpoint on the no-checkout `do --remote` path — same routing
|
|
1950
|
+
// as the in-place path (see performDo).
|
|
1951
|
+
if (agent.timedOut) {
|
|
1952
|
+
return await routeDeadlineCheckpoint({
|
|
1953
|
+
slug,
|
|
1954
|
+
branch: branch ?? workBranchRef('task', slug),
|
|
1955
|
+
cwd,
|
|
1956
|
+
arbiter: arbiterRemote,
|
|
1957
|
+
maxAutoCheckpoints: options.maxAutoCheckpoints ?? 5,
|
|
1958
|
+
env,
|
|
1959
|
+
note,
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1577
1962
|
if (!agent.ok) {
|
|
1578
1963
|
const detail = agent.detail ?? `the agent failed to build '${slug}'.`;
|
|
1579
1964
|
return await saveRemoteAgentFailure({
|
|
@@ -1604,7 +1989,8 @@ async function runRemotePipeline(options, tree, slug, note, env) {
|
|
|
1604
1989
|
branch,
|
|
1605
1990
|
cwd,
|
|
1606
1991
|
arbiter: arbiterRemote,
|
|
1607
|
-
reason: stopReason,
|
|
1992
|
+
reason: stopReason.reason,
|
|
1993
|
+
kind: stopReason.kind,
|
|
1608
1994
|
env,
|
|
1609
1995
|
note,
|
|
1610
1996
|
});
|
|
@@ -1775,8 +2161,9 @@ async function saveRemoteAgentFailure(params) {
|
|
|
1775
2161
|
: `Agent run failed building '${slug}' [${cause}] (${detail}); could not ` +
|
|
1776
2162
|
`mark '${slug}' stuck (${routed.reasonNotMoved ?? 'unknown'}).`;
|
|
1777
2163
|
note(message);
|
|
2164
|
+
// PR-2b D3: cleanly-surfaced remote agent failure is GREEN.
|
|
1778
2165
|
return {
|
|
1779
|
-
exitCode: 1,
|
|
2166
|
+
exitCode: routed.moved ? 0 : 1,
|
|
1780
2167
|
outcome: failureCauseToDoOutcome(cause),
|
|
1781
2168
|
slug,
|
|
1782
2169
|
branch,
|
|
@@ -1871,6 +2258,8 @@ export function jobWorktreeDoDriver(closure) {
|
|
|
1871
2258
|
watch: options.watch,
|
|
1872
2259
|
watchSink: options.watchSink,
|
|
1873
2260
|
color: options.color,
|
|
2261
|
+
agentDeadlineMinutes: options.agentDeadlineMinutes,
|
|
2262
|
+
maxAutoCheckpoints: options.maxAutoCheckpoints,
|
|
1874
2263
|
read: options.read,
|
|
1875
2264
|
env: options.env,
|
|
1876
2265
|
note: options.note,
|