Files

204 lines
10 KiB
Markdown
Raw Permalink Normal View History

---
name: land-batch
description: >
Use ONLY when the orchestrator lands the finished task branches of one DAG
iteration as a single batch PR ([org-internal #3731] batch-landing pilot). Composes
path-disjoint member branches via the deterministic batch-compose script,
pre-validates locally, opens ONE PR for N tasks (one CI fan-out instead of
N on the single-runner pool), pastes each member's review synthesis into
the PR conversation, runs one delta-scoped review-code on the batch head,
then hands the labeled PR to the merge coordinator. NOT for hotfixes /
CI-infra fixes (fast lane: 1:1 PR), single tasks, or single-session
mono-branch batches (topology A — one session owns every ticket on one
branch from inception; hand-compose those, see Applicability).
stage: merge-pr
triggers:
- land batch
- batch land
- batch-compose
- 批量归并
- 批量提交 pr
role: Producer
---
# land-batch — batch PR composition for one DAG iteration
**Orchestrator-only.** The composing session is the epic's orchestrator
(single writer at iteration close — the same role that already owns uniform
merge/close). `workflow/batch/*` branches are NOT claim branches: no
`claim.sh` interaction, no assignee; the orchestrator is the only composer.
Pilot charter: the decision comment on [org-internal #3731] (scope, opt-in protocol, cap,
success criteria) — this skill implements that charter, not the epic's
frozen DAG v1 (which covers only the merge-coordinator nodes N-01..04).
## Applicability: post-hoc composition only (topology B)
Two batch shapes exist ([org-internal #3731] charter addendum, comment 39347):
- **Topology A — single-session mono-branch batch**: one session owns every
ticket from inception and writes them on ONE branch (instance: PR [org-internal #3861],
audit-h1). Hand composition is natural there — do NOT force this skill on
it; record the outcome on [org-internal #3731] as a manual-batch datum instead.
- **Topology B — parallel workers, post-hoc composition**: workers finish on
SEPARATE branches at different times; the orchestrator composes at
iteration close. This is the skill's ONLY applicability — cross-branch
conflict attribution, lockfile convergence, and composition ordering are
exactly the error-prone parts it mechanizes (path-disjoint admission,
fail-closed attribution, `--convergent` surgery, per-member commit
retention for revert granularity).
Quick discriminator: if every "member" would be a commit on a branch you are
already writing, that is topology A — keep going, skip this skill. If you
are landing OTHER workers' branches you never owned, that is topology B —
this skill applies.
## Preconditions (all mandatory, fail-closed)
1. **Pilot opt-in**: the epic runs in batch-landing mode — its workers were
seeded to report `status=done branch=<ref>` WITHOUT opening PRs (see the
pilot note in `headless-session-ops/reference/completion-report.md`).
Non-pilot epics keep the per-task 1:1 PR flow.
2. **Every member is done**: review-code PASS on its own branch, run bundle
archived (`gen-run-bundle.ts --slug <slug> --ticket <N>` — the bundle is
committed on the member branch, so composition carries it; the
merge-gate's presence check is per close-ref and expects it).
3. **2..7 members** — a single done task rides its own 1:1 PR; more than 7
(`batch-compose --max-members`, the pilot cap) splits into multiple
batches along the path-overlap report.
4. **No fast-lane cargo**: prod hotfixes and CI-infra fixes bypass batching
entirely (1:1 PR, land immediately) — batching latency must never gate
urgent work.
## Procedure
### 1. Compose (deterministic core)
```bash
bun .gitea/scripts/batch-compose.ts \
--branch workflow/task/<a> --branch workflow/task/<b> … \
--out workflow/batch/<epic-slug>-iter<N> --dry-run
```
For batches whose members ALL rewrite a regenerable shared file — the
rule, not the exception, for dependency-bump batches (every `bun install`
rewrites the root `bun.lock`) — pass `--convergent bun.lock`: the file is
exempt from the overlap admission, and a merge that conflicts ONLY on it is
surgically resolved to the running head's version (dropped from the index
outright when the head no longer carries the path), with the JSON
reporting `converged: ["bun.lock"]` and a regeneration hint. A conflict
report carrying shapes the tool cannot attribute (rename/delete etc.)
fails CLOSED — convergent exemption never widens into a blind pass.
Exit codes / handling: `1 usage / no-changes-over-base` → drop the named
member or fix the invocation; `3 path-overlap` → split the batch along the
reported pairs (or land the overlapping member 1:1) and re-run per group;
`4 merge conflict` / `5 transport/git error` → fall
back to 1:1 for this batch; `0` → drop `--dry-run`
and push the composed branch. The script never touches the worktree or
index (keeper-style plumbing) and each member keeps its original commits —
revert granularity stays per-task.
### 2. Pre-validate locally (never enter CI red)
```bash
git fetch origin workflow/batch/<slug>
git switch --detach origin/workflow/batch/<slug>
cd packages/octopus && bun run test:changed
cd ../.. && git switch - # back to the orchestrator branch
```
If the compose output carried a `regenerate convergent files (…)` hint —
regenerate them FIRST, on the detached batch branch, and push the result
(the composed branch deliberately carries a stale convergent file: either
the surgically-resolved running-head version, or a member's clean-merged
one; either way it is deterministic only after regeneration):
```bash
git switch --detach origin/workflow/batch/<slug>
bun install # regenerates bun.lock
git add bun.lock # NEVER `commit -am` here: -a stages the
# worktree's symlink stubs and trips the
# pre-commit symlink guard (core.symlinks=false)
git commit -m "[<epic-slug>][iter-N] chore(deps): regenerate bun.lock for batch <slug>"
git push origin HEAD:refs/heads/workflow/batch/<slug>
```
(On detached HEAD the commit-msg hook's format check is inert — the
`[slug][iter-N]` prefix above keeps the corpus contract anyway.)
Failure → fix on the offending member branch (it is the only place the
fault can live — paths are disjoint), re-compose, re-validate. Do NOT push
a known-red batch.
### 3. Open the batch PR (gitea-rest recipes)
Title: `[<epic-slug>][iter-N] batch: <task-a> / <task-b> / …`
Body constraints (each is a merge-gate requirement, not style):
- **English close keywords, one per member issue**: `Closes #A`, `Closes #B`,
… — Gitea auto-closes all of them; Chinese 关闭/修复 never auto-close.
- **Zero unchecked top-level `- [ ]` lines in the body** — the gate's regex
(`^-\s+\[ \]`) blocks on any unindented unchecked box, with no fence
stripping. Render deferred/pending items as
plain text (`· deferred: …`), never as checkboxes; the per-task live
checklists stay on their issues.
- Per-member summary (ticket, branch, one line) + bundle index.
### 4. Paste member syntheses into the PR conversation
Post one comment per member carrying its review-code verdict block
(`**Verdict**: PASS|WARN` + the fenced JSON synthesis, from the member
issue's review thread). **Why**: the merge-gate hunts FAIL verdicts only in
the PR's own comment stream — a batch PR without these comments passes
vacuously (a fake-green hole). Pasting restores FAIL visibility; a member
synthesis of FAIL blocks the gate exactly as a 1:1 PR's would.
### 5. One review-code pass on the batch head
Run review-code against the batch PR. Members already PASS'd individually —
scope the review delta to what composition added (merge seams, cross-member
integration the per-task reviews could not see). This posts the required
per-sha `pipeline/review-code` status; member statuses do NOT transfer to
the composed head.
### 6. Label and hand off
Label the PR `ready-to-merge`. From here the flow is stock: the merge
coordinator's double-green queue serially merges (CI/check +
pipeline/merge-gate on the batch head, bot approve, main-account merge).
Do NOT hand-merge, do NOT hand-sync main into the batch branch.
### 7. Post-merge expectations
Member issues auto-close via the English keywords; their `## 当前状态`
PR/代码评审/CI rows fan out to EVERY close ref (status-sync poller Wide
behavior — requires [org-internal #3867], merged 2026-08-31). `task done ≡ PR merged`
still holds, transitively
through the batch PR; verify milestones fire unchanged.
## Fallback matrix
| Failure | Action |
| ---------------------------- | ------------------------------------------------------------------------------------- |
| path-overlap (exit 3) | split batch along overlap pairs / 1:1 for the odd member |
| merge conflict (exit 4) | 1:1 for the whole batch |
| local pre-validation red | fix on the offending member, re-compose |
| batch CI red after mergeable | coordinator dequeues with trail; fix on member branches, re-compose as a new batch PR |
Any fallback preserves today's behavior — batch landing is an optimization,
never a dependency.
## Measurement (pilot duty — revised per [org-internal #3731] charter addendum 39347)
Per-iteration attribution only; the global 7-day ratio is trend context,
never a pass/fail judge (same-window confounders: test-slim [org-internal #3855] /
test-dedupe [org-internal #3854] / the pilot's own PRs). On the server,
`bun .gitea/scripts/ci-run-stats.ts --days 7 --json /tmp/<slug>-ci-stats.json`
(the `--json` flag takes a filename — bare, it is a silent no-op; the human
summary still prints). Same methodology as the checklist-merge-gate.yml
2026-08-27 note. Record on [org-internal #3731]: (a) this batch iteration's per-workflow
trigger counts vs a historically similar 1:1 iteration, and (b) one
fallback drill (construct a path-overlap group → exit-3 split executed).
Baseline, trend only: 2026-08-31, 5,081 runs / TOTAL 2.75x single runner.