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/src/do.ts
CHANGED
|
@@ -59,7 +59,14 @@ import {
|
|
|
59
59
|
parseStopSentinel,
|
|
60
60
|
isWorkBranchDiffEmpty,
|
|
61
61
|
emptyDiffStopReason,
|
|
62
|
+
emptyDiffDisposeEnvelope,
|
|
63
|
+
type AgentStopClass,
|
|
62
64
|
} from './agent-stop.js';
|
|
65
|
+
import {
|
|
66
|
+
surfaceStuckToNeedsAttention,
|
|
67
|
+
routeToNeedsAttention,
|
|
68
|
+
} from './needs-attention.js';
|
|
69
|
+
import {resolveItemPathByIdentity} from './item-path.js';
|
|
63
70
|
import {
|
|
64
71
|
classifyFailureCause,
|
|
65
72
|
failureCauseLabel,
|
|
@@ -117,6 +124,149 @@ import {
|
|
|
117
124
|
* `complete` (the caller threads the resolved mode in as `integration`).
|
|
118
125
|
*/
|
|
119
126
|
|
|
127
|
+
/**
|
|
128
|
+
* The commit-message subject the deadline-checkpoint save writes, and the
|
|
129
|
+
* pattern used to COUNT consecutive checkpoints on the branch (spec
|
|
130
|
+
* `graceful-pre-timeout-wip-checkpoint` — the anti-loop ceiling). Living
|
|
131
|
+
* on-branch means the counter naturally RESETS when the item completes (the
|
|
132
|
+
* branch is discarded on integration) and when a `--reset` requeue discards
|
|
133
|
+
* the branch: a genuinely-progressing task cannot silently accumulate a
|
|
134
|
+
* ceiling breach across generations.
|
|
135
|
+
*/
|
|
136
|
+
const DEADLINE_CHECKPOINT_COMMIT_SUBJECT_PREFIX = 'chore(deadline-checkpoint)';
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Distinct reason strings for the two deadline-checkpoint branches (spec
|
|
140
|
+
* `graceful-pre-timeout-wip-checkpoint` — the CI/human report must NEVER
|
|
141
|
+
* collapse to a generic "agent-failed"). Auto-continue and surface carry
|
|
142
|
+
* different phrasing so the counter + ceiling are legible.
|
|
143
|
+
*/
|
|
144
|
+
function deadlineAutoContinueReason(params: {
|
|
145
|
+
slug: string;
|
|
146
|
+
count: number;
|
|
147
|
+
max: number;
|
|
148
|
+
}): string {
|
|
149
|
+
return (
|
|
150
|
+
`deadline checkpoint (auto-continued ${params.count}/${params.max}): ` +
|
|
151
|
+
`'${params.slug}' hit the dorfl-internal deadline with new work-branch ` +
|
|
152
|
+
`progress this session; saved the WIP and released the lock so the next ` +
|
|
153
|
+
`tick continues from the branch tip.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
function deadlineSurfaceReason(params: {
|
|
157
|
+
slug: string;
|
|
158
|
+
kind: 'no-progress' | 'ceiling';
|
|
159
|
+
count?: number;
|
|
160
|
+
max?: number;
|
|
161
|
+
}): string {
|
|
162
|
+
if (params.kind === 'no-progress') {
|
|
163
|
+
return (
|
|
164
|
+
`deadline checkpoint (no progress / ceiling): '${params.slug}' hit ` +
|
|
165
|
+
`the dorfl-internal deadline WITHOUT any work-branch progress this ` +
|
|
166
|
+
`session — indistinguishable from a wedge; surfaced for a human to ` +
|
|
167
|
+
`decide (continue / split / cancel).`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return (
|
|
171
|
+
`deadline checkpoint (no progress / ceiling): '${params.slug}' has hit ` +
|
|
172
|
+
`the dorfl-internal deadline ${params.count}/${params.max} times ` +
|
|
173
|
+
`consecutively — it may be too big for one CI leg. Split it or run ` +
|
|
174
|
+
`it locally; surfaced for a human to decide.`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Count how many DEADLINE-CHECKPOINT commits (subject prefix
|
|
180
|
+
* `chore(deadline-checkpoint)`) currently sit on the work branch ahead of
|
|
181
|
+
* `<arbiter>/main` — the branch-based anti-loop counter (spec
|
|
182
|
+
* `graceful-pre-timeout-wip-checkpoint`). A best-effort count: an unreachable
|
|
183
|
+
* arbiter / a bare mirror reads as 0 (the SAFE direction — never falsely block
|
|
184
|
+
* an auto-continue) so a genuine plumbing fault degrades to "under the ceiling"
|
|
185
|
+
* rather than surfacing a spurious ceiling breach. Resets naturally when the
|
|
186
|
+
* item completes (the branch is discarded on integration).
|
|
187
|
+
*/
|
|
188
|
+
async function countDeadlineCheckpointsOnBranch(params: {
|
|
189
|
+
cwd: string;
|
|
190
|
+
arbiter: string;
|
|
191
|
+
branch: string;
|
|
192
|
+
env: NodeJS.ProcessEnv | undefined;
|
|
193
|
+
}): Promise<number> {
|
|
194
|
+
const {cwd, arbiter, branch, env} = params;
|
|
195
|
+
const range = `${arbiter}/main..HEAD`;
|
|
196
|
+
void branch;
|
|
197
|
+
const r = await runAsync('git', ['log', '--format=%s', range], cwd, {env});
|
|
198
|
+
if (r.status !== 0) {
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
const lines = r.stdout.split('\n').filter((s) => s.length > 0);
|
|
202
|
+
return lines.filter((s) =>
|
|
203
|
+
s.startsWith(DEADLINE_CHECKPOINT_COMMIT_SUBJECT_PREFIX),
|
|
204
|
+
).length;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* SAVE the WIP on a deadline stop (spec `graceful-pre-timeout-wip-checkpoint`,
|
|
209
|
+
* build step 3): commit the tree as-is + push `work/<slug>`. Reuses the SAVE
|
|
210
|
+
* HALF of the needs-attention mechanism ({@link routeToNeedsAttention}) but
|
|
211
|
+
* ADDS a distinguishing checkpoint-marker commit on top so
|
|
212
|
+
* {@link countDeadlineCheckpointsOnBranch} can count auto-continues. Distinct
|
|
213
|
+
* from `applyNeedsAttentionTransition` (the whole thing) which ALSO marks the
|
|
214
|
+
* lock stuck — auto-continue must NOT mark stuck; only the surface branch does.
|
|
215
|
+
*/
|
|
216
|
+
async function saveDeadlineCheckpoint(params: {
|
|
217
|
+
slug: string;
|
|
218
|
+
branch: string;
|
|
219
|
+
cwd: string;
|
|
220
|
+
arbiter: string;
|
|
221
|
+
reason: string;
|
|
222
|
+
env: NodeJS.ProcessEnv | undefined;
|
|
223
|
+
note: (message: string) => void;
|
|
224
|
+
}): Promise<import('./needs-attention.js').RouteToNeedsAttentionResult> {
|
|
225
|
+
const {slug, branch, cwd, arbiter, reason, env, note} = params;
|
|
226
|
+
void reason;
|
|
227
|
+
// Save any uncommitted work first (the routeToNeedsAttention save half also
|
|
228
|
+
// commits + pushes; but we ALSO want a distinct checkpoint-marker commit so
|
|
229
|
+
// countDeadlineCheckpointsOnBranch can enumerate them). Stage everything and
|
|
230
|
+
// commit a MARKER commit if there's anything to record OR if there is not
|
|
231
|
+
// already a marker on the tip — the marker's job is to make the checkpoint
|
|
232
|
+
// COUNTABLE, not to represent new work.
|
|
233
|
+
const addResult = await runAsync('git', ['add', '-A'], cwd, {env});
|
|
234
|
+
void addResult;
|
|
235
|
+
const diffCached = await runAsync(
|
|
236
|
+
'git',
|
|
237
|
+
['diff', '--cached', '--quiet'],
|
|
238
|
+
cwd,
|
|
239
|
+
{env},
|
|
240
|
+
);
|
|
241
|
+
const hasStaged = diffCached.status !== 0;
|
|
242
|
+
if (hasStaged) {
|
|
243
|
+
await runAsync(
|
|
244
|
+
'git',
|
|
245
|
+
[
|
|
246
|
+
'commit',
|
|
247
|
+
'-q',
|
|
248
|
+
'-m',
|
|
249
|
+
`${DEADLINE_CHECKPOINT_COMMIT_SUBJECT_PREFIX}: save wip for '${slug}'`,
|
|
250
|
+
],
|
|
251
|
+
cwd,
|
|
252
|
+
{env},
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
// Push the branch to the arbiter (best-effort). Reuse routeToNeedsAttention's
|
|
256
|
+
// save-half exact contract: it commits any residue + pushes the branch with
|
|
257
|
+
// bounded backoff. We already committed above; the wip commit inside
|
|
258
|
+
// routeToNeedsAttention will be a no-op (clean tree) and the push runs.
|
|
259
|
+
return routeToNeedsAttention({
|
|
260
|
+
cwd,
|
|
261
|
+
slug,
|
|
262
|
+
reason: `deadline-checkpoint save for '${slug}' (see branch)`,
|
|
263
|
+
arbiter,
|
|
264
|
+
branch,
|
|
265
|
+
note,
|
|
266
|
+
env,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
120
270
|
/** The terminal status of one in-place `do` run. */
|
|
121
271
|
export type DoOutcome =
|
|
122
272
|
| 'completed' // claimed/onboarded → agent → gate green → integrated → exited
|
|
@@ -126,8 +276,11 @@ export type DoOutcome =
|
|
|
126
276
|
| 'surface-unmoved' // the tree-less surface to needs-attention did NOT land on the arbiter (lost the CAS race / no arbiter) — the item is STILL in-progress on the arbiter; retry/resolve
|
|
127
277
|
| 'agent-failed' // the agent ran but produced bad/empty output (the conservative generic), OR the cause is unknown — work SAVED + surfaced
|
|
128
278
|
| 'transient-infra' // a harness-surfaced model/connection outage (post-retry) or a git/provider outage — RETRY the same work (FAILURE-CAUSE axis)
|
|
279
|
+
| 'needs-reauth' // a credential expired / was revoked (OAuth refresh) — retry cannot help; a human must RE-AUTH (FAILURE-CAUSE axis)
|
|
129
280
|
| 'config-error' // a thrown CORE wiring/config error (e.g. review on, no reviewGate) — fix the WIRING, not the task (FAILURE-CAUSE axis)
|
|
130
281
|
| 'agent-stopped' // the agent DELIBERATELY stopped (task drifted/ambiguous) OR produced no change → surfaced; gate + Gate-2 SKIPPED
|
|
282
|
+
| 'deadline-auto-continued' // the dorfl-internal deadline fired; WIP saved + branch pushed + lock RELEASED (no sidecar) so the next tick continues (spec `graceful-pre-timeout-wip-checkpoint`)
|
|
283
|
+
| 'deadline-surfaced' // the dorfl-internal deadline fired but auto-continue was refused (no progress this session, or the maxAutoCheckpoints ceiling was hit); WIP saved + question SURFACED for a human
|
|
131
284
|
| 'refused' // refused (dirty tree, wrong folder, nothing to complete, …)
|
|
132
285
|
| 'usage-error' // usage / environment problem, or a slug-resolution error
|
|
133
286
|
| 'tasked' // `do spec:<slug>` — the spec was tasked into work/backlog/ (runner-owned)
|
|
@@ -168,6 +321,14 @@ export type DoDorfl = (input: {
|
|
|
168
321
|
* Absent ⇒ no body ⇒ the provider degrades to `--fill` (no regression).
|
|
169
322
|
*/
|
|
170
323
|
output?: string;
|
|
324
|
+
/**
|
|
325
|
+
* **True iff the injected agent simulates a dorfl-internal DEADLINE stop**
|
|
326
|
+
* (spec `graceful-pre-timeout-wip-checkpoint`). Test-only signal so an
|
|
327
|
+
* injected `DoDorfl` can drive the checkpoint routing (save WIP +
|
|
328
|
+
* auto-continue / surface). Production `runDoAgent` never sets this on the
|
|
329
|
+
* injected path — the real deadline race lives in `PiHarness.launchAsync`.
|
|
330
|
+
*/
|
|
331
|
+
timedOut?: boolean;
|
|
171
332
|
};
|
|
172
333
|
|
|
173
334
|
export interface DoOptions {
|
|
@@ -251,10 +412,10 @@ export interface DoOptions {
|
|
|
251
412
|
* CONFIGURED-DEFAULT rung into the runner-deterministic placement resolver
|
|
252
413
|
* (`src/placement.ts`). Resolved per-repo through the SAME chain as
|
|
253
414
|
* `taskingIntegration` (flag > env > per-repo > global > built-in
|
|
254
|
-
* `
|
|
415
|
+
* `backlog`). The task-BUILD path ignores it (placement is a TASKING
|
|
255
416
|
* lifecycle concern).
|
|
256
417
|
*/
|
|
257
|
-
tasksLandIn?: '
|
|
418
|
+
tasksLandIn?: 'backlog' | 'ready';
|
|
258
419
|
/**
|
|
259
420
|
* **The OPERATOR's EXPLICIT task-placement override** (the TOP precedence
|
|
260
421
|
* rung in the placement resolver). Set ONLY when the operator typed
|
|
@@ -263,7 +424,7 @@ export interface DoOptions {
|
|
|
263
424
|
* CLI always wins, no special force-key) — the positional analogue of
|
|
264
425
|
* `explicitMerge` overriding the untrusted-origin build-propose rule.
|
|
265
426
|
*/
|
|
266
|
-
explicitTasksLandIn?: '
|
|
427
|
+
explicitTasksLandIn?: 'backlog' | 'ready';
|
|
267
428
|
/**
|
|
268
429
|
* Override the pre-flight DIVERGENCE guard (`--ignore-diverged-main`, mirroring
|
|
269
430
|
* `--ignore-not-ready`): proceed even when local `main` is ahead of
|
|
@@ -408,6 +569,22 @@ export interface DoOptions {
|
|
|
408
569
|
* a sink to assert the surfaced lines without a real terminal.
|
|
409
570
|
*/
|
|
410
571
|
watchSink?: (line: string) => void;
|
|
572
|
+
/**
|
|
573
|
+
* The dorfl-internal agent-session deadline in minutes (spec
|
|
574
|
+
* `graceful-pre-timeout-wip-checkpoint`). Threaded through {@link runDoAgent}
|
|
575
|
+
* to `launchWithOptionalWatch` — the harness races the child against it and
|
|
576
|
+
* flags a graceful stop on fire, which `performDo` routes as a CHECKPOINT
|
|
577
|
+
* (save WIP + auto-continue on progress under the ceiling, else surface).
|
|
578
|
+
* Absent ⇒ no deadline (a run that finishes on its own is byte-for-byte
|
|
579
|
+
* unchanged).
|
|
580
|
+
*/
|
|
581
|
+
agentDeadlineMinutes?: number;
|
|
582
|
+
/**
|
|
583
|
+
* Anti-loop ceiling on CONSECUTIVE deadline auto-continues (spec
|
|
584
|
+
* `graceful-pre-timeout-wip-checkpoint`). On hitting this the next
|
|
585
|
+
* deadline checkpoint surfaces to a human instead of auto-continuing.
|
|
586
|
+
*/
|
|
587
|
+
maxAutoCheckpoints?: number;
|
|
411
588
|
}
|
|
412
589
|
|
|
413
590
|
/**
|
|
@@ -433,6 +610,25 @@ interface DoAgentLaunchOptions {
|
|
|
433
610
|
watch?: boolean;
|
|
434
611
|
watchSink?: (line: string) => void;
|
|
435
612
|
color?: boolean;
|
|
613
|
+
/**
|
|
614
|
+
* **The dorfl-internal agent deadline in minutes** (spec
|
|
615
|
+
* `graceful-pre-timeout-wip-checkpoint`). Threaded to {@link runDoAgent},
|
|
616
|
+
* which converts it to a wall-clock `deadlineMs` and passes it to
|
|
617
|
+
* {@link launchWithOptionalWatch} — the pi harness races the child against
|
|
618
|
+
* that deadline. When the deadline fires, the agent is gracefully stopped
|
|
619
|
+
* (SIGTERM + 10s grace + SIGKILL) and the caller routes the run as a
|
|
620
|
+
* CHECKPOINT (save WIP + auto-continue / surface). Absent ⇒ no deadline
|
|
621
|
+
* (byte-for-byte the pre-task behaviour).
|
|
622
|
+
*/
|
|
623
|
+
agentDeadlineMinutes?: number;
|
|
624
|
+
/**
|
|
625
|
+
* **The anti-loop ceiling** for consecutive deadline auto-continues (spec
|
|
626
|
+
* `graceful-pre-timeout-wip-checkpoint`). On hitting this the next deadline
|
|
627
|
+
* checkpoint SURFACES to a human instead of auto-continuing. Absent ⇒
|
|
628
|
+
* unlimited (⇒ defensive callers should always thread the resolved config
|
|
629
|
+
* value; the default is 5).
|
|
630
|
+
*/
|
|
631
|
+
maxAutoCheckpoints?: number;
|
|
436
632
|
env?: NodeJS.ProcessEnv;
|
|
437
633
|
/**
|
|
438
634
|
* The optional runner IDENTITY (a bot). Threaded from the host-only
|
|
@@ -507,12 +703,12 @@ export interface DoRemoteOptions extends DoAgentLaunchOptions {
|
|
|
507
703
|
* spec:<slug>` path: threaded into {@link performTask} as the
|
|
508
704
|
* configured-default rung. See {@link DoOptions.tasksLandIn}.
|
|
509
705
|
*/
|
|
510
|
-
tasksLandIn?: '
|
|
706
|
+
tasksLandIn?: 'backlog' | 'ready';
|
|
511
707
|
/**
|
|
512
708
|
* **The OPERATOR's EXPLICIT task-placement override** on the `do --remote
|
|
513
709
|
* spec:` path. See {@link DoOptions.explicitTasksLandIn}.
|
|
514
710
|
*/
|
|
515
|
-
explicitTasksLandIn?: '
|
|
711
|
+
explicitTasksLandIn?: 'backlog' | 'ready';
|
|
516
712
|
/** The declared per-repo ENV-PREP step (string | list), run ONCE before the
|
|
517
713
|
* first `verify` on a fresh worktree. Unset ⇒ a no-op (NO default install). */
|
|
518
714
|
prepare?: VerifyConfig;
|
|
@@ -741,6 +937,11 @@ export async function performDo(options: DoOptions): Promise<DoResult> {
|
|
|
741
937
|
// `integration:'propose'` + `taskingIntegration:'merge'` lands the task FILES
|
|
742
938
|
// on main here while the BUILD path below still threads plain `integration`.
|
|
743
939
|
integration: options.taskingIntegration ?? options.integration,
|
|
940
|
+
// The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) —
|
|
941
|
+
// threaded so the `do spec:` tasking-transition's land tail respects the
|
|
942
|
+
// same per-repo cap the build path uses (task
|
|
943
|
+
// `thread-merge-retries-cross-task-and-ratify-default`).
|
|
944
|
+
mergeRetries: options.mergeRetries,
|
|
744
945
|
// The per-repo TASK-PLACEMENT default + the operator's explicit
|
|
745
946
|
// override (task `runner-deterministic-slice-placement-policy-and-
|
|
746
947
|
// precedence`). The tasker reads them as the configured-default + the
|
|
@@ -942,15 +1143,18 @@ export async function performDo(options: DoOptions): Promise<DoResult> {
|
|
|
942
1143
|
// auto-resolved). Surface to needs-attention TREE-LESSLY via the SAME `#89`
|
|
943
1144
|
// mechanism `requeue` uses for the reverse direction — the rebase was
|
|
944
1145
|
// ABORTED, so the kept `work/<slug>` tip == the arbiter tip (already on the
|
|
945
|
-
// arbiter, after-commit, recoverable). The surface is purely the
|
|
946
|
-
// `
|
|
947
|
-
// worktree mutation) instead of running the agent — the §10
|
|
1146
|
+
// arbiter, after-commit, recoverable). The surface is purely the lock amend
|
|
1147
|
+
// to `state: stuck` with the reason (post lock-cutover — no folder move, no
|
|
1148
|
+
// branch push, no worktree mutation) instead of running the agent — the §10
|
|
1149
|
+
// path. The work
|
|
948
1150
|
// did NOT onboard; the runner owns the bounce.
|
|
949
1151
|
if (tree.continueRebaseConflict) {
|
|
950
1152
|
const reason =
|
|
951
1153
|
`continuing the kept ${tree.branch}: rebase onto the latest main ` +
|
|
952
|
-
'conflicted (aborted, never auto-resolved) —
|
|
953
|
-
'
|
|
1154
|
+
'conflicted (aborted, never auto-resolved) — run `requeue --reconcile` ' +
|
|
1155
|
+
'to non-destructively re-sync the mirror and retry the rebase (keeps ' +
|
|
1156
|
+
'the work). Last resort: `requeue --reset` DESTRUCTIVELY discards the ' +
|
|
1157
|
+
'branch and starts fresh.';
|
|
954
1158
|
const surfaced = await ledgerWrite.applyTreelessNeedsAttentionTransition({
|
|
955
1159
|
cwd: tree.dir,
|
|
956
1160
|
slug,
|
|
@@ -984,8 +1188,10 @@ export async function performDo(options: DoOptions): Promise<DoResult> {
|
|
|
984
1188
|
const reason =
|
|
985
1189
|
`continuing the kept ${tree.branch}: publishing the rebased work branch ` +
|
|
986
1190
|
`to the arbiter failed terminally (${tree.continuePushFailure}) — the kept ` +
|
|
987
|
-
'branch is left intact on the arbiter (recoverable); `requeue`
|
|
988
|
-
'
|
|
1191
|
+
'branch is left intact on the arbiter (recoverable); `requeue --reconcile` ' +
|
|
1192
|
+
'to non-destructively re-sync the mirror and retry once the churn ' +
|
|
1193
|
+
'settles (keeps the work). Last resort: `requeue --reset` DESTRUCTIVELY ' +
|
|
1194
|
+
'discards the branch and starts fresh.';
|
|
989
1195
|
const surfaced = await ledgerWrite.applyTreelessNeedsAttentionTransition({
|
|
990
1196
|
cwd: tree.dir,
|
|
991
1197
|
slug,
|
|
@@ -1074,7 +1280,12 @@ export async function performDo(options: DoOptions): Promise<DoResult> {
|
|
|
1074
1280
|
throw err;
|
|
1075
1281
|
}
|
|
1076
1282
|
|
|
1077
|
-
let agent: {
|
|
1283
|
+
let agent: {
|
|
1284
|
+
ok: boolean;
|
|
1285
|
+
detail?: string;
|
|
1286
|
+
output?: string;
|
|
1287
|
+
timedOut?: boolean;
|
|
1288
|
+
};
|
|
1078
1289
|
try {
|
|
1079
1290
|
agent = await runDoAgent(options, tree.dir, prompt, slug);
|
|
1080
1291
|
} catch (err) {
|
|
@@ -1089,6 +1300,23 @@ export async function performDo(options: DoOptions): Promise<DoResult> {
|
|
|
1089
1300
|
note,
|
|
1090
1301
|
});
|
|
1091
1302
|
}
|
|
1303
|
+
// DEADLINE CHECKPOINT (spec `graceful-pre-timeout-wip-checkpoint`): the
|
|
1304
|
+
// dorfl-internal deadline fired — stop is graceful, NOT a failure. Route
|
|
1305
|
+
// through the shared checkpoint path (save WIP + auto-continue on progress
|
|
1306
|
+
// under the ceiling, else surface). Distinct from `saveAgentFailure` on
|
|
1307
|
+
// purpose — the deadline path must NOT mark the item stuck when it
|
|
1308
|
+
// auto-continues.
|
|
1309
|
+
if (agent.timedOut) {
|
|
1310
|
+
return await routeDeadlineCheckpoint({
|
|
1311
|
+
slug,
|
|
1312
|
+
branch,
|
|
1313
|
+
cwd: tree.dir,
|
|
1314
|
+
arbiter: tree.arbiterRemote,
|
|
1315
|
+
maxAutoCheckpoints: options.maxAutoCheckpoints ?? 5,
|
|
1316
|
+
env,
|
|
1317
|
+
note,
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1092
1320
|
if (!agent.ok) {
|
|
1093
1321
|
const detail = agent.detail ?? `the agent failed to build '${slug}'.`;
|
|
1094
1322
|
return await saveAgentFailure({
|
|
@@ -1123,7 +1351,8 @@ export async function performDo(options: DoOptions): Promise<DoResult> {
|
|
|
1123
1351
|
branch,
|
|
1124
1352
|
cwd: tree.dir,
|
|
1125
1353
|
arbiter: tree.arbiterRemote,
|
|
1126
|
-
reason: stopReason,
|
|
1354
|
+
reason: stopReason.reason,
|
|
1355
|
+
kind: stopReason.kind,
|
|
1127
1356
|
env,
|
|
1128
1357
|
note,
|
|
1129
1358
|
});
|
|
@@ -1275,8 +1504,10 @@ export async function performDo(options: DoOptions): Promise<DoResult> {
|
|
|
1275
1504
|
// (`runRemotePipeline`) uses, so `advance task:<slug>` (via the default
|
|
1276
1505
|
// `doDriver = performDo`) agrees with the remote path on the caller-visible
|
|
1277
1506
|
// label.
|
|
1507
|
+
//
|
|
1508
|
+
// PR-2b D3: a clean surface (routedToNeedsAttention:true) is GREEN.
|
|
1278
1509
|
return {
|
|
1279
|
-
exitCode: 1,
|
|
1510
|
+
exitCode: completed.routedToNeedsAttention ? 0 : 1,
|
|
1280
1511
|
outcome: 'needs-attention',
|
|
1281
1512
|
slug,
|
|
1282
1513
|
branch,
|
|
@@ -1373,7 +1604,9 @@ function routeReport(
|
|
|
1373
1604
|
return {
|
|
1374
1605
|
fragment:
|
|
1375
1606
|
`push of ${branch} FAILED — the work is saved LOCALLY only; push it ` +
|
|
1376
|
-
'when online, then `requeue` (continue)
|
|
1607
|
+
'when online, then `requeue` (continue) or `requeue --reconcile` ' +
|
|
1608
|
+
'(non-destructive retry). Last resort: `requeue --reset` ' +
|
|
1609
|
+
'DESTRUCTIVELY discards.',
|
|
1377
1610
|
pushFailed: true,
|
|
1378
1611
|
};
|
|
1379
1612
|
}
|
|
@@ -1389,7 +1622,10 @@ function routeReport(
|
|
|
1389
1622
|
landed = 'saved locally';
|
|
1390
1623
|
}
|
|
1391
1624
|
return {
|
|
1392
|
-
fragment:
|
|
1625
|
+
fragment:
|
|
1626
|
+
`${landed}. Recover via \`requeue\` (continue) or \`requeue --reconcile\` ` +
|
|
1627
|
+
'(non-destructive retry). Last resort: `requeue --reset` ' +
|
|
1628
|
+
'DESTRUCTIVELY discards.',
|
|
1393
1629
|
pushFailed: false,
|
|
1394
1630
|
};
|
|
1395
1631
|
}
|
|
@@ -1403,10 +1639,12 @@ function routeReport(
|
|
|
1403
1639
|
*
|
|
1404
1640
|
* This routes it through the SAME work-preserving machinery a RED GATE uses: the
|
|
1405
1641
|
* ledger write seam's needs-attention transition (`git add -A` + a wip commit
|
|
1406
|
-
* capturing the agent's work
|
|
1407
|
-
* with the failure detail
|
|
1408
|
-
*
|
|
1409
|
-
*
|
|
1642
|
+
* capturing the agent's work, then — post lock-cutover — a LOCK AMEND to
|
|
1643
|
+
* `state: stuck` with the failure detail as the reason on the lock entry, in
|
|
1644
|
+
* place of the retired `git mv → needs-attention/` move-only commit), surfaced on
|
|
1645
|
+
* the arbiter's `main` (the autonomous, cross-machine-visible mode-M surfacing
|
|
1646
|
+
* `do` already uses for the gate-fail path) so `scan`/`status`/another machine
|
|
1647
|
+
* see it.
|
|
1410
1648
|
*
|
|
1411
1649
|
* It ALSO pushes the `work/<slug>` branch to the arbiter so the saved partial
|
|
1412
1650
|
* commits travel cross-machine and the item is RECOVERABLE via `requeue`
|
|
@@ -1429,7 +1667,8 @@ function routeReport(
|
|
|
1429
1667
|
* contract stays coherent). Only the WORK-PRESERVING side-effect (unchanged here)
|
|
1430
1668
|
* matches the gate-failure path. We do NOT validate or "fix" the partial work — a broken
|
|
1431
1669
|
* tree committed + surfaced (with the reason) is recoverable; the human chooses
|
|
1432
|
-
* `requeue` (continue)
|
|
1670
|
+
* `requeue` (continue) / `requeue --reconcile` (non-destructive retry) vs
|
|
1671
|
+
* `requeue --reset` (destructive last resort).
|
|
1433
1672
|
*/
|
|
1434
1673
|
async function saveAgentFailure(params: {
|
|
1435
1674
|
slug: string;
|
|
@@ -1477,8 +1716,10 @@ async function saveAgentFailure(params: {
|
|
|
1477
1716
|
: `Agent run failed building '${slug}' [${cause}] (${detail}); could not ` +
|
|
1478
1717
|
`mark '${slug}' stuck (${routed.reasonNotMoved ?? 'unknown'}).`;
|
|
1479
1718
|
note(message);
|
|
1719
|
+
// PR-2b D3: a cleanly-surfaced bounce is GREEN (exitCode 0); a FAILED surface
|
|
1720
|
+
// stays non-zero.
|
|
1480
1721
|
return {
|
|
1481
|
-
exitCode: 1,
|
|
1722
|
+
exitCode: routed.moved ? 0 : 1,
|
|
1482
1723
|
outcome: failureCauseToDoOutcome(cause),
|
|
1483
1724
|
slug,
|
|
1484
1725
|
branch,
|
|
@@ -1499,7 +1740,9 @@ function failureCauseToDoOutcome(cause: FailureCause): DoOutcome {
|
|
|
1499
1740
|
|
|
1500
1741
|
/**
|
|
1501
1742
|
* Build the HONEST result for a CONTINUE-site surface that did NOT land on the
|
|
1502
|
-
* arbiter (`{moved: false}`). The tree-less
|
|
1743
|
+
* arbiter (`{moved: false}`). The tree-less stuck-lock amend (post lock-cutover —
|
|
1744
|
+
* `state: stuck` with the reason, in place of the retired `in-progress/ →
|
|
1745
|
+
* needs-attention/` move)
|
|
1503
1746
|
* lost the CAS race against a busy arbiter (its contention-retry cap exhausted) or
|
|
1504
1747
|
* had no arbiter to publish to, so the item is STILL in-progress on the arbiter —
|
|
1505
1748
|
* a clean `needs-attention` would mislead (it claims the surface landed). Distinct
|
|
@@ -1544,14 +1787,14 @@ async function resolveStopReason(params: {
|
|
|
1544
1787
|
cwd: string;
|
|
1545
1788
|
arbiter: string;
|
|
1546
1789
|
env: NodeJS.ProcessEnv | undefined;
|
|
1547
|
-
}): Promise<
|
|
1790
|
+
}): Promise<AgentStopClass | undefined> {
|
|
1548
1791
|
const {output, slug, cwd, arbiter, env} = params;
|
|
1549
1792
|
const sentinel = parseStopSentinel(output);
|
|
1550
1793
|
if (sentinel !== undefined) {
|
|
1551
|
-
return sentinel.reason;
|
|
1794
|
+
return {kind: 'sentinel', reason: sentinel.reason};
|
|
1552
1795
|
}
|
|
1553
1796
|
if (await isWorkBranchDiffEmpty({cwd, arbiter, env})) {
|
|
1554
|
-
return emptyDiffStopReason(slug);
|
|
1797
|
+
return {kind: 'empty-diff', reason: emptyDiffStopReason(slug)};
|
|
1555
1798
|
}
|
|
1556
1799
|
return undefined;
|
|
1557
1800
|
}
|
|
@@ -1570,12 +1813,66 @@ async function saveAgentStop(params: {
|
|
|
1570
1813
|
cwd: string;
|
|
1571
1814
|
arbiter: string;
|
|
1572
1815
|
reason: string;
|
|
1816
|
+
kind: 'sentinel' | 'empty-diff';
|
|
1573
1817
|
env: NodeJS.ProcessEnv | undefined;
|
|
1574
1818
|
note: (message: string) => void;
|
|
1575
1819
|
}): Promise<DoResult> {
|
|
1576
|
-
const {slug, cwd, arbiter, reason, env, note} = params;
|
|
1820
|
+
const {slug, cwd, arbiter, reason, kind, env, note} = params;
|
|
1577
1821
|
const branch = params.branch ?? workBranchRef('task', slug);
|
|
1578
1822
|
|
|
1823
|
+
// EMPTY-DIFF branch (spec resolved decision #2, task
|
|
1824
|
+
// `empty-diff-bounce-surfaces-dispose-defaulted-question`): surface a sidecar
|
|
1825
|
+
// on `<arbiter>/main` carrying an engine-authored DISPOSE-DEFAULTED envelope
|
|
1826
|
+
// question, THEN release the lock. "Nothing to do" is a non-deterministic LLM
|
|
1827
|
+
// judgement, so this seam MUST NOT blindly requeue (that would infinite-loop);
|
|
1828
|
+
// the surfaced sidecar + `needsAnswers:true` on `main` parks the item as a
|
|
1829
|
+
// human-visible question a `dispose` answer settles to `tasks/cancelled/`.
|
|
1830
|
+
if (kind === 'empty-diff') {
|
|
1831
|
+
const item = `task:${slug}`;
|
|
1832
|
+
const itemPath = resolveItemPathByIdentity(cwd, item);
|
|
1833
|
+
if (itemPath === undefined) {
|
|
1834
|
+
const message =
|
|
1835
|
+
`The agent STOPPED building '${slug}' (empty diff) but its body could ` +
|
|
1836
|
+
`not be located on the arbiter to surface a sidecar. Reason: ${reason}`;
|
|
1837
|
+
note(message);
|
|
1838
|
+
return {
|
|
1839
|
+
exitCode: 1,
|
|
1840
|
+
outcome: 'agent-stopped',
|
|
1841
|
+
slug,
|
|
1842
|
+
branch,
|
|
1843
|
+
routedToNeedsAttention: false,
|
|
1844
|
+
message,
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
const surfaced = await surfaceStuckToNeedsAttention({
|
|
1848
|
+
cwd,
|
|
1849
|
+
slug,
|
|
1850
|
+
item,
|
|
1851
|
+
itemPath,
|
|
1852
|
+
reason,
|
|
1853
|
+
envelope: emptyDiffDisposeEnvelope({item, reason}),
|
|
1854
|
+
arbiter,
|
|
1855
|
+
env,
|
|
1856
|
+
note,
|
|
1857
|
+
});
|
|
1858
|
+
const message = surfaced.surfaced
|
|
1859
|
+
? `The agent STOPPED building '${slug}' (empty diff); surfaced a ` +
|
|
1860
|
+
`dispose-defaulted question on ${arbiter}/main and released the lock. ` +
|
|
1861
|
+
`Reason: ${reason}`
|
|
1862
|
+
: `The agent STOPPED building '${slug}' (empty diff) but the surface ` +
|
|
1863
|
+
`did not land (${surfaced.reasonNotSurfaced ?? 'unknown'}). ` +
|
|
1864
|
+
`Reason: ${reason}`;
|
|
1865
|
+
note(message);
|
|
1866
|
+
return {
|
|
1867
|
+
exitCode: 1,
|
|
1868
|
+
outcome: 'agent-stopped',
|
|
1869
|
+
slug,
|
|
1870
|
+
branch,
|
|
1871
|
+
routedToNeedsAttention: surfaced.surfaced,
|
|
1872
|
+
message,
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1579
1876
|
const routed = await ledgerWrite.applyNeedsAttentionTransition({
|
|
1580
1877
|
cwd,
|
|
1581
1878
|
slug,
|
|
@@ -1593,8 +1890,11 @@ async function saveAgentStop(params: {
|
|
|
1593
1890
|
: `The agent STOPPED building '${slug}' but it could not be marked stuck ` +
|
|
1594
1891
|
`(${routed.reasonNotMoved ?? 'unknown'}). Reason: ${reason}`;
|
|
1595
1892
|
note(message);
|
|
1893
|
+
// PR-2b D3: agent-stopped (sentinel) on a clean surface is GREEN (exitCode 0);
|
|
1894
|
+
// a FAILED surface stays non-zero. The empty-diff branch above is UNCHANGED
|
|
1895
|
+
// (owned by `empty-diff-bounce-surfaces-dispose-defaulted-question`).
|
|
1596
1896
|
return {
|
|
1597
|
-
exitCode: 1,
|
|
1897
|
+
exitCode: routed.moved ? 0 : 1,
|
|
1598
1898
|
outcome: 'agent-stopped',
|
|
1599
1899
|
slug,
|
|
1600
1900
|
branch,
|
|
@@ -1626,11 +1926,26 @@ async function runDoAgent(
|
|
|
1626
1926
|
cwd: string,
|
|
1627
1927
|
prompt: string,
|
|
1628
1928
|
slug: string,
|
|
1629
|
-
): Promise<{
|
|
1929
|
+
): Promise<{
|
|
1930
|
+
ok: boolean;
|
|
1931
|
+
detail?: string;
|
|
1932
|
+
output?: string;
|
|
1933
|
+
timedOut?: boolean;
|
|
1934
|
+
}> {
|
|
1630
1935
|
if (options.dorfl) {
|
|
1631
1936
|
return options.dorfl({cwd, prompt, slug, env: options.env});
|
|
1632
1937
|
}
|
|
1633
1938
|
const harness = options.harness ?? new NullHarness();
|
|
1939
|
+
// Convert the dorfl-internal deadline (minutes) into a wall-clock epoch-ms so
|
|
1940
|
+
// the harness (`launchAsync`) can race the child against it (spec
|
|
1941
|
+
// `graceful-pre-timeout-wip-checkpoint`). Absent ⇒ no deadline; a run that
|
|
1942
|
+
// finishes before it is byte-for-byte unchanged (the harness clears the
|
|
1943
|
+
// timer on normal exit). Forcing the async path when a deadline is set is
|
|
1944
|
+
// {@link launchWithOptionalWatch}'s job.
|
|
1945
|
+
const deadlineMs =
|
|
1946
|
+
options.agentDeadlineMinutes !== undefined
|
|
1947
|
+
? Date.now() + options.agentDeadlineMinutes * 60_000
|
|
1948
|
+
: undefined;
|
|
1634
1949
|
const launched = await launchWithOptionalWatch({
|
|
1635
1950
|
harness,
|
|
1636
1951
|
dir: cwd,
|
|
@@ -1642,6 +1957,7 @@ async function runDoAgent(
|
|
|
1642
1957
|
sessionId: slug,
|
|
1643
1958
|
sessionsDir: options.sessionsDir,
|
|
1644
1959
|
watch: options.watch,
|
|
1960
|
+
deadlineMs,
|
|
1645
1961
|
watchSink: options.watchSink,
|
|
1646
1962
|
color: options.color,
|
|
1647
1963
|
env: options.env,
|
|
@@ -1649,7 +1965,134 @@ async function runDoAgent(
|
|
|
1649
1965
|
// Surface the agent's FINAL SUMMARY (`LaunchResult.output`) — the source channel
|
|
1650
1966
|
// for the propose-mode PR body — instead of dropping it. Absent (no parseable
|
|
1651
1967
|
// assistant text) ⇒ undefined ⇒ the body degrades to `--fill` (no regression).
|
|
1652
|
-
return {
|
|
1968
|
+
return {
|
|
1969
|
+
ok: launched.ok,
|
|
1970
|
+
detail: launched.detail,
|
|
1971
|
+
output: launched.output,
|
|
1972
|
+
timedOut: launched.timedOut,
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
/**
|
|
1977
|
+
* Route a DEADLINE STOP (spec `graceful-pre-timeout-wip-checkpoint`) through
|
|
1978
|
+
* the checkpoint machinery: ALWAYS save the WIP (commit + push), then
|
|
1979
|
+
* decide — progress AND under ceiling ⇒ AUTO-CONTINUE (release lock, keep
|
|
1980
|
+
* branch, NO sidecar, exit 0); else ⇒ SURFACE a `needsAnswers:true` question.
|
|
1981
|
+
* Shared by {@link performDo} and {@link runRemotePipeline} so the in-place
|
|
1982
|
+
* and remote forms route a deadline stop identically.
|
|
1983
|
+
*/
|
|
1984
|
+
async function routeDeadlineCheckpoint(params: {
|
|
1985
|
+
slug: string;
|
|
1986
|
+
branch: string;
|
|
1987
|
+
cwd: string;
|
|
1988
|
+
arbiter: string;
|
|
1989
|
+
maxAutoCheckpoints: number;
|
|
1990
|
+
env: NodeJS.ProcessEnv | undefined;
|
|
1991
|
+
note: (message: string) => void;
|
|
1992
|
+
}): Promise<DoResult> {
|
|
1993
|
+
const {slug, branch, cwd, arbiter, maxAutoCheckpoints, env, note} = params;
|
|
1994
|
+
|
|
1995
|
+
// 1. ALWAYS save the WIP first: commit any residue + push the work branch.
|
|
1996
|
+
const savedReason = `deadline checkpoint save for '${slug}'`;
|
|
1997
|
+
const saved = await saveDeadlineCheckpoint({
|
|
1998
|
+
slug,
|
|
1999
|
+
branch,
|
|
2000
|
+
cwd,
|
|
2001
|
+
arbiter,
|
|
2002
|
+
reason: savedReason,
|
|
2003
|
+
env,
|
|
2004
|
+
note,
|
|
2005
|
+
});
|
|
2006
|
+
void saved;
|
|
2007
|
+
|
|
2008
|
+
// 2. Progress this session? Reuse the SAME primitive the STOP backstop uses.
|
|
2009
|
+
const emptyDiff = await isWorkBranchDiffEmpty({cwd, arbiter, env});
|
|
2010
|
+
const madeProgressThisSession = !emptyDiff;
|
|
2011
|
+
|
|
2012
|
+
// 3. Count consecutive deadline checkpoints on the branch (ceiling gate). The
|
|
2013
|
+
// marker commit we just saved is included — so the count is the number
|
|
2014
|
+
// of checkpoints INCLUDING this one.
|
|
2015
|
+
const checkpointCount = await countDeadlineCheckpointsOnBranch({
|
|
2016
|
+
cwd,
|
|
2017
|
+
arbiter,
|
|
2018
|
+
branch,
|
|
2019
|
+
env,
|
|
2020
|
+
});
|
|
2021
|
+
|
|
2022
|
+
if (madeProgressThisSession && checkpointCount <= maxAutoCheckpoints) {
|
|
2023
|
+
// AUTO-CONTINUE: release the lock via the SAME default keep+continue path
|
|
2024
|
+
// `requeue` uses (no --reset, no --reconcile, no sidecar). The branch is
|
|
2025
|
+
// KEPT on the arbiter so the next claim continues from its tip.
|
|
2026
|
+
const reason = deadlineAutoContinueReason({
|
|
2027
|
+
slug,
|
|
2028
|
+
count: checkpointCount,
|
|
2029
|
+
max: maxAutoCheckpoints,
|
|
2030
|
+
});
|
|
2031
|
+
const returned = await ledgerWrite.applyReturnToBacklogTransition({
|
|
2032
|
+
cwd,
|
|
2033
|
+
slug,
|
|
2034
|
+
arbiter,
|
|
2035
|
+
env,
|
|
2036
|
+
note,
|
|
2037
|
+
});
|
|
2038
|
+
if (returned.moved) {
|
|
2039
|
+
const message =
|
|
2040
|
+
`Auto-continued '${slug}' at the dorfl-internal deadline (checkpoint ` +
|
|
2041
|
+
`${checkpointCount}/${maxAutoCheckpoints}): WIP saved + branch pushed, ` +
|
|
2042
|
+
`lock released so the next tick continues from ${branch}. ${reason}`;
|
|
2043
|
+
note(message);
|
|
2044
|
+
return {
|
|
2045
|
+
exitCode: 0,
|
|
2046
|
+
outcome: 'deadline-auto-continued',
|
|
2047
|
+
slug,
|
|
2048
|
+
branch,
|
|
2049
|
+
message,
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
// The auto-continue lock release did not land — fall through to SURFACE.
|
|
2053
|
+
note(
|
|
2054
|
+
`Auto-continue lock release for '${slug}' did not land ` +
|
|
2055
|
+
`(${returned.reasonNotMoved ?? 'unknown'}); surfacing instead.`,
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
// SURFACE: mark the lock stuck via the whole applyNeedsAttentionTransition
|
|
2060
|
+
// (save + push + stuck). The WIP was already saved above; a second save is
|
|
2061
|
+
// idempotent (empty commit is skipped inside routeToNeedsAttention).
|
|
2062
|
+
const surfaceReason = madeProgressThisSession
|
|
2063
|
+
? deadlineSurfaceReason({
|
|
2064
|
+
slug,
|
|
2065
|
+
kind: 'ceiling',
|
|
2066
|
+
count: checkpointCount,
|
|
2067
|
+
max: maxAutoCheckpoints,
|
|
2068
|
+
})
|
|
2069
|
+
: deadlineSurfaceReason({slug, kind: 'no-progress'});
|
|
2070
|
+
const routed = await ledgerWrite.applyNeedsAttentionTransition({
|
|
2071
|
+
cwd,
|
|
2072
|
+
slug,
|
|
2073
|
+
reason: surfaceReason,
|
|
2074
|
+
arbiter,
|
|
2075
|
+
env,
|
|
2076
|
+
note,
|
|
2077
|
+
});
|
|
2078
|
+
const report = routed.moved ? routeReport(routed, branch) : undefined;
|
|
2079
|
+
const message = routed.moved
|
|
2080
|
+
? `Surfaced '${slug}' at the deadline checkpoint (${
|
|
2081
|
+
madeProgressThisSession
|
|
2082
|
+
? `ceiling ${checkpointCount}/${maxAutoCheckpoints}`
|
|
2083
|
+
: 'no progress this session'
|
|
2084
|
+
}); ${report!.fragment}. ${surfaceReason}`
|
|
2085
|
+
: `Could not surface '${slug}' at the deadline checkpoint ` +
|
|
2086
|
+
`(${routed.reasonNotMoved ?? 'unknown'}). ${surfaceReason}`;
|
|
2087
|
+
note(message);
|
|
2088
|
+
return {
|
|
2089
|
+
exitCode: 1,
|
|
2090
|
+
outcome: 'deadline-surfaced',
|
|
2091
|
+
slug,
|
|
2092
|
+
branch,
|
|
2093
|
+
routedToNeedsAttention: routed.moved,
|
|
2094
|
+
message,
|
|
2095
|
+
};
|
|
1653
2096
|
}
|
|
1654
2097
|
|
|
1655
2098
|
/**
|
|
@@ -1922,6 +2365,10 @@ export async function performDoRemote(
|
|
|
1922
2365
|
// `taskingIntegration ?? integration`, so the `--remote spec:` output ALSO
|
|
1923
2366
|
// routes through the shared core with the tasking-resolved mode.
|
|
1924
2367
|
integration: options.taskingIntegration ?? options.integration,
|
|
2368
|
+
// The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) —
|
|
2369
|
+
// threaded on the no-checkout `do --remote spec:` tasking path too,
|
|
2370
|
+
// mirroring the in-place site above.
|
|
2371
|
+
mergeRetries: options.mergeRetries,
|
|
1925
2372
|
// The per-repo TASK-PLACEMENT default + the operator's explicit
|
|
1926
2373
|
// override (task `runner-deterministic-slice-placement-policy-and-
|
|
1927
2374
|
// precedence`). Same threading as the in-place `do spec:` path.
|
|
@@ -2094,8 +2541,10 @@ async function runRemotePipeline(
|
|
|
2094
2541
|
if (tree.continueRebaseConflict) {
|
|
2095
2542
|
const reason =
|
|
2096
2543
|
`continuing the kept ${tree.branch}: rebase onto the latest main ` +
|
|
2097
|
-
'conflicted (aborted, never auto-resolved) —
|
|
2098
|
-
'
|
|
2544
|
+
'conflicted (aborted, never auto-resolved) — run `requeue --reconcile` ' +
|
|
2545
|
+
'to non-destructively re-sync the mirror and retry the rebase (keeps ' +
|
|
2546
|
+
'the work). Last resort: `requeue --reset` DESTRUCTIVELY discards the ' +
|
|
2547
|
+
'branch and starts fresh.';
|
|
2099
2548
|
await ledgerWrite.applyNeedsAttentionTransition({
|
|
2100
2549
|
cwd,
|
|
2101
2550
|
slug,
|
|
@@ -2127,8 +2576,10 @@ async function runRemotePipeline(
|
|
|
2127
2576
|
const reason =
|
|
2128
2577
|
`continuing the kept ${tree.branch}: publishing the rebased work branch ` +
|
|
2129
2578
|
`to the arbiter failed terminally (${tree.continuePushFailure}) — the kept ` +
|
|
2130
|
-
'branch is left intact on the arbiter (recoverable); `requeue`
|
|
2131
|
-
'
|
|
2579
|
+
'branch is left intact on the arbiter (recoverable); `requeue --reconcile` ' +
|
|
2580
|
+
'to non-destructively re-sync the mirror and retry once the churn ' +
|
|
2581
|
+
'settles (keeps the work). Last resort: `requeue --reset` DESTRUCTIVELY ' +
|
|
2582
|
+
'discards the branch and starts fresh.';
|
|
2132
2583
|
await ledgerWrite.applyNeedsAttentionTransition({
|
|
2133
2584
|
cwd,
|
|
2134
2585
|
slug,
|
|
@@ -2265,7 +2716,12 @@ async function runRemotePipeline(
|
|
|
2265
2716
|
throw err;
|
|
2266
2717
|
}
|
|
2267
2718
|
|
|
2268
|
-
let agent: {
|
|
2719
|
+
let agent: {
|
|
2720
|
+
ok: boolean;
|
|
2721
|
+
detail?: string;
|
|
2722
|
+
output?: string;
|
|
2723
|
+
timedOut?: boolean;
|
|
2724
|
+
};
|
|
2269
2725
|
try {
|
|
2270
2726
|
agent = await runDoAgent(options, cwd, prompt, slug);
|
|
2271
2727
|
} catch (err) {
|
|
@@ -2280,6 +2736,19 @@ async function runRemotePipeline(
|
|
|
2280
2736
|
note,
|
|
2281
2737
|
});
|
|
2282
2738
|
}
|
|
2739
|
+
// Deadline checkpoint on the no-checkout `do --remote` path — same routing
|
|
2740
|
+
// as the in-place path (see performDo).
|
|
2741
|
+
if (agent.timedOut) {
|
|
2742
|
+
return await routeDeadlineCheckpoint({
|
|
2743
|
+
slug,
|
|
2744
|
+
branch: branch ?? workBranchRef('task', slug),
|
|
2745
|
+
cwd,
|
|
2746
|
+
arbiter: arbiterRemote,
|
|
2747
|
+
maxAutoCheckpoints: options.maxAutoCheckpoints ?? 5,
|
|
2748
|
+
env,
|
|
2749
|
+
note,
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2283
2752
|
if (!agent.ok) {
|
|
2284
2753
|
const detail = agent.detail ?? `the agent failed to build '${slug}'.`;
|
|
2285
2754
|
return await saveRemoteAgentFailure({
|
|
@@ -2311,7 +2780,8 @@ async function runRemotePipeline(
|
|
|
2311
2780
|
branch,
|
|
2312
2781
|
cwd,
|
|
2313
2782
|
arbiter: arbiterRemote,
|
|
2314
|
-
reason: stopReason,
|
|
2783
|
+
reason: stopReason.reason,
|
|
2784
|
+
kind: stopReason.kind,
|
|
2315
2785
|
env,
|
|
2316
2786
|
note,
|
|
2317
2787
|
});
|
|
@@ -2500,8 +2970,9 @@ async function saveRemoteAgentFailure(params: {
|
|
|
2500
2970
|
: `Agent run failed building '${slug}' [${cause}] (${detail}); could not ` +
|
|
2501
2971
|
`mark '${slug}' stuck (${routed.reasonNotMoved ?? 'unknown'}).`;
|
|
2502
2972
|
note(message);
|
|
2973
|
+
// PR-2b D3: cleanly-surfaced remote agent failure is GREEN.
|
|
2503
2974
|
return {
|
|
2504
|
-
exitCode: 1,
|
|
2975
|
+
exitCode: routed.moved ? 0 : 1,
|
|
2505
2976
|
outcome: failureCauseToDoOutcome(cause),
|
|
2506
2977
|
slug,
|
|
2507
2978
|
branch,
|
|
@@ -2613,6 +3084,8 @@ export function jobWorktreeDoDriver(closure: {
|
|
|
2613
3084
|
watch: options.watch,
|
|
2614
3085
|
watchSink: options.watchSink,
|
|
2615
3086
|
color: options.color,
|
|
3087
|
+
agentDeadlineMinutes: options.agentDeadlineMinutes,
|
|
3088
|
+
maxAutoCheckpoints: options.maxAutoCheckpoints,
|
|
2616
3089
|
read: options.read,
|
|
2617
3090
|
env: options.env,
|
|
2618
3091
|
note: options.note,
|