dorfl 0.1.2 → 0.2.1
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/env-config.ts
CHANGED
|
@@ -100,12 +100,12 @@ const KEY_COERCIONS: {[K in keyof Config]?: Coercion} = {
|
|
|
100
100
|
taskingIntegration: {enum: ['propose', 'merge']},
|
|
101
101
|
// `tasksLandIn` (the per-repo TASK-PLACEMENT default — spec
|
|
102
102
|
// `staging-pool-position-gate-and-trust-model` US #5) coerces as the
|
|
103
|
-
// `
|
|
103
|
+
// `backlog`/`ready` enum, so `DORFL_TASKS_LAND_IN=ready` works and a
|
|
104
104
|
// typo FAILS LOUDLY. Same
|
|
105
105
|
// precedence chain as `taskingIntegration` (flag > env > per-repo > global >
|
|
106
|
-
// built-in `
|
|
106
|
+
// built-in `backlog`); fed into the runner-deterministic placement
|
|
107
107
|
// resolver (`src/placement.ts`) as the configured-default rung.
|
|
108
|
-
tasksLandIn: {enum: ['
|
|
108
|
+
tasksLandIn: {enum: ['backlog', 'ready']},
|
|
109
109
|
// `specsLandIn` (the per-repo SPEC-PLACEMENT default — spec
|
|
110
110
|
// `staging-pool-position-gate-and-trust-model` US #2/#5) coerces as the
|
|
111
111
|
// `pre-proposed`/`ready` enum, so `DORFL_SPECS_LAND_IN=ready` works and a typo
|
|
@@ -113,7 +113,7 @@ const KEY_COERCIONS: {[K in keyof Config]?: Coercion} = {
|
|
|
113
113
|
// > global > built-in `pre-proposed`); fed into the shared placement resolver
|
|
114
114
|
// (`src/placement.ts`) as the configured-default rung for the spec lifecycle.
|
|
115
115
|
// The legacy `prdsLandIn` key / `DORFL_PRDS_LAND_IN` env are GONE after the
|
|
116
|
-
//
|
|
116
|
+
// ''prd'' → `spec` hard cutover (clean break).
|
|
117
117
|
specsLandIn: {enum: ['pre-proposed', 'ready']},
|
|
118
118
|
// `noPR` (the PR-INTENT axis) is a BOOLEAN coercion (like `review`), so
|
|
119
119
|
// `DORFL_NO_PR=true|false` works and a typo FAILS LOUDLY. The removed
|
|
@@ -152,6 +152,14 @@ const KEY_COERCIONS: {[K in keyof Config]?: Coercion} = {
|
|
|
152
152
|
// works and a typo FAILS LOUDLY. Default OFF; ON re-surfaces the
|
|
153
153
|
// merge-question on a merge-base change instead of auto-landing.
|
|
154
154
|
strictMergeApproval: 'boolean',
|
|
155
|
+
// The dorfl-INTERNAL agent deadline (minutes) — spec
|
|
156
|
+
// `graceful-pre-timeout-wip-checkpoint`. NUMBER coercion (like
|
|
157
|
+
// `reviewMaxRounds`); range validation is fail-loud in `validateDeadlineConfig`
|
|
158
|
+
// (not clamped here). Env is a per-machine source, so CI can override the
|
|
159
|
+
// per-repo default without a config edit: `DORFL_AGENT_DEADLINE_MINUTES=90`.
|
|
160
|
+
agentDeadlineMinutes: 'number',
|
|
161
|
+
checkpointHeadroomMinutes: 'number',
|
|
162
|
+
maxAutoCheckpoints: 'number',
|
|
155
163
|
// `promptGuidance` is a STRUCTURED (nested) namespace, so it has no scalar env
|
|
156
164
|
// var of its own — each MEMBER carries its own env var (the nested-key form
|
|
157
165
|
// `DORFL_PROMPT_GUIDANCE_<MEMBER>`), handled out-of-band in
|
package/src/failure-cause.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* **The failure-CAUSE classifier** (task `failure-cause-classification-model-vs-
|
|
3
3
|
* git-vs-agent`).
|
|
4
4
|
*
|
|
5
|
-
* When a claimed item
|
|
5
|
+
* When a claimed item is marked stuck on its per-item lock (post lock-cutover —
|
|
6
|
+
* `state: stuck`, no `work/needs-attention/` folder write), the runner records WHY.
|
|
6
7
|
* Historically several DIFFERENT causes collapsed into one undifferentiated label
|
|
7
8
|
* ("agent failed"), even though the CAUSE drives the RECOVERY:
|
|
8
9
|
*
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
*/
|
|
50
51
|
export type FailureCause =
|
|
51
52
|
| 'transient-infra' // a harness-surfaced model/connection outage (post-retry), or a git/provider outage — RETRY the same work
|
|
53
|
+
| 'needs-reauth' // a credential expired / was revoked (e.g. OAuth refresh token) — RETRY CANNOT help; a human must RE-AUTH
|
|
52
54
|
| 'config-error' // a thrown CORE wiring/config error (e.g. review on, no reviewGate) — fix the WIRING
|
|
53
55
|
| 'agent-failed'; // the conservative generic: the agent ran but produced bad/empty output, OR the cause is UNKNOWN
|
|
54
56
|
|
|
@@ -65,6 +67,24 @@ const CONFIG_ERROR_SIGNATURES = [
|
|
|
65
67
|
/no review gate is configured/i,
|
|
66
68
|
];
|
|
67
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Substrings that mark a CREDENTIAL-EXPIRY failure surfaced by the harness: an
|
|
72
|
+
* OAuth refresh token has expired or been revoked, or a 401 came back tagged
|
|
73
|
+
* `authentication_required`. Retry cannot help these — only a human RE-AUTH can.
|
|
74
|
+
* Kept SEPARATE from `TRANSIENT_INFRA_SIGNATURES` (see ADR
|
|
75
|
+
* `transient-infra-and-needs-reauth-routing`) so downstream routing can branch
|
|
76
|
+
* cleanly on the cause: retry-with-backoff for infra, straight to a needs-reauth
|
|
77
|
+
* surface for credentials. Conservative: unrelated 401s that don't mention auth /
|
|
78
|
+
* token stay `agent-failed`.
|
|
79
|
+
*/
|
|
80
|
+
const NEEDS_REAUTH_SIGNATURES = [
|
|
81
|
+
/authentication_required/i,
|
|
82
|
+
/OAuth (?:refresh )?token (?:expired|revoked|invalid)/i,
|
|
83
|
+
/refresh token (?:expired|revoked|invalid)/i,
|
|
84
|
+
/\b401\b[^\n]{0,80}(?:auth|token|credential|unauthori[sz]ed)/i,
|
|
85
|
+
/(?:auth|token|credential)[^\n]{0,40}\b401\b/i,
|
|
86
|
+
];
|
|
87
|
+
|
|
68
88
|
/**
|
|
69
89
|
* Substrings that mark a TRANSIENT-INFRA failure: a model endpoint / connection
|
|
70
90
|
* outage the harness surfaced AFTER exhausting its own retries, or a git/provider
|
|
@@ -117,6 +137,13 @@ export function classifyFailureCause(detail: string | undefined): FailureCause {
|
|
|
117
137
|
if (CONFIG_ERROR_SIGNATURES.some((re) => re.test(text))) {
|
|
118
138
|
return 'config-error';
|
|
119
139
|
}
|
|
140
|
+
// Credential-expiry is checked BEFORE `transient-infra` because a 401 body may
|
|
141
|
+
// also mention words like "unavailable"/network, and no amount of retry helps
|
|
142
|
+
// — a human must re-auth. Kept as its own cause per ADR
|
|
143
|
+
// `transient-infra-and-needs-reauth-routing`.
|
|
144
|
+
if (NEEDS_REAUTH_SIGNATURES.some((re) => re.test(text))) {
|
|
145
|
+
return 'needs-reauth';
|
|
146
|
+
}
|
|
120
147
|
if (TRANSIENT_INFRA_SIGNATURES.some((re) => re.test(text))) {
|
|
121
148
|
return 'transient-infra';
|
|
122
149
|
}
|
|
@@ -134,6 +161,8 @@ export function failureCauseLabel(cause: FailureCause): string {
|
|
|
134
161
|
switch (cause) {
|
|
135
162
|
case 'transient-infra':
|
|
136
163
|
return 'transient infra';
|
|
164
|
+
case 'needs-reauth':
|
|
165
|
+
return 'needs re-auth (credential expired)';
|
|
137
166
|
case 'config-error':
|
|
138
167
|
return 'config error';
|
|
139
168
|
case 'agent-failed':
|
package/src/format.ts
CHANGED
|
@@ -117,40 +117,20 @@ function cwdDivergenceLine(
|
|
|
117
117
|
* consistency rule): the two reads have different freshness + storage models.
|
|
118
118
|
*/
|
|
119
119
|
/**
|
|
120
|
-
* Render ONE held per-item lock entry
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
120
|
+
* Render ONE held per-item lock entry as an indented line, the SHARED renderer
|
|
121
|
+
* for the in-flight lock surface on BOTH the registry view (`formatStatus`) and
|
|
122
|
+
* the cwd section (`formatCwdSection`). Post-`retire-stuck-lock-state` the
|
|
123
|
+
* lock state is degenerate (`active` only) — a held lock is always an
|
|
124
|
+
* in-progress hold; a parked item is a `needsAnswers:true` pool item on `main`
|
|
125
|
+
* with a sidecar, rendered by the pool categoriser (NOT here).
|
|
126
126
|
*/
|
|
127
127
|
export function formatLockEntryLines(
|
|
128
128
|
entry: LockEntry,
|
|
129
129
|
indent: string,
|
|
130
130
|
): string[] {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
`${indent}${entry.entry} (${entry.action}/${entry.state} = ${view})`,
|
|
135
|
-
);
|
|
136
|
-
if (entry.state === 'stuck') {
|
|
137
|
-
const reason =
|
|
138
|
-
entry.reason && entry.reason !== ''
|
|
139
|
-
? entry.reason
|
|
140
|
-
: '(no reason recorded)';
|
|
141
|
-
const reasonLines = reason.split('\n');
|
|
142
|
-
lines.push(`${indent} reason: ${reasonLines[0]}`);
|
|
143
|
-
for (const extra of reasonLines.slice(1)) {
|
|
144
|
-
lines.push(`${indent} ${extra}`);
|
|
145
|
-
}
|
|
146
|
-
if (entry.questions && entry.questions.length > 0) {
|
|
147
|
-
lines.push(`${indent} questions:`);
|
|
148
|
-
for (const q of entry.questions) {
|
|
149
|
-
lines.push(`${indent} - ${q}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return lines;
|
|
131
|
+
return [
|
|
132
|
+
`${indent}${entry.entry} (${entry.action}/${entry.state} = in-progress)`,
|
|
133
|
+
];
|
|
154
134
|
}
|
|
155
135
|
|
|
156
136
|
export function formatCwdSection(section: CwdSection): string[] {
|
|
@@ -214,7 +194,7 @@ export function formatCwdSection(section: CwdSection): string[] {
|
|
|
214
194
|
const lockHeld = section.repo.lockHeld ?? [];
|
|
215
195
|
if (lockHeld.length > 0) {
|
|
216
196
|
lines.push('');
|
|
217
|
-
lines.push(' In progress
|
|
197
|
+
lines.push(' In progress (lock held — refs/dorfl/lock/*):');
|
|
218
198
|
for (const entry of lockHeld) {
|
|
219
199
|
lines.push(...formatLockEntryLines(entry, ' '));
|
|
220
200
|
}
|
package/src/frontmatter.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* **Origin-trust PROVENANCE** (task `untrusted-origin-forces-build-propose`).
|
|
10
|
-
* How a
|
|
11
|
-
* signal SURVIVES the
|
|
10
|
+
* How a spec/task was BORN and the AUTHOR-TRUST verdict at birth, stamped so the
|
|
11
|
+
* signal SURVIVES the spec/task merge boundary (a landed-on-`main` artifact would
|
|
12
12
|
* otherwise erase how it was created — the laundering gap).
|
|
13
13
|
*
|
|
14
14
|
* - `origin` — `human` (the default / unset: a human authored it locally; a local
|
|
@@ -48,8 +48,8 @@ export interface Frontmatter {
|
|
|
48
48
|
* spec lives at `work/specs/ready/<spec>.md`. The `spec` vocabulary name (spec
|
|
49
49
|
* `prd-to-spec-vocabulary-cutover-and-migration-command`). `parseFrontmatter`
|
|
50
50
|
* populates it from the canonical `spec:` key ONLY — the HARD CUTOVER (the last
|
|
51
|
-
*
|
|
52
|
-
* un-migrated
|
|
51
|
+
* ''prd'' back-compat surface removed): the legacy ''prd:'' KEY read is GONE, so
|
|
52
|
+
* an un-migrated ''prd:'' field no longer silently resolves. A downstream repo
|
|
53
53
|
* migrates its data with `dorfl prd-to-spec` (a purely TEXTUAL `prd: → spec:`
|
|
54
54
|
* rewrite that does NOT go through this parser), after which the field reads
|
|
55
55
|
* canonically. There is no `fm.prd` FIELD.
|
|
@@ -292,6 +292,36 @@ export function setFrontmatterMarker(
|
|
|
292
292
|
return lines.join('\n');
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Read ONE arbitrary top-level scalar frontmatter key by name, returning its
|
|
297
|
+
* unquoted value (or `undefined` when the doc has no fence, or the key is absent).
|
|
298
|
+
*
|
|
299
|
+
* A general-purpose companion to {@link parseFrontmatter} for keys that are NOT in
|
|
300
|
+
* the modelled {@link Frontmatter} shape — e.g. the observation→task provenance
|
|
301
|
+
* back-reference `promotedFrom:` the triage-promote path stamps and the create-CAS
|
|
302
|
+
* lost-race disambiguation reads (task
|
|
303
|
+
* `observation-triage-already-triaged-benign-skip`). Only top-level `key: value`
|
|
304
|
+
* lines are matched (block-list / nested values are out of scope for a scalar read).
|
|
305
|
+
*/
|
|
306
|
+
export function readFrontmatterField(
|
|
307
|
+
content: string,
|
|
308
|
+
key: string,
|
|
309
|
+
): string | undefined {
|
|
310
|
+
const block = extractBlock(content);
|
|
311
|
+
if (block === undefined) {
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
const pattern = new RegExp(`^${key}\\s*:\\s*(.*)$`);
|
|
315
|
+
for (const line of block.split('\n')) {
|
|
316
|
+
const match = pattern.exec(line);
|
|
317
|
+
if (match) {
|
|
318
|
+
const value = unquote(match[1]);
|
|
319
|
+
return value === '' ? undefined : value;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
|
|
295
325
|
/**
|
|
296
326
|
* PROPAGATE the origin-trust PROVENANCE (`origin` + `originTrust`) from a SOURCE
|
|
297
327
|
* artifact (a spec) onto a TARGET artifact's content (an emitted task), returning
|
|
@@ -374,7 +404,7 @@ export function parseFrontmatter(content: string): Frontmatter {
|
|
|
374
404
|
} else if (key === 'spec') {
|
|
375
405
|
// The parent-spec pointer, read from the canonical `spec:` key ONLY into the
|
|
376
406
|
// `fm.spec` field (spec `prd-to-spec-vocabulary-cutover-and-migration-command`,
|
|
377
|
-
// HARD CUTOVER). The legacy
|
|
407
|
+
// HARD CUTOVER). The legacy ''prd:'' KEY read is GONE — an un-migrated ''prd:''
|
|
378
408
|
// field no longer resolves here; a repo converts its data via the TEXTUAL
|
|
379
409
|
// `dorfl prd-to-spec` rewrite (which does not use this parser). An empty value
|
|
380
410
|
// leaves the field undefined.
|
package/src/gc.ts
CHANGED
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
removeJobRecord,
|
|
9
9
|
type JobRecord,
|
|
10
10
|
} from './workspace.js';
|
|
11
|
-
import {workBranchRef} from './slug-namespace.js';
|
|
11
|
+
import {parseWorkBranchRef, workBranchRef} from './slug-namespace.js';
|
|
12
|
+
import {terminalMainPaths} from './item-lock.js';
|
|
13
|
+
import type {SidecarType} from './sidecar.js';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* The **reaper** for job worktrees, governed by the **provably-safe deletion
|
|
@@ -140,8 +142,23 @@ export function evaluateDeletionSafety(
|
|
|
140
142
|
|
|
141
143
|
const localTip = revParse(input.dir, 'HEAD', env);
|
|
142
144
|
|
|
143
|
-
// 2a. Merged: the tip is
|
|
144
|
-
|
|
145
|
+
// 2a. Merged: the tip is provably on <arbiter>/main — either a plain
|
|
146
|
+
// ancestor (fast path) OR squash-aware-equivalent (the durable done/
|
|
147
|
+
// dropped record is on main AND the branch carries nothing main lacks).
|
|
148
|
+
const arbiterMainRef = `refs/remotes/${arbiter}/main`;
|
|
149
|
+
const reapIdentity = reapIdentityFromBranch(input.branch);
|
|
150
|
+
if (
|
|
151
|
+
reapIdentity !== undefined
|
|
152
|
+
? isProvablyMergedForReap({
|
|
153
|
+
cwd: input.dir,
|
|
154
|
+
tip: localTip,
|
|
155
|
+
arbiterMain: arbiterMainRef,
|
|
156
|
+
namespace: reapIdentity.namespace,
|
|
157
|
+
slug: reapIdentity.slug,
|
|
158
|
+
env,
|
|
159
|
+
})
|
|
160
|
+
: isAncestor(input.dir, localTip, arbiterMainRef, env)
|
|
161
|
+
) {
|
|
145
162
|
return {safe: true, reachableVia: 'merged'};
|
|
146
163
|
}
|
|
147
164
|
|
|
@@ -432,14 +449,160 @@ function fetchTracking(
|
|
|
432
449
|
);
|
|
433
450
|
}
|
|
434
451
|
|
|
452
|
+
/**
|
|
453
|
+
* Extract the `{namespace, slug}` identity a reap decision needs from a work
|
|
454
|
+
* branch ref (`work/[<producer>-]<type>-<slug>`). Returns `undefined` for a
|
|
455
|
+
* non-namespaced branch — the caller then falls back to the ancestry-only
|
|
456
|
+
* fast-path (the squash-aware fallback has no anchor without an identity to
|
|
457
|
+
* look up on `<arbiter>/main`, so the safe direction is the strict old rule).
|
|
458
|
+
*/
|
|
459
|
+
function reapIdentityFromBranch(
|
|
460
|
+
branch: string,
|
|
461
|
+
): {namespace: SidecarType; slug: string} | undefined {
|
|
462
|
+
const parsed = parseWorkBranchRef(branch);
|
|
463
|
+
if (parsed === undefined) {
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
// `SlugNamespace` and `SidecarType` share the same three members
|
|
467
|
+
// (`'task' | 'spec' | 'observation'`); the cast is structural.
|
|
468
|
+
return {namespace: parsed.namespace as SidecarType, slug: parsed.slug};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** Input for {@link isProvablyMergedForReap}. */
|
|
472
|
+
export interface ProvablyMergedForReapInput {
|
|
473
|
+
/** The local repo the git predicates run in. */
|
|
474
|
+
cwd: string;
|
|
475
|
+
/** The candidate branch tip (a 40-hex sha or a resolvable ref). */
|
|
476
|
+
tip: string;
|
|
477
|
+
/**
|
|
478
|
+
* The arbiter's main as a ref RESOLVABLE INSIDE `cwd` — a fetched
|
|
479
|
+
* remote-tracking ref (`refs/remotes/<arbiter>/main`) or short-form
|
|
480
|
+
* (`<arbiter>/main`). Callers are responsible for the fetch (best-effort;
|
|
481
|
+
* an unresolvable ref reads as not-provable ⇒ false).
|
|
482
|
+
*/
|
|
483
|
+
arbiterMain: string;
|
|
484
|
+
/**
|
|
485
|
+
* The item's type (task/spec/observation) — used to look up the durable
|
|
486
|
+
* terminal records on `<arbiter>/main`. Reap decisions on a
|
|
487
|
+
* `work/<type>-<slug>` branch pass the parsed type from
|
|
488
|
+
* {@link parseWorkBranchRef}.
|
|
489
|
+
*/
|
|
490
|
+
namespace: SidecarType;
|
|
491
|
+
/** The item's bare slug — the anchor for the squash-aware fallback. */
|
|
492
|
+
slug: string;
|
|
493
|
+
env?: NodeJS.ProcessEnv;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* The **squash-aware, provably-merged predicate** used to decide reap-safety
|
|
498
|
+
* across the workspace: `gc.ts`'s worktree reaper, `reap-branches.ts`'s remote
|
|
499
|
+
* `work/*` sweep, `integrator.ts`'s post-merge remote-head reap, and
|
|
500
|
+
* `complete.ts`'s local-branch reap. ONE decision point so the two rules can
|
|
501
|
+
* never drift apart, and NEVER `--force` — a merged-for-reap ref needs none.
|
|
502
|
+
*
|
|
503
|
+
* A remote/local `work/<slug>` branch is reapable iff EITHER:
|
|
504
|
+
*
|
|
505
|
+
* 1. **Fast path (unchanged).** `git merge-base --is-ancestor <tip>
|
|
506
|
+
* <arbiter>/main` succeeds — the fast-forward / true-merge land case.
|
|
507
|
+
* 2. **Squash-aware fallback (new).** BOTH of the following hold:
|
|
508
|
+
* - the item is TERMINAL on `<arbiter>/main` — i.e. the matching
|
|
509
|
+
* {@link terminalMainPaths} (`work/tasks/done/<slug>.md` or
|
|
510
|
+
* `work/tasks/cancelled/<slug>.md` for a task; `work/specs/tasked/…`
|
|
511
|
+
* or `work/specs/dropped/…` for a spec) exists in the fetched main
|
|
512
|
+
* tree; AND
|
|
513
|
+
* - the branch carries NOTHING main lacks — either `git diff --quiet
|
|
514
|
+
* <arbiter>/main <tip>` reads clean (content-equal / content-subset)
|
|
515
|
+
* OR `git cherry <arbiter>/main <tip>` reports every commit as
|
|
516
|
+
* already applied by patch-id (`-` on every line, catching
|
|
517
|
+
* rebase-lands the tree check misses).
|
|
518
|
+
*
|
|
519
|
+
* Grounding: the durable `<arbiter>/main` record is authoritative. It is what
|
|
520
|
+
* `complete` writes FIRST, so if the terminal record is there the item's work
|
|
521
|
+
* is at rest, and a branch that carries nothing main lacks has no in-flight
|
|
522
|
+
* work to protect. The safety floor is unchanged: NEVER reap an in-flight /
|
|
523
|
+
* unmerged branch (no terminal record ⇒ retain), NEVER reap a branch carrying
|
|
524
|
+
* commits main lacks (content check fails ⇒ retain), NEVER `--force`.
|
|
525
|
+
*
|
|
526
|
+
* This is the branch-side twin of the lock-side terminal-on-main recovery
|
|
527
|
+
* (`isTerminalOnMain` in `item-lock.ts`): same anchor, different subject.
|
|
528
|
+
*/
|
|
529
|
+
export function isProvablyMergedForReap(
|
|
530
|
+
input: ProvablyMergedForReapInput,
|
|
531
|
+
): boolean {
|
|
532
|
+
const {cwd, tip, arbiterMain, namespace, slug, env} = input;
|
|
533
|
+
|
|
534
|
+
// 1. Fast path — the fast-forward / true-merge land case. Cheap: one
|
|
535
|
+
// `git merge-base --is-ancestor`.
|
|
536
|
+
if (isAncestor(cwd, tip, arbiterMain, env)) {
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// 2. Squash-aware fallback. Anchor: the durable terminal record on
|
|
541
|
+
// `<arbiter>/main`. No terminal record ⇒ the item is still in-flight (or
|
|
542
|
+
// an observation, which has no terminal folder) ⇒ retain (never reap
|
|
543
|
+
// in-flight work).
|
|
544
|
+
if (!terminalRecordOnMain(cwd, arbiterMain, namespace, slug, env)) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 2a. Content-subset: `git diff --quiet <arbiterMain> <tip>` clean ⇒ the
|
|
549
|
+
// two trees are identical, so the branch carries nothing main lacks
|
|
550
|
+
// (the incident shape: the branch is a since-lagged snapshot of a
|
|
551
|
+
// squash-landed item, no new content on it).
|
|
552
|
+
const diff = run('git', ['diff', '--quiet', arbiterMain, tip], cwd, {env});
|
|
553
|
+
if (diff.status === 0) {
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// 2b. Patch-id equivalence via `git cherry <upstream> <head>`: every commit
|
|
558
|
+
// reachable from `<tip>` but not `<arbiterMain>` is reported as `-`
|
|
559
|
+
// (already applied upstream by patch-id). This catches rebase-lands
|
|
560
|
+
// where the tree diff is non-empty but every commit is already on main
|
|
561
|
+
// under a different sha.
|
|
562
|
+
const cherry = run('git', ['cherry', arbiterMain, tip], cwd, {env});
|
|
563
|
+
if (cherry.status === 0) {
|
|
564
|
+
const lines = cherry.stdout
|
|
565
|
+
.split('\n')
|
|
566
|
+
.map((l) => l.trim())
|
|
567
|
+
.filter((l) => l !== '');
|
|
568
|
+
if (lines.length > 0 && lines.every((l) => l.startsWith('- '))) {
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** True iff `<arbiterMain>` carries ANY of the item's durable terminal records
|
|
577
|
+
* ({@link terminalMainPaths}). Best-effort: `cat-file -e` is a cheap tree
|
|
578
|
+
* probe; an unresolvable `<arbiterMain>` reads as no-record ⇒ retain (the safe
|
|
579
|
+
* direction). Observations have no terminal folder ⇒ always false. */
|
|
580
|
+
function terminalRecordOnMain(
|
|
581
|
+
cwd: string,
|
|
582
|
+
arbiterMain: string,
|
|
583
|
+
namespace: SidecarType,
|
|
584
|
+
slug: string,
|
|
585
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
586
|
+
): boolean {
|
|
587
|
+
for (const path of terminalMainPaths(namespace, slug)) {
|
|
588
|
+
const res = run('git', ['cat-file', '-e', `${arbiterMain}:${path}`], cwd, {
|
|
589
|
+
env,
|
|
590
|
+
});
|
|
591
|
+
if (res.status === 0) {
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
|
|
435
598
|
/**
|
|
436
599
|
* True iff `ancestor` is an ancestor of (or equal to) `descendant`, both
|
|
437
|
-
* resolved IN the local repo `dir` (`git merge-base --is-ancestor`).
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
600
|
+
* resolved IN the local repo `dir` (`git merge-base --is-ancestor`). The
|
|
601
|
+
* low-level fast-path building block for {@link isProvablyMergedForReap} —
|
|
602
|
+
* reap-safety decisions across the workspace go through the squash-aware
|
|
603
|
+
* wrapper, never this raw predicate. EXPORTED so callers that legitimately
|
|
604
|
+
* need pure ancestry (non-reap idempotency checks, claim-reachability guards)
|
|
605
|
+
* keep the one shared implementation.
|
|
443
606
|
*/
|
|
444
607
|
export function isAncestor(
|
|
445
608
|
dir: string,
|
package/src/github.ts
CHANGED
|
@@ -209,6 +209,26 @@ export class GitHubProvider implements ReviewProvider {
|
|
|
209
209
|
return this.parseOpened(input, first);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
// PR ALREADY EXISTS (task `committed-recovery-completes-existing-pr`): a
|
|
213
|
+
// re-pushed branch whose PR is still open makes `gh pr create` fail with
|
|
214
|
+
// "a pull request for branch ... already exists". This is NOT an outage and
|
|
215
|
+
// NOT a missing/unauth `gh` — retrying is pointless (it fails identically)
|
|
216
|
+
// and degrading to push-only would DROP the refreshed body + the Gate-2
|
|
217
|
+
// review comment on a PR that genuinely exists (the committed-recovery re-run
|
|
218
|
+
// case). Instead RESOLVE the existing PR, UPDATE its title/body to match what
|
|
219
|
+
// this run would have created, and return it as `opened` so the caller's
|
|
220
|
+
// review-comment path fires normally. NEVER throws; a failed edit still
|
|
221
|
+
// returns the resolved PR (the comment is the more important audit trail).
|
|
222
|
+
if (prAlreadyExists(first)) {
|
|
223
|
+
const existing = this.updateExistingRequest(input);
|
|
224
|
+
if (existing !== undefined) {
|
|
225
|
+
return existing;
|
|
226
|
+
}
|
|
227
|
+
// Could not resolve the existing PR's url (e.g. a transient `gh pr view`
|
|
228
|
+
// failure) — fall through to the degrade path rather than crashing; the
|
|
229
|
+
// branch is already pushed, so the work is safe.
|
|
230
|
+
}
|
|
231
|
+
|
|
212
232
|
// Failed once — distinguish DETERMINISTIC (missing/unauth) from a TRANSIENT
|
|
213
233
|
// outage by an availability probe. Missing/unauth ⇒ degrade now (no retry).
|
|
214
234
|
// Thread the FAILED first attempt so the degrade surfaces the REAL `gh` cause
|
|
@@ -246,6 +266,47 @@ export class GitHubProvider implements ReviewProvider {
|
|
|
246
266
|
return this.parseOpened(input, attempt.value);
|
|
247
267
|
}
|
|
248
268
|
|
|
269
|
+
/**
|
|
270
|
+
* The PR for this branch ALREADY EXISTS (task
|
|
271
|
+
* `committed-recovery-completes-existing-pr`): resolve its url, REFRESH its
|
|
272
|
+
* title/body via `gh pr edit` (so a committed-recovery re-run's synthesised
|
|
273
|
+
* title + task-pointer body replace the earlier run's blank `--fill` ones),
|
|
274
|
+
* and return it as `{opened: true, url}` so the caller's review-comment path
|
|
275
|
+
* fires on the existing PR. Returns `undefined` when the url cannot be
|
|
276
|
+
* resolved (the caller then falls through to the degrade path). The `gh pr
|
|
277
|
+
* edit` is best-effort: a failed edit still returns the resolved PR (posting
|
|
278
|
+
* the review comment matters more than refreshing the body), and it is only
|
|
279
|
+
* attempted when this run actually has explicit title/body to set (else the
|
|
280
|
+
* existing PR's content is left untouched).
|
|
281
|
+
*/
|
|
282
|
+
private updateExistingRequest(
|
|
283
|
+
input: OpenRequestInput,
|
|
284
|
+
): OpenRequestResult | undefined {
|
|
285
|
+
const url = this.resolvePrUrlForBranch(input.branch, input.cwd, input.env);
|
|
286
|
+
if (url === undefined) {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
// Refresh the PR body/title only when this run supplies them (a committed-
|
|
290
|
+
// recovery re-run does; an unset run leaves the existing content alone).
|
|
291
|
+
if (input.title !== undefined || input.body !== undefined) {
|
|
292
|
+
const editArgs = ['pr', 'edit', url];
|
|
293
|
+
if (input.title !== undefined) {
|
|
294
|
+
editArgs.push('--title', input.title);
|
|
295
|
+
}
|
|
296
|
+
if (input.body !== undefined) {
|
|
297
|
+
editArgs.push('--body', input.body);
|
|
298
|
+
}
|
|
299
|
+
// Best-effort: ignore the result. A failed edit must not lose the PR (or
|
|
300
|
+
// the upcoming review comment) — NEVER throws (runGh swallows spawn errors).
|
|
301
|
+
this.runGh(editArgs, input.cwd, input.env);
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
opened: true,
|
|
305
|
+
url,
|
|
306
|
+
instruction: `Updated the existing GitHub PR for ${input.branch}: ${url}`,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
249
310
|
/** Map a successful `gh pr create` RunResult to an OpenRequestResult. */
|
|
250
311
|
private parseOpened(
|
|
251
312
|
input: OpenRequestInput,
|
|
@@ -457,6 +518,23 @@ export function prCreateContentArgs(input: {
|
|
|
457
518
|
return ['--title', input.title ?? '', '--body', input.body ?? ''];
|
|
458
519
|
}
|
|
459
520
|
|
|
521
|
+
/**
|
|
522
|
+
* Does a FAILED `gh pr create` result mean "a PR for this branch already
|
|
523
|
+
* exists" (task `committed-recovery-completes-existing-pr`)? `gh` exits non-zero
|
|
524
|
+
* with a message like `a pull request for branch "work/task-x" into branch
|
|
525
|
+
* "main" already exists: https://github.com/.../pull/357`. We match on the
|
|
526
|
+
* stable "already exists" phrase in `gh`'s stderr/stdout so a re-pushed branch
|
|
527
|
+
* whose PR is still open is treated as an UPDATE, not a transient outage. A
|
|
528
|
+
* missing `gh` (`undefined`) or a success is not this case.
|
|
529
|
+
*/
|
|
530
|
+
function prAlreadyExists(result: RunResult | undefined): boolean {
|
|
531
|
+
if (result === undefined || result.status === 0) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
const text = `${result.stderr ?? ''}\n${result.stdout ?? ''}`.toLowerCase();
|
|
535
|
+
return text.includes('already exists');
|
|
536
|
+
}
|
|
537
|
+
|
|
460
538
|
/**
|
|
461
539
|
* Parse the PR URL `gh pr create` prints to stdout. `gh` prints the PR URL as a
|
|
462
540
|
* line (often the only/last line); we pick the first GitHub PR URL we find. Any
|
package/src/harness.ts
CHANGED
|
@@ -61,6 +61,21 @@ export interface LaunchInput {
|
|
|
61
61
|
* `undefined` ⇒ the pi adapter falls back to a generated default for its cwd.
|
|
62
62
|
*/
|
|
63
63
|
session?: string;
|
|
64
|
+
/**
|
|
65
|
+
* **Optional agent-session DEADLINE** (absolute epoch-ms wall-clock timestamp)
|
|
66
|
+
* for the ASYNC {@link Harness} launch (spec
|
|
67
|
+
* `graceful-pre-timeout-wip-checkpoint`). When set AND the future arrives
|
|
68
|
+
* before the child exits, the harness sends SIGTERM (a ~10s grace, then
|
|
69
|
+
* SIGKILL) and resolves the {@link LaunchResult} with `timedOut: true` — so
|
|
70
|
+
* the caller can route the run as a CHECKPOINT (save WIP + auto-continue /
|
|
71
|
+
* surface) rather than a generic agent-failure. Ignored on the synchronous
|
|
72
|
+
* {@link Harness.launch} path (`spawnSync` cannot be interrupted from the
|
|
73
|
+
* same event loop; the async path is where CI's `--watch` legs run and where
|
|
74
|
+
* the checkpoint discipline lives). Absent ⇒ no deadline (byte-for-byte the
|
|
75
|
+
* pre-task behaviour: a run finishing before its deadline is unchanged; on
|
|
76
|
+
* `exit` the timer is cleared).
|
|
77
|
+
*/
|
|
78
|
+
deadlineMs?: number;
|
|
64
79
|
env?: NodeJS.ProcessEnv;
|
|
65
80
|
}
|
|
66
81
|
|
|
@@ -170,6 +185,16 @@ export interface LaunchResult {
|
|
|
170
185
|
record: HarnessRecord;
|
|
171
186
|
/** Failure detail when `ok` is false (the stderr/failure channel). */
|
|
172
187
|
detail?: string;
|
|
188
|
+
/**
|
|
189
|
+
* **True iff the launch was stopped by the dorfl-internal DEADLINE**
|
|
190
|
+
* ({@link LaunchInput.deadlineMs}) rather than exiting on its own (spec
|
|
191
|
+
* `graceful-pre-timeout-wip-checkpoint`). Distinct from `ok` (a deadline
|
|
192
|
+
* stop is neither a success nor a generic failure — it is a CHECKPOINT the
|
|
193
|
+
* caller routes through the save+auto-continue/surface path). Absent/false
|
|
194
|
+
* when the child exited before the deadline, so a run that finishes
|
|
195
|
+
* normally is byte-for-byte unchanged.
|
|
196
|
+
*/
|
|
197
|
+
timedOut?: boolean;
|
|
173
198
|
/**
|
|
174
199
|
* The agent's final ANSWER (task `harness-agent-output`): the concatenated
|
|
175
200
|
* `text` of the LAST assistant turn the invocation produced. This is the
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,8 @@ export type {
|
|
|
26
26
|
} from './repo-config.js';
|
|
27
27
|
export {
|
|
28
28
|
REPO_CONFIG_FILENAME,
|
|
29
|
+
REPO_CONFIG_FILENAME_LEGACY,
|
|
30
|
+
resolveRepoConfigPath,
|
|
29
31
|
REPO_ALLOWED_KEYS,
|
|
30
32
|
REPO_REJECTED_KEYS,
|
|
31
33
|
repoConfigPath,
|
|
@@ -783,3 +785,11 @@ export type {
|
|
|
783
785
|
InstallCIResult,
|
|
784
786
|
} from './install-ci.js';
|
|
785
787
|
export {installCI, runWizard} from './install-ci.js';
|
|
788
|
+
|
|
789
|
+
export type {
|
|
790
|
+
InstallSkillsOptions,
|
|
791
|
+
InstallSkillsResult,
|
|
792
|
+
InstalledAgent,
|
|
793
|
+
} from './install-skills.js';
|
|
794
|
+
export {installSkills, resolveSkillsSourceDir} from './install-skills.js';
|
|
795
|
+
export type {Agent} from './vendor/incur/agents.js';
|