"""A phase runs itself, on a branch of its own. A phase is a card that lists its cards (`taskfiles.weave_phases`). Running one means working that list into a single integration branch: `phase/` cut from the newest main, each member branched from the phase's tip, run headless, merged back when its checks are green, and the next one started. At the end one PR into `main`, for a human. The board never merges into `main` — a phase branch is the board's own, and merging into it is bookkeeping in the same family as committing a move. **The runner is a beat, not an agent.** Everything it decides is already structured state — a card's stage, a PR's CI verdict, whether one branch is contained in another — so an agent paid to poll would be the wrong tool at the wrong price. **The beat is stateless.** On each pass it recomputes, from disk and from git, which members are finished, which is first unfinished and what that one needs. It holds no registry of where a phase "is": a restarted board resumes a phase by looking, and the same logic answers "what now?" whether the last event was a launch, a merge or a crash. Two things carry the memory, and both are durable: - **git** — a member is merged when its branch is contained in the phase branch. That is what makes a restart safe from repeating a merge. - **the phase card** — a `## Phase log` section the runner appends one line to per decision (a run started, a member started, a member merged, a halt). It is the record a person reads, and the only thing that can tell "the phase already started this member and its run ended badly" from "the phase has not reached this member yet". Without it a restart would silently relaunch a run that died. **Halt, never skip.** Five conditions stop a phase, each of them already a visible state on the card: a member that declined (`NOT READY`), a run that exited non-zero, a clean exit that committed nothing, CI red, and a merge into the phase branch that is not mechanical. A phase that steps over a failed card builds the rest on a foundation that never landed. Halting is recorded in the log and nothing retries by itself; running the phase again is a person's decision and appends the line that clears the halt. **One board runs it.** The actor rule decides, and the phase card's **Assignee** is where it is written down — the same claim that gates starting work. A replica renders the phase and advances nothing. """ from __future__ import annotations import re import subprocess import threading import time from pathlib import Path import agents import config import github import state import taskfiles LOG_HEADING = "Phase log" # One log line: `-