dorfl 0.14.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advance-ci-template.d.ts +17 -16
- package/dist/advance-ci-template.d.ts.map +1 -1
- package/dist/advance-ci-template.js +42 -53
- package/dist/advance-ci-template.js.map +1 -1
- package/dist/advance-drivers.d.ts +8 -0
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +28 -4
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-isolated.d.ts.map +1 -1
- package/dist/advance-isolated.js +3 -2
- package/dist/advance-isolated.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts +41 -40
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +309 -489
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-loop-driver.d.ts.map +1 -1
- package/dist/advance-loop-driver.js +3 -2
- package/dist/advance-loop-driver.js.map +1 -1
- package/dist/advance-treeless-publish.d.ts +31 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -1
- package/dist/advance-treeless-publish.js +40 -0
- package/dist/advance-treeless-publish.js.map +1 -1
- package/dist/advance.d.ts +63 -3
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +80 -12
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +9 -0
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +21 -1
- package/dist/advancing-lock.js.map +1 -1
- package/dist/agent-env.d.ts +61 -0
- package/dist/agent-env.d.ts.map +1 -0
- package/dist/agent-env.js +144 -0
- package/dist/agent-env.js.map +1 -0
- package/dist/agent-stop.d.ts +35 -0
- package/dist/agent-stop.d.ts.map +1 -1
- package/dist/agent-stop.js +62 -0
- package/dist/agent-stop.js.map +1 -1
- package/dist/apply-merge-action.d.ts +76 -34
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +339 -129
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-stuck-action.js +2 -2
- package/dist/apply-stuck-action.js.map +1 -1
- package/dist/ci-agent-result.d.ts +151 -0
- package/dist/ci-agent-result.d.ts.map +1 -0
- package/dist/ci-agent-result.js +314 -0
- package/dist/ci-agent-result.js.map +1 -0
- package/dist/ci-handoff-apply.d.ts +162 -0
- package/dist/ci-handoff-apply.d.ts.map +1 -0
- package/dist/ci-handoff-apply.js +497 -0
- package/dist/ci-handoff-apply.js.map +1 -0
- package/dist/ci-handoff-format.d.ts +369 -0
- package/dist/ci-handoff-format.d.ts.map +1 -0
- package/dist/ci-handoff-format.js +646 -0
- package/dist/ci-handoff-format.js.map +1 -0
- package/dist/ci-handoff-lfs.d.ts +146 -0
- package/dist/ci-handoff-lfs.d.ts.map +1 -0
- package/dist/ci-handoff-lfs.js +397 -0
- package/dist/ci-handoff-lfs.js.map +1 -0
- package/dist/ci-handoff.d.ts +99 -0
- package/dist/ci-handoff.d.ts.map +1 -0
- package/dist/ci-handoff.js +277 -0
- package/dist/ci-handoff.js.map +1 -0
- package/dist/ci-lock-outputs.d.ts +93 -0
- package/dist/ci-lock-outputs.d.ts.map +1 -0
- package/dist/ci-lock-outputs.js +223 -0
- package/dist/ci-lock-outputs.js.map +1 -0
- package/dist/ci-phase-build.d.ts +178 -0
- package/dist/ci-phase-build.d.ts.map +1 -0
- package/dist/ci-phase-build.js +989 -0
- package/dist/ci-phase-build.js.map +1 -0
- package/dist/ci-phase-driver.d.ts +247 -0
- package/dist/ci-phase-driver.d.ts.map +1 -0
- package/dist/ci-phase-driver.js +370 -0
- package/dist/ci-phase-driver.js.map +1 -0
- package/dist/ci-phase-intake.d.ts +172 -0
- package/dist/ci-phase-intake.d.ts.map +1 -0
- package/dist/ci-phase-intake.js +555 -0
- package/dist/ci-phase-intake.js.map +1 -0
- package/dist/ci-phase-tasking.d.ts +205 -0
- package/dist/ci-phase-tasking.d.ts.map +1 -0
- package/dist/ci-phase-tasking.js +906 -0
- package/dist/ci-phase-tasking.js.map +1 -0
- package/dist/ci-phase-treeless.d.ts +224 -0
- package/dist/ci-phase-treeless.d.ts.map +1 -0
- package/dist/ci-phase-treeless.js +1482 -0
- package/dist/ci-phase-treeless.js.map +1 -0
- package/dist/ci-read-token.d.ts +47 -0
- package/dist/ci-read-token.d.ts.map +1 -0
- package/dist/ci-read-token.js +80 -0
- package/dist/ci-read-token.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +447 -22
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.d.ts +10 -1
- package/dist/close-job-template.d.ts.map +1 -1
- package/dist/close-job-template.js +46 -14
- package/dist/close-job-template.js.map +1 -1
- package/dist/close-job.d.ts +20 -5
- package/dist/close-job.d.ts.map +1 -1
- package/dist/close-job.js +65 -12
- package/dist/close-job.js.map +1 -1
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +20 -1
- package/dist/complete.js.map +1 -1
- package/dist/config.d.ts +41 -44
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -4
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts +63 -15
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +75 -8
- package/dist/continue-branch.js.map +1 -1
- package/dist/crashed-run-save.d.ts +73 -0
- package/dist/crashed-run-save.d.ts.map +1 -0
- package/dist/crashed-run-save.js +86 -0
- package/dist/crashed-run-save.js.map +1 -0
- package/dist/cwd-section.d.ts.map +1 -1
- package/dist/cwd-section.js +10 -2
- package/dist/cwd-section.js.map +1 -1
- package/dist/decision-engine.d.ts +10 -0
- package/dist/decision-engine.d.ts.map +1 -1
- package/dist/decision-engine.js.map +1 -1
- package/dist/do-config.d.ts +2 -24
- package/dist/do-config.d.ts.map +1 -1
- package/dist/do-config.js +1 -33
- package/dist/do-config.js.map +1 -1
- package/dist/do.d.ts +149 -1
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +115 -25
- package/dist/do.js.map +1 -1
- package/dist/dorfl-item-template.d.ts +98 -0
- package/dist/dorfl-item-template.d.ts.map +1 -0
- package/dist/dorfl-item-template.js +504 -0
- package/dist/dorfl-item-template.js.map +1 -0
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +7 -5
- package/dist/env-config.js.map +1 -1
- package/dist/frontmatter.d.ts +52 -0
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +136 -2
- package/dist/frontmatter.js.map +1 -1
- package/dist/gc.d.ts +13 -0
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +16 -2
- package/dist/gc.js.map +1 -1
- package/dist/git.d.ts +18 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +70 -5
- package/dist/git.js.map +1 -1
- package/dist/harness.d.ts +44 -5
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +24 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/install-ci-action-pins.d.ts +123 -0
- package/dist/install-ci-action-pins.d.ts.map +1 -0
- package/dist/install-ci-action-pins.js +190 -0
- package/dist/install-ci-action-pins.js.map +1 -0
- package/dist/install-ci-actions-pr-creation.d.ts +70 -0
- package/dist/install-ci-actions-pr-creation.d.ts.map +1 -0
- package/dist/install-ci-actions-pr-creation.js +106 -0
- package/dist/install-ci-actions-pr-creation.js.map +1 -0
- package/dist/install-ci-capabilities/dorfl-item.d.ts +12 -0
- package/dist/install-ci-capabilities/dorfl-item.d.ts.map +1 -0
- package/dist/install-ci-capabilities/dorfl-item.js +26 -0
- package/dist/install-ci-capabilities/dorfl-item.js.map +1 -0
- package/dist/install-ci-core.d.ts +155 -37
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +304 -200
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +47 -2
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +113 -1
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-ci.d.ts +25 -6
- package/dist/install-ci.d.ts.map +1 -1
- package/dist/install-ci.js +72 -36
- package/dist/install-ci.js.map +1 -1
- package/dist/intake-marker.d.ts +6 -1
- package/dist/intake-marker.d.ts.map +1 -1
- package/dist/intake-marker.js.map +1 -1
- package/dist/intake-triage.d.ts.map +1 -1
- package/dist/intake-triage.js +6 -2
- package/dist/intake-triage.js.map +1 -1
- package/dist/intake-trigger-template.d.ts +31 -25
- package/dist/intake-trigger-template.d.ts.map +1 -1
- package/dist/intake-trigger-template.js +107 -292
- package/dist/intake-trigger-template.js.map +1 -1
- package/dist/intake.d.ts +186 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +533 -416
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +154 -1
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +759 -486
- package/dist/integration-core.js.map +1 -1
- package/dist/integrator.d.ts +12 -0
- package/dist/integrator.d.ts.map +1 -1
- package/dist/integrator.js +21 -0
- package/dist/integrator.js.map +1 -1
- package/dist/isolation.d.ts +12 -2
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +19 -8
- package/dist/isolation.js.map +1 -1
- package/dist/item-lock.d.ts +93 -11
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +233 -65
- package/dist/item-lock.js.map +1 -1
- package/dist/land-question-residue.d.ts +58 -0
- package/dist/land-question-residue.d.ts.map +1 -0
- package/dist/land-question-residue.js +95 -0
- package/dist/land-question-residue.js.map +1 -0
- package/dist/lifecycle-gather.d.ts +16 -0
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +54 -6
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/merge-answer.d.ts +41 -0
- package/dist/merge-answer.d.ts.map +1 -0
- package/dist/merge-answer.js +60 -0
- package/dist/merge-answer.js.map +1 -0
- package/dist/merge-question-surfacer.d.ts +105 -30
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +202 -91
- package/dist/merge-question-surfacer.js.map +1 -1
- package/dist/merge-question-tick.d.ts +64 -0
- package/dist/merge-question-tick.d.ts.map +1 -0
- package/dist/merge-question-tick.js +96 -0
- package/dist/merge-question-tick.js.map +1 -0
- package/dist/mint-adr.d.ts.map +1 -1
- package/dist/mint-adr.js +20 -2
- package/dist/mint-adr.js.map +1 -1
- package/dist/needs-attention.d.ts +72 -2
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +275 -38
- package/dist/needs-attention.js.map +1 -1
- package/dist/phase-recorder.d.ts +168 -0
- package/dist/phase-recorder.d.ts.map +1 -0
- package/dist/phase-recorder.js +218 -0
- package/dist/phase-recorder.js.map +1 -0
- package/dist/phase.d.ts +94 -0
- package/dist/phase.d.ts.map +1 -0
- package/dist/phase.js +148 -0
- package/dist/phase.js.map +1 -0
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +19 -6
- package/dist/pi-harness.js.map +1 -1
- package/dist/prepare.d.ts.map +1 -1
- package/dist/prepare.js +4 -0
- package/dist/prepare.js.map +1 -1
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +48 -1
- package/dist/prompt.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +3 -1
- package/dist/protocol/WORK-CONTRACT.md +1 -1
- package/dist/reap-branches.d.ts +48 -0
- package/dist/reap-branches.d.ts.map +1 -1
- package/dist/reap-branches.js +64 -0
- package/dist/reap-branches.js.map +1 -1
- package/dist/ref-write.d.ts +198 -0
- package/dist/ref-write.d.ts.map +1 -0
- package/dist/ref-write.js +79 -0
- package/dist/ref-write.js.map +1 -0
- package/dist/repo-config.js +8 -8
- package/dist/repo-config.js.map +1 -1
- package/dist/repo-mirror.d.ts +36 -0
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +91 -1
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-verdict.d.ts +1 -1
- package/dist/review-verdict.js +1 -1
- package/dist/run.d.ts +7 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +28 -4
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +19 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +21 -8
- package/dist/scan.js.map +1 -1
- package/dist/sidecar.d.ts +14 -0
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +21 -0
- package/dist/sidecar.js.map +1 -1
- package/dist/single-job-warning.d.ts +63 -0
- package/dist/single-job-warning.d.ts.map +1 -0
- package/dist/single-job-warning.js +110 -0
- package/dist/single-job-warning.js.map +1 -0
- package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +3 -1
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +1 -1
- package/dist/spec-complete.d.ts +13 -6
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +23 -11
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +47 -14
- package/dist/start.js.map +1 -1
- package/dist/status.d.ts +12 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +11 -3
- package/dist/status.js.map +1 -1
- package/dist/tasking.d.ts +246 -1
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +452 -358
- package/dist/tasking.js.map +1 -1
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +21 -2
- package/dist/triage-persist.js.map +1 -1
- package/dist/verify-workflow-template.d.ts.map +1 -1
- package/dist/verify-workflow-template.js +8 -4
- package/dist/verify-workflow-template.js.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +4 -0
- package/dist/verify.js.map +1 -1
- package/dist/watch-session.d.ts +42 -13
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +52 -23
- package/dist/watch-session.js.map +1 -1
- package/dist/work-layout.d.ts +10 -4
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +11 -3
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +65 -4
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +159 -11
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
- package/src/advance-ci-template.ts +80 -81
- package/src/advance-drivers.ts +45 -6
- package/src/advance-isolated.ts +3 -5
- package/src/advance-lifecycle-template.ts +375 -538
- package/src/advance-loop-driver.ts +3 -5
- package/src/advance-treeless-publish.ts +61 -0
- package/src/advance.ts +145 -15
- package/src/advancing-lock.ts +36 -1
- package/src/agent-env.ts +174 -0
- package/src/agent-stop.ts +81 -0
- package/src/apply-merge-action.ts +401 -154
- package/src/apply-stuck-action.ts +2 -2
- package/src/ci-agent-result.ts +421 -0
- package/src/ci-handoff-apply.ts +737 -0
- package/src/ci-handoff-format.ts +926 -0
- package/src/ci-handoff-lfs.ts +519 -0
- package/src/ci-handoff.ts +392 -0
- package/src/ci-lock-outputs.ts +273 -0
- package/src/ci-phase-build.ts +1328 -0
- package/src/ci-phase-driver.ts +548 -0
- package/src/ci-phase-intake.ts +861 -0
- package/src/ci-phase-tasking.ts +1255 -0
- package/src/ci-phase-treeless.ts +2028 -0
- package/src/ci-read-token.ts +90 -0
- package/src/cli.ts +591 -29
- package/src/close-job-template.ts +52 -15
- package/src/close-job.ts +77 -12
- package/src/complete.ts +19 -1
- package/src/config.ts +43 -48
- package/src/continue-branch.ts +121 -16
- package/src/crashed-run-save.ts +162 -0
- package/src/cwd-section.ts +10 -0
- package/src/decision-engine.ts +10 -0
- package/src/do-config.ts +2 -57
- package/src/do.ts +170 -28
- package/src/dorfl-item-template.ts +556 -0
- package/src/env-config.ts +7 -5
- package/src/frontmatter.ts +155 -2
- package/src/gc.ts +27 -2
- package/src/git.ts +83 -5
- package/src/harness.ts +58 -6
- package/src/index.ts +50 -1
- package/src/install-ci-action-pins.ts +209 -0
- package/src/install-ci-actions-pr-creation.ts +186 -0
- package/src/install-ci-capabilities/dorfl-item.ts +33 -0
- package/src/install-ci-core.ts +388 -233
- package/src/install-ci-github.ts +149 -1
- package/src/install-ci.ts +83 -37
- package/src/intake-marker.ts +6 -1
- package/src/intake-triage.ts +7 -3
- package/src/intake-trigger-template.ts +126 -327
- package/src/intake.ts +725 -544
- package/src/integration-core.ts +975 -523
- package/src/integrator.ts +28 -0
- package/src/isolation.ts +29 -10
- package/src/item-lock.ts +308 -92
- package/src/land-question-residue.ts +122 -0
- package/src/lifecycle-gather.ts +82 -10
- package/src/merge-answer.ts +82 -0
- package/src/merge-question-surfacer.ts +320 -112
- package/src/merge-question-tick.ts +182 -0
- package/src/mint-adr.ts +28 -2
- package/src/needs-attention.ts +350 -42
- package/src/phase-recorder.ts +350 -0
- package/src/phase.ts +177 -0
- package/src/pi-harness.ts +25 -5
- package/src/prepare.ts +4 -0
- package/src/prompt.ts +68 -1
- package/src/reap-branches.ts +107 -0
- package/src/ref-write.ts +324 -0
- package/src/repo-config.ts +8 -8
- package/src/repo-mirror.ts +107 -1
- package/src/review-verdict.ts +1 -1
- package/src/run.ts +47 -6
- package/src/scan.ts +37 -7
- package/src/sidecar.ts +36 -0
- package/src/single-job-warning.ts +135 -0
- package/src/spec-complete.ts +36 -14
- package/src/start.ts +55 -16
- package/src/status.ts +29 -3
- package/src/tasking.ts +593 -378
- package/src/triage-persist.ts +24 -2
- package/src/verify-workflow-template.ts +8 -4
- package/src/verify.ts +4 -0
- package/src/watch-session.ts +86 -28
- package/src/work-layout.ts +11 -3
- package/src/workspace.ts +210 -14
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import {existsSync, readFileSync} from 'node:fs';
|
|
1
|
+
import {existsSync, readFileSync, statSync} from 'node:fs';
|
|
2
2
|
import {join} from 'node:path';
|
|
3
3
|
import {
|
|
4
|
-
isEntryAnswered,
|
|
5
4
|
parseSidecar,
|
|
6
5
|
resolveSidecarIdentity,
|
|
7
6
|
sidecarPathFor,
|
|
8
|
-
type SidecarEntry,
|
|
9
7
|
type SidecarModel,
|
|
10
8
|
} from './sidecar.js';
|
|
11
9
|
import {run as runProc} from './git.js';
|
|
12
|
-
import {createJob, type Job} from './workspace.js';
|
|
10
|
+
import {createJob, withCheckoutCommitIdentity, type Job} from './workspace.js';
|
|
11
|
+
import {continueRebaseSentence} from './continue-branch.js';
|
|
12
|
+
import {ensureMirrorMain, type EnsureMirrorResult} from './repo-mirror.js';
|
|
13
13
|
import {
|
|
14
14
|
performIntegration,
|
|
15
|
+
type IntegrationCoreInput,
|
|
15
16
|
type IntegrationCoreResult,
|
|
16
17
|
} from './integration-core.js';
|
|
18
|
+
import {
|
|
19
|
+
changedFileBlobs,
|
|
20
|
+
lfsObjectPath,
|
|
21
|
+
localLfsObjectsDir,
|
|
22
|
+
scanLfsPointers,
|
|
23
|
+
} from './ci-handoff-lfs.js';
|
|
24
|
+
import {commitAbortedWork} from './needs-attention.js';
|
|
25
|
+
import {createPhaseRecorder, runAgentPhase} from './phase-recorder.js';
|
|
17
26
|
import type {VerifyConfig} from './verify.js';
|
|
18
27
|
|
|
19
28
|
/**
|
|
@@ -58,9 +67,9 @@ import type {VerifyConfig} from './verify.js';
|
|
|
58
67
|
* "HONOUR the prior approval + land on a green re-verify" path; opt-in
|
|
59
68
|
* `strictMergeApproval` (resolved per-repo via the gate-family precedence chain
|
|
60
69
|
* by the sibling task `strict-merge-approval-gate`, default OFF) RE-SURFACES
|
|
61
|
-
* the merge-question instead of landing when
|
|
62
|
-
*
|
|
63
|
-
* in both modes.
|
|
70
|
+
* the merge-question instead of landing when `main`'s code moved since the
|
|
71
|
+
* question was asked (its recorded `askedAtMain`; see `approvedBaseMoved`).
|
|
72
|
+
* The RED-re-verify refusal is unchanged in both modes.
|
|
64
73
|
*
|
|
65
74
|
* House-style boundary: this module is the DETERMINISTIC DISPATCH LAYER. It
|
|
66
75
|
* does NOT re-implement rebase / verify / integrate (it drives the EXISTING
|
|
@@ -72,48 +81,16 @@ import type {VerifyConfig} from './verify.js';
|
|
|
72
81
|
* needs-attention, that `verify` ran on the rebased tip).
|
|
73
82
|
*/
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Parse the human's plain free-text answer into the deterministic
|
|
88
|
-
* {@link MergeActionVerb}. The merge-question surfacer renders
|
|
89
|
-
* `merge | hold | drop` in the entry's `default` as a human-readable hint, so
|
|
90
|
-
* the human typically types one of those words verbatim. We accept any text
|
|
91
|
-
* whose first whole word (case-insensitive) is one of `merge` / `hold` / `drop`
|
|
92
|
-
* — this is the same machine-parseable choice shape the surfacer's applied
|
|
93
|
-
* answer q1 documents ("a DETERMINISTIC CHOICE shape … the human picks and the
|
|
94
|
-
* system parses unambiguously").
|
|
95
|
-
*
|
|
96
|
-
* Returns `undefined` on ANYTHING else (an empty answer, a typo, a long
|
|
97
|
-
* narrative without one of the three words at the start) so the caller can
|
|
98
|
-
* route the ambiguity HONESTLY — never default-to-merge on a malformed answer,
|
|
99
|
-
* which would invent a land the human did not authorise.
|
|
100
|
-
*/
|
|
101
|
-
export function parseMergeAnswer(text: string): MergeActionVerb | undefined {
|
|
102
|
-
const trimmed = text.trim().toLowerCase();
|
|
103
|
-
if (trimmed === '') return undefined;
|
|
104
|
-
// The first whole alphabetic word; trailing punctuation (a comma, em-dash,
|
|
105
|
-
// period) and any following commentary are tolerated. We deliberately do
|
|
106
|
-
// NOT consume hyphens / apostrophes — the three verbs are plain ASCII
|
|
107
|
-
// words, and over-tolerating typo-shapes would silently invent a verb the
|
|
108
|
-
// human did not pick.
|
|
109
|
-
const match = /^([a-z]+)/.exec(trimmed);
|
|
110
|
-
if (match === null) return undefined;
|
|
111
|
-
const word = match[1];
|
|
112
|
-
if (word === 'merge' || word === 'hold' || word === 'drop') {
|
|
113
|
-
return word;
|
|
114
|
-
}
|
|
115
|
-
return undefined;
|
|
116
|
-
}
|
|
84
|
+
export {
|
|
85
|
+
answeredMergeActionIn,
|
|
86
|
+
parseMergeAnswer,
|
|
87
|
+
type DetectedMergeAction,
|
|
88
|
+
type MergeActionVerb,
|
|
89
|
+
} from './merge-answer.js';
|
|
90
|
+
import {
|
|
91
|
+
answeredMergeActionIn,
|
|
92
|
+
type DetectedMergeAction,
|
|
93
|
+
} from './merge-answer.js';
|
|
117
94
|
|
|
118
95
|
/**
|
|
119
96
|
* Detect the `kind: merge` action verb that should drive the next apply run.
|
|
@@ -151,21 +128,7 @@ export function detectAnsweredMergeAction(
|
|
|
151
128
|
} catch {
|
|
152
129
|
return undefined;
|
|
153
130
|
}
|
|
154
|
-
|
|
155
|
-
for (const entry of model.entries) {
|
|
156
|
-
if (entry.kind === 'merge' && !isEntryAnswered(entry)) return undefined;
|
|
157
|
-
}
|
|
158
|
-
// (2) LATEST answered `kind: merge` entry — a fresh follow-up beats a stale one.
|
|
159
|
-
for (let i = model.entries.length - 1; i >= 0; i--) {
|
|
160
|
-
const entry = model.entries[i];
|
|
161
|
-
if (entry.kind !== 'merge') continue;
|
|
162
|
-
if (!isEntryAnswered(entry)) continue;
|
|
163
|
-
const verb = parseMergeAnswer(entry.answer);
|
|
164
|
-
if (verb !== undefined) {
|
|
165
|
-
return {verb, entry};
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return undefined;
|
|
131
|
+
return answeredMergeActionIn(model);
|
|
169
132
|
}
|
|
170
133
|
|
|
171
134
|
/** Input the production merge-action handler consumes. */
|
|
@@ -197,8 +160,8 @@ export interface MergeActionInput {
|
|
|
197
160
|
* `strictMergeApproval` (resolved per-repo by the sibling task
|
|
198
161
|
* `strict-merge-approval-gate`; default OFF). OFF ⇒ honour the prior answer
|
|
199
162
|
* + land on a green re-verify (the cheap default). ON ⇒ re-surface the
|
|
200
|
-
* merge-question when
|
|
201
|
-
*
|
|
163
|
+
* merge-question when `main`'s code moved since the question was asked
|
|
164
|
+
* (don't land; the apply rung folds this into a re-pause).
|
|
202
165
|
*/
|
|
203
166
|
strictMergeApproval?: boolean;
|
|
204
167
|
/** Bounded recovery-rebase retry knob (mirrors the build path). */
|
|
@@ -228,9 +191,9 @@ export type MergeActionOutcome =
|
|
|
228
191
|
*/
|
|
229
192
|
| 'refused'
|
|
230
193
|
/**
|
|
231
|
-
* `answer=merge` + `strictMergeApproval` ON +
|
|
232
|
-
*
|
|
233
|
-
*
|
|
194
|
+
* `answer=merge` + `strictMergeApproval` ON + `main`'s code moved since the
|
|
195
|
+
* question was asked: the dispatcher RE-SURFACES the merge-question (the
|
|
196
|
+
* apply rung appends a fresh follow-up asked at the new `main` + re-pauses).
|
|
234
197
|
*/
|
|
235
198
|
| 'restale'
|
|
236
199
|
/** `answer=hold` ⇒ no land; the apply rung records the answer in body as usual. */
|
|
@@ -250,6 +213,12 @@ export interface MergeActionResult {
|
|
|
250
213
|
* `already-integrated`, …), so callers can branch on it.
|
|
251
214
|
*/
|
|
252
215
|
integration?: IntegrationCoreResult;
|
|
216
|
+
/**
|
|
217
|
+
* On `restale`: the arbiter `main` the approval was found stale against. The
|
|
218
|
+
* apply rung records it as the re-surfaced question's `askedAtMain`, so a
|
|
219
|
+
* re-answer while `main` stays put lands (no re-stale livelock).
|
|
220
|
+
*/
|
|
221
|
+
main?: string;
|
|
253
222
|
}
|
|
254
223
|
|
|
255
224
|
/**
|
|
@@ -282,50 +251,77 @@ function resolveArbiterUrl(input: MergeActionInput): string | undefined {
|
|
|
282
251
|
return url === '' ? undefined : url;
|
|
283
252
|
}
|
|
284
253
|
|
|
254
|
+
/** The verdict of {@link approvedBaseMoved}. */
|
|
255
|
+
interface ApprovedBaseCheck {
|
|
256
|
+
/** `main`'s code moved since the answered question was asked ⇒ re-surface. */
|
|
257
|
+
stale: boolean;
|
|
258
|
+
/** The arbiter `main` the check compared against (when it was read). */
|
|
259
|
+
main?: string;
|
|
260
|
+
}
|
|
261
|
+
|
|
285
262
|
/**
|
|
286
|
-
* The `strictMergeApproval` re-stale check (SPEC OQ6 opt-in
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
* approvals when the base changes"
|
|
263
|
+
* The `strictMergeApproval` re-stale check (SPEC OQ6 opt-in; task
|
|
264
|
+
* `strict-merge-approval-restale-check-runs-before-the-continue-rebase`): did
|
|
265
|
+
* `main` move since the answered merge question was ASKED? The git-alone
|
|
266
|
+
* analogue of GitHub's "dismiss stale approvals when the base changes".
|
|
267
|
+
*
|
|
268
|
+
* ONE implementation for the laptop dispatcher ({@link performMergeAction}) and
|
|
269
|
+
* the CI agent half ({@link prepareMergeLand}), and both call it BEFORE
|
|
270
|
+
* {@link createJob}, whose continue rebase would otherwise make the kept branch
|
|
271
|
+
* a descendant of the current `main` and hide the move (the bug this replaced).
|
|
272
|
+
*
|
|
273
|
+
* The approved base is the `askedAtMain` the answered entry carries (stamped by
|
|
274
|
+
* the merge-question surfacer, and by the re-surface path with the `main` the
|
|
275
|
+
* follow-up is asked against, so a re-answer while `main` stays put lands and
|
|
276
|
+
* the CI split cannot livelock). It is compared with the arbiter's current
|
|
277
|
+
* `main`, freshly fetched into the hub mirror {@link createJob} then reuses.
|
|
290
278
|
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* compare
|
|
294
|
-
* the branch is a strict descendant — the merge-base did NOT move — and the
|
|
295
|
-
* cheap default applies (HONOUR + land on green re-verify). If the merge-base
|
|
296
|
-
* is NOT `<origin/main>`, `main` advanced past the branch's divergence point
|
|
297
|
-
* since the branch was last rebased — the merge-base MOVED — and the strict
|
|
298
|
-
* mode re-surfaces.
|
|
279
|
+
* "Moved" means the tree OUTSIDE `work/` differs: the question, its answer and
|
|
280
|
+
* every other ledger write are commits on `main` under `work/`, so a plain sha
|
|
281
|
+
* compare would re-stale every answer (the answer commit itself moves `main`).
|
|
299
282
|
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* red re-verify
|
|
303
|
-
* not block a clean answer-then-land
|
|
283
|
+
* Never stale (the cheap default applies) when the entry carries no
|
|
284
|
+
* `askedAtMain` (a sidecar written before it existed) or on any plumbing
|
|
285
|
+
* failure: the red re-verify on the rebased tip stays the load-bearing safety,
|
|
286
|
+
* and a transient git failure must not block a clean answer-then-land.
|
|
304
287
|
*/
|
|
305
|
-
function
|
|
306
|
-
|
|
288
|
+
function approvedBaseMoved(
|
|
289
|
+
url: string,
|
|
290
|
+
input: MergeActionInput,
|
|
291
|
+
): ApprovedBaseCheck {
|
|
292
|
+
const asked = input.action.entry.askedAtMain;
|
|
293
|
+
if (asked === undefined) return {stale: false};
|
|
294
|
+
let mirror: EnsureMirrorResult;
|
|
295
|
+
try {
|
|
296
|
+
mirror = ensureMirrorMain({
|
|
297
|
+
url,
|
|
298
|
+
workspacesDir: input.workspacesDir,
|
|
299
|
+
env: input.env,
|
|
300
|
+
});
|
|
301
|
+
} catch {
|
|
302
|
+
return {stale: false};
|
|
303
|
+
}
|
|
304
|
+
const main = mirror.mainSha;
|
|
305
|
+
if (main === asked) return {stale: false, main};
|
|
306
|
+
const diff = runProc(
|
|
307
307
|
'git',
|
|
308
|
-
[
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
job.arbiterRemote,
|
|
312
|
-
`+refs/heads/main:refs/remotes/${job.arbiterRemote}/main`,
|
|
313
|
-
],
|
|
314
|
-
job.dir,
|
|
315
|
-
{env},
|
|
308
|
+
['diff-tree', '--quiet', '-r', asked, main, '--', ':(exclude)work'],
|
|
309
|
+
mirror.path,
|
|
310
|
+
{env: input.env},
|
|
316
311
|
);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
312
|
+
// 0: no change outside `work/`; 1: changed; anything else: plumbing failure.
|
|
313
|
+
return {stale: diff.status === 1, main};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** The re-stale message both halves report. */
|
|
317
|
+
function restaleMessage(input: MergeActionInput, main: string): string {
|
|
318
|
+
const asked = input.action.entry.askedAtMain ?? '(unknown)';
|
|
319
|
+
return (
|
|
320
|
+
`merge-question for ${input.item} answered MERGE, but ` +
|
|
321
|
+
`strictMergeApproval is ON and \`main\` moved since the question was ` +
|
|
322
|
+
`asked (asked at ${asked}, now ${main}): RE-SURFACING the ` +
|
|
323
|
+
`merge-question (no land; the human re-confirms against the new base).`
|
|
324
324
|
);
|
|
325
|
-
if (mainSha.status !== 0) return false;
|
|
326
|
-
const base = runProc('git', ['merge-base', 'HEAD', mainRef], job.dir, {env});
|
|
327
|
-
if (base.status !== 0) return false;
|
|
328
|
-
return base.stdout.trim() !== mainSha.stdout.trim();
|
|
329
325
|
}
|
|
330
326
|
|
|
331
327
|
/**
|
|
@@ -405,6 +401,22 @@ export async function performMergeAction(
|
|
|
405
401
|
};
|
|
406
402
|
}
|
|
407
403
|
|
|
404
|
+
// STRICT re-stale check (OQ6 opt-in), BEFORE the checkout's continue rebase
|
|
405
|
+
// (which would push the rebased branch, and hide the move): when ON and
|
|
406
|
+
// `main` moved since the question was asked, RE-SURFACE the merge-question
|
|
407
|
+
// (the apply rung folds this into a re-pause) instead of landing. Default
|
|
408
|
+
// OFF ⇒ skipped; the cheap "green re-verify is enough" path runs.
|
|
409
|
+
if (input.strictMergeApproval === true) {
|
|
410
|
+
const check = approvedBaseMoved(url, input);
|
|
411
|
+
if (check.stale && check.main !== undefined) {
|
|
412
|
+
return {
|
|
413
|
+
outcome: 'restale',
|
|
414
|
+
message: restaleMessage(input, check.main),
|
|
415
|
+
main: check.main,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
408
420
|
// Check out the unmerged `work/<type>-<slug>` via the EXISTING per-job
|
|
409
421
|
// worktree seam — the SAME seam build/recovery callers use, NOT a bespoke
|
|
410
422
|
// worktree or clone. The job dir is the worktree `performIntegration` works
|
|
@@ -431,17 +443,17 @@ export async function performMergeAction(
|
|
|
431
443
|
}
|
|
432
444
|
|
|
433
445
|
try {
|
|
434
|
-
// CONTINUE-rebase-conflict: createJob's CONTINUE-rebase aborted
|
|
435
|
-
// genuine code conflict
|
|
436
|
-
//
|
|
446
|
+
// CONTINUE-rebase-conflict: createJob's CONTINUE-rebase aborted (a
|
|
447
|
+
// genuine code conflict, or any other rebase failure, named as such). The
|
|
448
|
+
// kept work stays on the branch (recoverable); we refuse the land and
|
|
449
|
+
// route via the standard refusal shape.
|
|
437
450
|
if (job.continueRebaseConflict) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
};
|
|
451
|
+
const message =
|
|
452
|
+
`merge-question for ${input.item} answered MERGE — but ` +
|
|
453
|
+
`${continueRebaseSentence(job.branch, job.continueRebaseFailure)} (the kept work is intact on the ` +
|
|
454
|
+
`branch; resolve and re-answer). NOT landing.`;
|
|
455
|
+
note(message);
|
|
456
|
+
return {outcome: 'refused', message};
|
|
445
457
|
}
|
|
446
458
|
if (job.continuePushFailure !== undefined) {
|
|
447
459
|
return {
|
|
@@ -453,50 +465,15 @@ export async function performMergeAction(
|
|
|
453
465
|
};
|
|
454
466
|
}
|
|
455
467
|
|
|
456
|
-
// STRICT re-stale check (OQ6 opt-in): when ON and the merge-base moved
|
|
457
|
-
// between the surfacer's question and this apply, RE-SURFACE the
|
|
458
|
-
// merge-question (the apply rung folds this into a re-pause) instead of
|
|
459
|
-
// landing. Default OFF ⇒ this block is skipped; the cheap "green
|
|
460
|
-
// re-verify is enough" path runs.
|
|
461
|
-
if (input.strictMergeApproval === true && mergeBaseMoved(job, input.env)) {
|
|
462
|
-
return {
|
|
463
|
-
outcome: 'restale',
|
|
464
|
-
message:
|
|
465
|
-
`merge-question for ${input.item} answered MERGE — but ` +
|
|
466
|
-
`strictMergeApproval is ON and the merge-base of \`work/${input.slug}\` ` +
|
|
467
|
-
`moved between answer and apply: RE-SURFACING the merge-question ` +
|
|
468
|
-
`(no land; the human re-confirms against the new base).`,
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
|
|
472
468
|
// LAND through the EXISTING `performIntegration`. The branch's tip
|
|
473
469
|
// already carries its done-move commit (surfacer enumerates `work/*`
|
|
474
470
|
// unreachable from main, so it is a previously-built strand), so we
|
|
475
471
|
// drive the committed-recovery tail — but with `freshWorktreeGate: true`
|
|
476
472
|
// so the REBASED TIP is re-verified BEFORE the integrate (the
|
|
477
473
|
// `committed-recovery-honours-fresh-worktree-gate` task's contract).
|
|
478
|
-
const integration = await performIntegration(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
slug: input.slug,
|
|
482
|
-
source: 'tasks-ready',
|
|
483
|
-
recovering: false,
|
|
484
|
-
committedRecovery: true,
|
|
485
|
-
freshWorktreeGate: true,
|
|
486
|
-
prepare: input.prepare,
|
|
487
|
-
verify: input.verify,
|
|
488
|
-
mode: 'merge',
|
|
489
|
-
// surfaceArbiter is set so a RED rebased-tip gate (or a rebase
|
|
490
|
-
// conflict surfaced during the retry loop) routes to needs-attention
|
|
491
|
-
// observably on the arbiter (not local-only). The build path uses
|
|
492
|
-
// this shape; we mirror it here for the answered-merge land.
|
|
493
|
-
surfaceArbiter: job.arbiterRemote,
|
|
494
|
-
recoveryRebaseRetries: input.recoveryRebaseRetries,
|
|
495
|
-
recoveryRebaseJitterMs: input.recoveryRebaseJitterMs,
|
|
496
|
-
mergeRetries: input.mergeRetries,
|
|
497
|
-
env: input.env,
|
|
498
|
-
note,
|
|
499
|
-
});
|
|
474
|
+
const integration = await performIntegration(
|
|
475
|
+
mergeLandIntegrationInput(job, input, note),
|
|
476
|
+
);
|
|
500
477
|
|
|
501
478
|
if (integration.outcome === 'completed') {
|
|
502
479
|
return {
|
|
@@ -538,3 +515,273 @@ export async function performMergeAction(
|
|
|
538
515
|
}
|
|
539
516
|
}
|
|
540
517
|
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* The `performIntegration` input of the answered-merge land: the committed-
|
|
521
|
+
* recovery tail (the branch already carries its done-move) with the fresh-
|
|
522
|
+
* worktree gate on the rebased tip, in merge mode. Shared by the laptop
|
|
523
|
+
* dispatcher ({@link performMergeAction}) and the CI agent half
|
|
524
|
+
* ({@link prepareMergeLand}), so both run the SAME rebase and gate.
|
|
525
|
+
*/
|
|
526
|
+
function mergeLandIntegrationInput(
|
|
527
|
+
job: Job,
|
|
528
|
+
input: MergeActionInput,
|
|
529
|
+
note: (message: string) => void,
|
|
530
|
+
): IntegrationCoreInput {
|
|
531
|
+
return {
|
|
532
|
+
cwd: job.dir,
|
|
533
|
+
arbiter: job.arbiterRemote,
|
|
534
|
+
slug: input.slug,
|
|
535
|
+
source: 'tasks-ready',
|
|
536
|
+
recovering: false,
|
|
537
|
+
committedRecovery: true,
|
|
538
|
+
freshWorktreeGate: true,
|
|
539
|
+
prepare: input.prepare,
|
|
540
|
+
verify: input.verify,
|
|
541
|
+
mode: 'merge',
|
|
542
|
+
// surfaceArbiter is set so a RED rebased-tip gate (or a rebase
|
|
543
|
+
// conflict surfaced during the retry loop) routes to needs-attention
|
|
544
|
+
// observably on the arbiter (not local-only). The build path uses
|
|
545
|
+
// this shape; we mirror it here for the answered-merge land.
|
|
546
|
+
surfaceArbiter: job.arbiterRemote,
|
|
547
|
+
recoveryRebaseRetries: input.recoveryRebaseRetries,
|
|
548
|
+
recoveryRebaseJitterMs: input.recoveryRebaseJitterMs,
|
|
549
|
+
mergeRetries: input.mergeRetries,
|
|
550
|
+
env: input.env,
|
|
551
|
+
note,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ---------------------------------------------------------------------------
|
|
556
|
+
// The CI agent half (task `ci-split-answered-merge-action`)
|
|
557
|
+
// ---------------------------------------------------------------------------
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* What the CI agent job found for an answered `merge` (spec
|
|
561
|
+
* `ci-agent-job-without-write-token` §3, the `apply, kind: merge` row). Each
|
|
562
|
+
* maps to one handoff intent of the `apply` rung:
|
|
563
|
+
*
|
|
564
|
+
* - `integrate`: the rebased tip passed the fresh-worktree gate; the apply job
|
|
565
|
+
* pushes it leased and lands it (`integrate`, the rebased tip bundled);
|
|
566
|
+
* - `restale`: `strictMergeApproval` is on and `main`'s code moved since the
|
|
567
|
+
* question was asked (`merge-restale`; checked before any checkout);
|
|
568
|
+
* - `needs-attention`: a red gate on the rebased tip (the rebased tip bundled)
|
|
569
|
+
* or a rebase conflict (nothing bundled: the kept branch is untouched);
|
|
570
|
+
* - `already-integrated`: the kept tip is already on `main`; nothing to land
|
|
571
|
+
* (the lock job's `needsAgent: false` covers this case, so here it means the
|
|
572
|
+
* branch landed between the lock job and this one).
|
|
573
|
+
*/
|
|
574
|
+
export type MergeLandPreparation =
|
|
575
|
+
| {kind: 'integrate'; repo: string; workBranch: string; tip: string}
|
|
576
|
+
| {kind: 'restale'; message: string; main: string}
|
|
577
|
+
| {
|
|
578
|
+
kind: 'needs-attention';
|
|
579
|
+
reason: string;
|
|
580
|
+
questions?: string[];
|
|
581
|
+
/** The rebased (red) tip to publish, when there is one. */
|
|
582
|
+
bundle?: {repo: string; workBranch: string};
|
|
583
|
+
}
|
|
584
|
+
| {kind: 'already-integrated'; message: string};
|
|
585
|
+
|
|
586
|
+
/** A {@link MergeLandPreparation} and the job worktree it lives in. */
|
|
587
|
+
export interface PreparedMergeLand {
|
|
588
|
+
preparation: MergeLandPreparation;
|
|
589
|
+
/** Remove the job worktree; call it once the handoff (and its bundle) is written. */
|
|
590
|
+
dispose(): void;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* The AGENT half of the answered-merge land, for the CI agent job, which holds a
|
|
595
|
+
* read-only token: the SAME optional `strictMergeApproval` re-stale check
|
|
596
|
+
* (before the checkout), checkout ({@link createJob}), rebase and
|
|
597
|
+
* fresh-worktree gate (`prepare` +
|
|
598
|
+
* `verify` run the branch's code) as {@link performMergeAction}, but nothing is
|
|
599
|
+
* written to the arbiter. The continue rebase stays local (decision 7,
|
|
600
|
+
* `localContinue`), and `performIntegration` runs under the phase recorder, so
|
|
601
|
+
* its first write (the land, or the needs-attention route of a red gate) halts
|
|
602
|
+
* it and names what the apply job performs. Only an answered `merge` gets here
|
|
603
|
+
* (`hold` / `drop` need no agent job).
|
|
604
|
+
*
|
|
605
|
+
* The caller writes the handoff from the returned preparation (a bundle reads
|
|
606
|
+
* the job worktree) and then calls `dispose`. Throws when the checkout itself
|
|
607
|
+
* fails (the agent job then fails, and the apply job surfaces the item).
|
|
608
|
+
*/
|
|
609
|
+
export async function prepareMergeLand(
|
|
610
|
+
answered: MergeActionInput,
|
|
611
|
+
): Promise<PreparedMergeLand> {
|
|
612
|
+
// The job worktree lives in the hub mirror, which does not see the
|
|
613
|
+
// checkout's local git config, where the CI workflow sets the identity: the
|
|
614
|
+
// continue rebase (and the wip commit of a red gate) need it
|
|
615
|
+
// (`withCheckoutCommitIdentity`).
|
|
616
|
+
const input: MergeActionInput = {
|
|
617
|
+
...answered,
|
|
618
|
+
env: withCheckoutCommitIdentity(answered.cwd, answered.env),
|
|
619
|
+
};
|
|
620
|
+
const note = input.note ?? (() => {});
|
|
621
|
+
const identity = resolveSidecarIdentity(input.item);
|
|
622
|
+
if (identity.type !== 'task' || input.action.verb !== 'merge') {
|
|
623
|
+
throw new Error(
|
|
624
|
+
`prepareMergeLand: only an answered merge of a task is prepared ` +
|
|
625
|
+
`(got ${input.item}, answer ${input.action.verb})`,
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
const url = resolveArbiterUrl(input);
|
|
629
|
+
if (url === undefined) {
|
|
630
|
+
throw new Error(
|
|
631
|
+
`the arbiter URL of ${input.item} could not be resolved (\`git remote ` +
|
|
632
|
+
`get-url ${input.arbiter}\` failed in ${input.cwd})`,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
// The re-stale check runs BEFORE the (local) continue rebase, exactly as on
|
|
636
|
+
// the laptop: the same `approvedBaseMoved`.
|
|
637
|
+
if (input.strictMergeApproval === true) {
|
|
638
|
+
const check = approvedBaseMoved(url, input);
|
|
639
|
+
if (check.stale && check.main !== undefined) {
|
|
640
|
+
const main = check.main;
|
|
641
|
+
return {
|
|
642
|
+
preparation: {
|
|
643
|
+
kind: 'restale',
|
|
644
|
+
message: restaleMessage(input, main),
|
|
645
|
+
main,
|
|
646
|
+
},
|
|
647
|
+
dispose: () => {},
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
const job = createJob({
|
|
652
|
+
url,
|
|
653
|
+
slug: input.slug,
|
|
654
|
+
type: 'task',
|
|
655
|
+
workspacesDir: input.workspacesDir,
|
|
656
|
+
localContinue: true,
|
|
657
|
+
env: input.env,
|
|
658
|
+
});
|
|
659
|
+
const dispose = (): void => {
|
|
660
|
+
try {
|
|
661
|
+
job.dispose();
|
|
662
|
+
} catch {
|
|
663
|
+
// Best-effort, as in performMergeAction: `gc` reaps a leftover.
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
const prepared = (preparation: MergeLandPreparation): PreparedMergeLand => ({
|
|
667
|
+
preparation,
|
|
668
|
+
dispose,
|
|
669
|
+
});
|
|
670
|
+
try {
|
|
671
|
+
if (job.continueRebaseConflict) {
|
|
672
|
+
const reason =
|
|
673
|
+
`${continueRebaseSentence(job.branch, job.continueRebaseFailure)}; the kept work is intact on the ` +
|
|
674
|
+
'branch. Resolve it, then answer the merge question again.';
|
|
675
|
+
note(reason);
|
|
676
|
+
return prepared({kind: 'needs-attention', reason});
|
|
677
|
+
}
|
|
678
|
+
const outcome = await runAgentPhase(
|
|
679
|
+
createPhaseRecorder({
|
|
680
|
+
record: [
|
|
681
|
+
'ledgerWrite.applyCompleteTransition',
|
|
682
|
+
'ledgerWrite.applyNeedsAttentionTransition',
|
|
683
|
+
],
|
|
684
|
+
}),
|
|
685
|
+
() => performIntegration(mergeLandIntegrationInput(job, input, note)),
|
|
686
|
+
);
|
|
687
|
+
if (outcome.halted) {
|
|
688
|
+
const call = `${outcome.intent.seam}.${outcome.intent.method}`;
|
|
689
|
+
if (call === 'ledgerWrite.applyCompleteTransition') {
|
|
690
|
+
fetchKeptLfsObjects(job, input.env, note);
|
|
691
|
+
const tip = runProc('git', ['rev-parse', 'HEAD'], job.dir, {
|
|
692
|
+
env: input.env,
|
|
693
|
+
}).stdout.trim();
|
|
694
|
+
return prepared({
|
|
695
|
+
kind: 'integrate',
|
|
696
|
+
repo: job.dir,
|
|
697
|
+
workBranch: job.branch,
|
|
698
|
+
tip,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
// A red gate (or prepare) on the rebased tip: the route's local half
|
|
702
|
+
// (the wip commit) runs here, so the rebased tip travels in the bundle.
|
|
703
|
+
const routed = outcome.intent.input as {
|
|
704
|
+
reason: string;
|
|
705
|
+
questions?: string[];
|
|
706
|
+
};
|
|
707
|
+
commitAbortedWork({cwd: job.dir, slug: input.slug, env: input.env});
|
|
708
|
+
fetchKeptLfsObjects(job, input.env, note);
|
|
709
|
+
return prepared({
|
|
710
|
+
kind: 'needs-attention',
|
|
711
|
+
reason: routed.reason,
|
|
712
|
+
...(routed.questions === undefined
|
|
713
|
+
? {}
|
|
714
|
+
: {questions: routed.questions}),
|
|
715
|
+
bundle: {repo: job.dir, workBranch: job.branch},
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
const result = outcome.result;
|
|
719
|
+
if (result.outcome === 'already-integrated') {
|
|
720
|
+
return prepared({
|
|
721
|
+
kind: 'already-integrated',
|
|
722
|
+
message: result.reason ?? `work/task-${input.slug} is already on main`,
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
// A rebase conflict on every attempt (or an invariant violation): nothing
|
|
726
|
+
// was written and the kept branch is untouched on the arbiter.
|
|
727
|
+
return prepared({
|
|
728
|
+
kind: 'needs-attention',
|
|
729
|
+
reason:
|
|
730
|
+
result.reason ??
|
|
731
|
+
`the answered merge of ${input.item} ended as ${result.outcome}`,
|
|
732
|
+
});
|
|
733
|
+
} catch (err) {
|
|
734
|
+
dispose();
|
|
735
|
+
throw err;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Bring into the job's local LFS store the objects of every LFS pointer the
|
|
741
|
+
* kept commits (`HEAD ^<origin>/main`) add or change, so the handoff carries
|
|
742
|
+
* them (`writeLfsObjects` copies from that store) and the apply job pushes them
|
|
743
|
+
* before the branch (decision 6). The kept branch was built elsewhere, so its
|
|
744
|
+
* objects live on the arbiter's LFS store, not in this fresh job: a read-only
|
|
745
|
+
* `git lfs fetch` of those commits. Best-effort: an object still missing is
|
|
746
|
+
* reported by the handoff writer, and the apply job then rejects the handoff
|
|
747
|
+
* and surfaces the item.
|
|
748
|
+
*/
|
|
749
|
+
function fetchKeptLfsObjects(
|
|
750
|
+
job: Job,
|
|
751
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
752
|
+
note: (message: string) => void,
|
|
753
|
+
): void {
|
|
754
|
+
const commits = runProc(
|
|
755
|
+
'git',
|
|
756
|
+
['rev-list', 'HEAD', `^refs/remotes/${job.arbiterRemote}/main`],
|
|
757
|
+
job.dir,
|
|
758
|
+
{env},
|
|
759
|
+
)
|
|
760
|
+
.stdout.split('\n')
|
|
761
|
+
.filter((l) => l !== '');
|
|
762
|
+
if (commits.length === 0) return;
|
|
763
|
+
const {pointers} = scanLfsPointers({
|
|
764
|
+
cwd: job.dir,
|
|
765
|
+
blobs: changedFileBlobs(job.dir, commits, env),
|
|
766
|
+
env,
|
|
767
|
+
});
|
|
768
|
+
if (pointers.length === 0) return;
|
|
769
|
+
const store = localLfsObjectsDir(job.dir, env);
|
|
770
|
+
const missing = pointers.some((p) => {
|
|
771
|
+
const path = lfsObjectPath(store, p.oid);
|
|
772
|
+
return !existsSync(path) || !statSync(path).isFile();
|
|
773
|
+
});
|
|
774
|
+
if (!missing) return;
|
|
775
|
+
const fetched = runProc(
|
|
776
|
+
'git',
|
|
777
|
+
['lfs', 'fetch', job.arbiterRemote, ...commits],
|
|
778
|
+
job.dir,
|
|
779
|
+
{env},
|
|
780
|
+
);
|
|
781
|
+
if (fetched.status !== 0) {
|
|
782
|
+
note(
|
|
783
|
+
`git lfs fetch of the kept commits failed (${fetched.stderr.trim().slice(0, 300)}); ` +
|
|
784
|
+
'the handoff will lack their LFS objects',
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type SidecarEntry,
|
|
8
8
|
type SidecarModel,
|
|
9
9
|
} from './sidecar.js';
|
|
10
|
-
import {
|
|
10
|
+
import {refWrite} from './ref-write.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The **answered STUCK-QUESTION ACTION DISPATCH** (spec
|
|
@@ -229,7 +229,7 @@ export async function performStuckAction(
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
// verb === 'reset'
|
|
232
|
-
const dropped = await
|
|
232
|
+
const dropped = await refWrite.deleteRemoteWorkBranch({
|
|
233
233
|
cwd: input.cwd,
|
|
234
234
|
arbiter: input.arbiter,
|
|
235
235
|
slug: input.slug,
|