build-tdds · diff

git:20261005.9be7de9 to git:20261005.dd78360

200 added, 23 removed. Audit A to A.

---
name: build-tdds
description: Turn features described in the PRD and designed in TDDs into code and tests. Confirms the queue, then sequences one implementer worker, mechanical gates, one runtime-verify worker, and one reviewer worker. Opens a PR per TDD. Never merges. Invoke with /build-tdds.
---
# `/build-tdds`
Interactive parent skill. It sequences workers; it is not a detached
coprocess. Session survival is whatever the harness does.
+ Usage: `/build-tdds [<tdd-path>] [--combined | --parallel] [--escalate]
+ [--no-auto-escalate]`. `--escalate` (or `THROUGHLINE_ESCALATE=1`) runs
+ this run's judgment workers on the escalation model; `--no-auto-escalate`
+ (or `THROUGHLINE_AUTO_ESCALATE=0`) declines the automatic escalation of a
+ Retry (FR-88).
+
## Block contract (every `<!-- tl:… -->` block)
The harness runs each shell call in a fresh shell, so a marker-tagged
block never relies on a variable or function from an earlier call. Run
each block as one shell command. Each block:
- sources its own helpers, fail closed: plugin-root, `models.sh`, and
`run-record.sh` (except `tl:fr86-check`, whose bytes are shared with
`/prd-author` and `/tdd-author`);
- computes `TL_PARENT="$(tl_parent_model 2>/dev/null)" || TL_PARENT=""`
itself when it needs the parent model;
- reads inputs only from env vars, as `${VAR:?VAR required}`, so a
missing input fails loudly. Prefix the block with `export` lines that
carry the real values, e.g.
`export TL_REPO='/abs/repo' TL_RUN='20261005-174458'`.
Inputs: `TL_REPO` (absolute repo root: the human checkout, never the
worktree), `TL_RUN` (run id), `TL_SLUG` (TDD file name without `.md`),
`TL_TDD` (absolute TDD path), `TL_QUEUE` (absolute TDD paths, one per
- line).
+ line), `TL_ARGS` (this skill's argument text; may be empty, so it is read
+ as `${TL_ARGS?…}`), `TL_REQUESTED` / `TL_AUTO` (the two values the
+ `tl:escalation-flags` block printed).
+ Reports: every worker or gate report lives in the run dir root,
+ `docs/tdd/.implement-logs/<run>/<slug>.<build|ci-checks|verify|review>.txt`
+ (the implementer's report is `build`, which the test-first gate reads).
+ The parent creates each one empty before the worker and passes its path.
+
## 1. Source helpers (fail closed)
Resolve the plugin tree from the first set of `CLAUDE_PLUGIN_ROOT` /
`GROK_PLUGIN_ROOT`, then source helpers. If any source fails, stop.
```
_tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
. "${_tl_src}/scripts/lib/plugin-root.sh" || { echo "throughline: cannot source plugin-root.sh" >&2; exit 1; }
. "$(tl_plugin_root)/scripts/lib/verdicts.sh" || exit 1
. "$(tl_plugin_root)/scripts/lib/run-record.sh" || exit 1
. "$(tl_plugin_root)/scripts/lib/models.sh" || exit 1
REPO="$(git rev-parse --show-toplevel)"
```
`REPO` is the human session / integration checkout. Never pass the build worktree path as <repo-root>. Every `tl_verdict_*` and `tl_run_*` call uses `"$REPO"`.
+ Escalation flags (FR-88): run this block with `TL_ARGS` set to this
+ skill's argument text (`export TL_ARGS=''` when there are none). Keep the
+ two printed values; later blocks take them as `TL_REQUESTED` (`requested=`)
+ and `TL_AUTO` (`auto=`). Non-zero exit → show its stderr and stop.
+
+ <!-- tl:escalation-flags -->
+ ```bash
+ _tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
+ . "${_tl_src}/scripts/lib/plugin-root.sh" || { echo "throughline: cannot source plugin-root.sh" >&2; exit 1; }
+ . "$(tl_plugin_root)/scripts/lib/models.sh" || { echo "throughline: cannot source models.sh" >&2; exit 1; }
+ . "$(tl_plugin_root)/scripts/lib/run-record.sh" || { echo "throughline: cannot source run-record.sh" >&2; exit 1; }
+ : "${TL_ARGS?TL_ARGS required (export TL_ARGS='' when there are no arguments)}"
+ tl_escalation_flags "$TL_ARGS"
+ ```
+
## 1a. Parent-session model check (FR-86)
Before the lock and the queue, run this block as one shell command. It
sources its own helpers, so it does not depend on step 1's shell.
<!-- tl:fr86-check -->
```bash
_tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
. "${_tl_src}/scripts/lib/plugin-root.sh" || { echo "throughline: cannot source plugin-root.sh" >&2; exit 1; }
. "$(tl_plugin_root)/scripts/lib/models.sh" || { echo "throughline: cannot source models.sh" >&2; exit 1; }
tl_fr86_message
```
If the block printed a line, show that line to the user and ask a
structured question with exactly two options, `Continue` and `Stop`.
`Stop` ends the skill with no interview, no draft init, no lock, and no
queue. `Continue` proceeds. If the block printed nothing, proceed
without asking. If the block exits non-zero, show its stderr and stop
(fail closed).
## 2. Lock (FR-18 / FR-43)
`tl_run_lock "$REPO"`. If held by a live PID → refuse. If held by a dead
PID → `tl_run_lock_reclaim "$REPO"`.
- ## 3. Resume vs fresh (FR-39 / FR-40)
+ ## 3. Resume, Retry, or fresh (FR-39 / FR-40 / FR-88)
+ First run this block (input `TL_REPO`). It reads `latest` itself and
+ prints `run=<id>`, then one slug per line for each `failed` /
+ `gate-fail` TDD of that run (the Retry candidates); it prints nothing when
+ there is no `latest`. Non-zero exit → show its stderr, unlock, stop.
+
+ <!-- tl:retry-candidates -->
+ ```bash
+ _tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
+ . "${_tl_src}/scripts/lib/plugin-root.sh" || { echo "throughline: cannot source plugin-root.sh" >&2; exit 1; }
+ . "$(tl_plugin_root)/scripts/lib/models.sh" || { echo "throughline: cannot source models.sh" >&2; exit 1; }
+ . "$(tl_plugin_root)/scripts/lib/run-record.sh" || { echo "throughline: cannot source run-record.sh" >&2; exit 1; }
+ : "${TL_REPO:?TL_REPO required}"
+ tl_run="$(tl_run_latest_run "$TL_REPO")" || exit 0
+ printf 'run=%s\n' "$tl_run"
+ tl_run_retry_candidates "$TL_REPO" "$tl_run" || exit 1
+ ```
+
If `docs/tdd/.implement-logs/latest` has a TDD whose `status` is not
- terminal (`done|failed|blocked|skipped`):
+ terminal (`done|failed|blocked|skipped`), **or** the block printed at
+ least one slug:
- Ask the user a structured question: **Resume** / **Start fresh**.
+ Ask the user a structured question: **Resume** (the non-terminal TDDs) /
+ **Retry** (the listed slugs) / **Start fresh**. Offer only the options
+ that apply.
- **Fresh:** delete `latest` state fragments only (`run.json` and
`<slug>.json`). Keep logs. Then continue at step 4.
- **Resume:** `g="$(tl_run_next_gate "$REPO" "$run" "$slug")"`. Jump:
- `test-first` — if the build branch exists **and** has commits beyond
integration, do **not** re-dispatch the implementer; run step 8
observe-only. If the branch has no commits beyond integration,
dispatch the implementer (step 7) with "continue from existing
branch".
- `ci-checks` — skip 7–8a; run `ci-checks.sh` only (step 8b).
- `runtime-verify` — skip 7–8; run step 9 only.
- `review` — skip 7–9; run step 10 only.
- `flip` — skip 7–10; run step 11 only.
Before the first worker a resume dispatches, run the step 7
`tl:models-record` block: it records the slots against this
session's parent and prints the dispatch lines.
+ - **Retry** (FR-88, FR-15), for each listed slug, with `run` = the
+ printed run id. Retry is a new, human-triggered invocation, so ADR
+ 0013's no-in-invocation-rework rule holds.
+ 1. Worktree: reuse `.worktrees/build-tdds-<slug>` if it exists;
+ otherwise `git worktree add .worktrees/build-tdds-<slug>
+ build/<run>/<slug>` from that existing branch. If the branch is
+ missing, refuse Retry for that TDD and tell the user to Start fresh.
+ 2. Run the step 5 `tl:escalation-decide` block for the slug **first**:
+ it reads the halt-time verdicts. Then run this block (inputs
+ `TL_REPO`, `TL_RUN`, `TL_SLUG`). It prints `report=<path>` (the
+ failed gate's report, read before archiving; empty when no gate
+ verdict FAILed) and saves a copy that step 7's empty
+ `<slug>.build.txt` cannot overwrite. Then it archives the halt-time
+ verdicts into `retry-<N>/` and sets the TDD `building`, so an
+ interrupted Retry resumes from test-first, never past gates on
+ changed code. It prints `archive=<dir>` and
+ `implementer_report=<copy>`. Non-zero exit → show its stderr,
+ unlock, stop.
+
+ <!-- tl:retry-begin -->
+ ```bash
+ _tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
+ . "${_tl_src}/scripts/lib/plugin-root.sh" || { echo "throughline: cannot source plugin-root.sh" >&2; exit 1; }
+ . "$(tl_plugin_root)/scripts/lib/models.sh" || { echo "throughline: cannot source models.sh" >&2; exit 1; }
+ . "$(tl_plugin_root)/scripts/lib/run-record.sh" || { echo "throughline: cannot source run-record.sh" >&2; exit 1; }
+ : "${TL_REPO:?TL_REPO required}" "${TL_RUN:?TL_RUN required}" "${TL_SLUG:?TL_SLUG required}"
+ tl_report="$(tl_run_failed_report "$TL_REPO" "$TL_RUN" "$TL_SLUG")" || tl_report=""
+ printf 'report=%s\n' "$tl_report"
+ tl_copy=""
+ if [ -n "$tl_report" ]; then tl_copy="${tl_report%.txt}.prev.txt"; cp "$tl_report" "$tl_copy" || exit 1; fi
+ tl_arch="$(tl_run_retry_begin "$TL_REPO" "$TL_RUN" "$TL_SLUG")" || exit 1
+ printf 'archive=%s\n' "$tl_arch"
+ printf 'implementer_report=%s\n' "$tl_copy"
+ ```
+
+ 3. Step 5's confirmation (`TL_QUEUE` = `$REPO/docs/tdd/<slug>.md` for
+ each retried slug, with `TL_REPO` / `TL_RUN`), then step 7 on the
+ existing branch. Prefix the implementer prompt with "A previous
+ gate FAILED. Fix what this report found on the existing branch; do
+ not start over." and pass the `implementer_report=` path (if it is
+ empty, say no gate report exists).
+ 4. Then run every gate from 8a; each one overwrites its verdict file.
+ The verdicts of the failed attempt stay in `retry-<N>/`.
+
Never treat partial `feat:` commits as build-gate completion. The
test-first verdict file is the only build-complete signal.
## 4. Queue (FR-13, FR-18, FR-84)
Every `docs/tdd/0*.md` on the integration branch whose `Status:` is
`draft` or `ready` and that is not already `implemented` on an unmerged
`build/` branch.
Skip `build-engine: bootstrap` only when
`docs/tdd/.implement-logs/.first-flip-done` does **not** exist (no TDD
on this harness has an `implemented` flip from `/build-tdds` yet). After
that file exists, ignore `build-engine: bootstrap` and queue those TDDs
normally.
Optional argument: a TDD path builds just that one.
`tl_run_init "$REPO" "<run-id>"` if starting fresh. `run-id` is a UTC
- timestamp `YYYYMMDD-HHMMSS`.
+ timestamp `YYYYMMDD-HHMMSS`. A Retry keeps its run and does not
+ re-queue.
## 5. Confirm queue + mode
- First run this block (input `TL_QUEUE`: every queued TDD path). For
+ Escalation is decided per TDD **before** the confirmation (FR-88), so
+ the confirmation shows the escalated model. Run this block once per
+ queued or retried TDD (inputs `TL_REPO`, `TL_RUN`, `TL_SLUG`,
+ `TL_REQUESTED`, `TL_AUTO`). When FR-88 applies it prints
+ `trigger=<requested|auto>` and the one outcome line
+ `throughline: <slug> escalation=<escalated|already-top|fell-back> model=<model>[ reason=<reason>]`,
+ recorded in the sidecar; otherwise it prints nothing and writes nothing.
+ Show any output. Non-zero exit → show its stderr and stop.
+
+ <!-- tl:escalation-decide -->
+ ```bash
+ _tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
+ . "${_tl_src}/scripts/lib/plugin-root.sh" || { echo "throughline: cannot source plugin-root.sh" >&2; exit 1; }
+ . "$(tl_plugin_root)/scripts/lib/models.sh" || { echo "throughline: cannot source models.sh" >&2; exit 1; }
+ . "$(tl_plugin_root)/scripts/lib/run-record.sh" || { echo "throughline: cannot source run-record.sh" >&2; exit 1; }
+ : "${TL_REPO:?TL_REPO required}" "${TL_RUN:?TL_RUN required}" "${TL_SLUG:?TL_SLUG required}"
+ : "${TL_REQUESTED:?TL_REQUESTED required}" "${TL_AUTO:?TL_AUTO required}"
+ TL_PARENT="$(tl_parent_model 2>/dev/null)" || TL_PARENT=""
+ tl_rel="docs/tdd/$TL_SLUG.md"
+ tl_trigger="$(tl_escalation_decide "$TL_REPO" "$TL_RUN" "$TL_SLUG" "$tl_rel" "$TL_REQUESTED" "$TL_AUTO")" || exit 1
+ case "$tl_trigger" in
+ requested|auto) ;;
+ none) exit 0 ;;
+ *) echo "throughline: unexpected escalation decision '$tl_trigger'" >&2; exit 1 ;;
+ esac
+ tl_out="$(tl_escalation_outcome "$TL_PARENT" "$TL_REPO/$tl_rel")" || exit 1
+ tl_outcome="${tl_out%% *}"; tl_rest="${tl_out#* }"; tl_model="${tl_rest%% *}"; tl_reason=""
+ case "$tl_rest" in *' '*) tl_reason="${tl_rest#* }" ;; esac
+ printf 'trigger=%s\n' "$tl_trigger"
+ tl_run_set_escalation "$TL_REPO" "$TL_RUN" "$TL_SLUG" "$tl_outcome" "$tl_model" ${tl_reason:+"$tl_reason"} || exit 1
+ ```
+
+ Then run this block (input `TL_QUEUE`: every queued TDD path; also
+ export `TL_REPO` and `TL_RUN` so each TDD's sidecar escalation is shown
+ and applied — without them it shows no escalation). For
each TDD it prints the `models <slug>:` confirmation (the parent model,
- the effort and its source, and each worker's model and source), then
+ the effort and its source, and each worker's model and source, plus an
+ ` escalation: <outcome> model=<model>` line when one was decided), then
any warning lines: a judgment slot pinned to the light tier, or an
ignored per-worker effort pin. Run it and show its output even when the
queue has one TDD: it is the operator's only cost signal before
dispatch (FR-87, NFR-3). Warnings do not stop the run. Non-zero exit →
show its stderr and stop.
<!-- tl:models-confirm -->
```bash
_tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
. "${_tl_src}/scripts/lib/plugin-root.sh" || { echo "throughline: cannot source plugin-root.sh" >&2; exit 1; }
. "$(tl_plugin_root)/scripts/lib/models.sh" || { echo "throughline: cannot source models.sh" >&2; exit 1; }
. "$(tl_plugin_root)/scripts/lib/run-record.sh" || { echo "throughline: cannot source run-record.sh" >&2; exit 1; }
: "${TL_QUEUE:?TL_QUEUE required}"
TL_PARENT="$(tl_parent_model 2>/dev/null)" || TL_PARENT=""
while IFS= read -r tl_tdd; do
[ -n "$tl_tdd" ] || continue
[ -r "$tl_tdd" ] || { echo "throughline: queued TDD not readable: $tl_tdd" >&2; exit 1; }
tl_slug="${tl_tdd##*/}"; tl_slug="${tl_slug%.md}"
- tl_models_confirm "$tl_slug" "$tl_tdd" "$TL_PARENT" || exit 1
- tl_model_warnings "$tl_tdd" "$TL_PARENT" || exit 1
+ tl_esc=""; tl_escm=""
+ if [ -n "${TL_REPO:-}" ] && [ -n "${TL_RUN:-}" ]; then
+ tl_esc="$(tl_run_get_model_field "$TL_REPO" "$TL_RUN" "$tl_slug" escalation 2>/dev/null)" || tl_esc=""
+ tl_escm="$(tl_run_get_model_field "$TL_REPO" "$TL_RUN" "$tl_slug" escalation_model 2>/dev/null)" || tl_escm=""
+ fi
+ tl_models_confirm "$tl_slug" "$tl_tdd" "$TL_PARENT" "$tl_escm" "$tl_esc" || exit 1
+ tl_model_warnings "$tl_tdd" "$TL_PARENT" "$tl_escm" "$tl_esc" || exit 1
done <<<"$TL_QUEUE"
```
Then ask a structured question that shows that output verbatim. Modes:
- **sequential** (default) — stack `build/<run>/<slug>` on the previous
TDD branch; one PR per TDD. Downstream `blocked` on halt (FR-16).
- **`--combined`** — one branch, one PR.
- **`--parallel`** — independent worktrees. A failure affects only that
TDD.
## 6. Worktree (FR-20)
`git worktree add .worktrees/build-tdds-<slug>` from integration (or
previous sequential HEAD). Install deps unless `THROUGHLINE_SKIP_DEPS=1`.
Call the worktree path `WT`. `REPO` stays the human checkout.
## 7. Implementer worker
First run this block (inputs `TL_REPO`, `TL_RUN`, `TL_SLUG`, `TL_TDD`).
It records the TDD's models in the sidecar
- `docs/tdd/.implement-logs/<run>/<slug>.models.json`, appends (and
- prints) `implementer model=<build> (src=<build_src>)` to the per-TDD
- log `docs/tdd/.implement-logs/<run>/<slug>.log`, then prints one
+ `docs/tdd/.implement-logs/<run>/<slug>.models.json` (resolved on the
+ sidecar's `escalation_model` only when its `escalation` is `escalated`),
+ appends (and prints) `implementer model=<build> (src=<build_src>)` to the
+ per-TDD log `docs/tdd/.implement-logs/<run>/<slug>.log`, then prints one
`dispatch <worker> model=<arg>` line per worker. Non-zero exit → show
its stderr, unlock, **stop**.
<!-- tl:models-record -->
```bash
_tl_src="${CLAUDE_PLUGIN_ROOT:-${GROK_PLUGIN_ROOT:-}}"
. "${_tl_src}/scripts/lib/plugin-root.sh" || { echo "throughline: cannot source plugin-root.sh" >&2; exit 1; }
. "$(tl_plugin_root)/scripts/lib/models.sh" || { echo "throughline: cannot source models.sh" >&2; exit 1; }
. "$(tl_plugin_root)/scripts/lib/run-record.sh" || { echo "throughline: cannot source run-record.sh" >&2; exit 1; }
: "${TL_REPO:?TL_REPO required}" "${TL_RUN:?TL_RUN required}" "${TL_SLUG:?TL_SLUG required}" "${TL_TDD:?TL_TDD required}"
TL_PARENT="$(tl_parent_model 2>/dev/null)" || TL_PARENT=""
- tl_run_set_models "$TL_REPO" "$TL_RUN" "$TL_SLUG" "$TL_TDD" "$TL_PARENT" || exit 1
_tl_get() { tl_run_get_model_field "$TL_REPO" "$TL_RUN" "$TL_SLUG" "$1"; }
+ tl_escm=""
+ if [ "$(_tl_get escalation 2>/dev/null)" = escalated ]; then tl_escm="$(_tl_get escalation_model)" || exit 1; fi
+ tl_run_set_models "$TL_REPO" "$TL_RUN" "$TL_SLUG" "$TL_TDD" "$TL_PARENT" "$tl_escm" || exit 1
tl_b="$(_tl_get build)" && tl_bs="$(_tl_get build_src)" \
&& tl_r="$(_tl_get review)" && tl_v="$(_tl_get verify)" || exit 1
printf 'implementer model=%s (src=%s)\n' "$tl_b" "$tl_bs" \
| tee -a "$TL_REPO/docs/tdd/.implement-logs/$TL_RUN/$TL_SLUG.log" || exit 1
printf 'dispatch implementer model=%s\n' "$(tl_dispatch_model_arg "$tl_b")"
printf 'dispatch reviewer model=%s\n' "$(tl_dispatch_model_arg "$tl_r")"
printf 'dispatch runtime-verify model=%s\n' "$(tl_dispatch_model_arg "$tl_v")"
```
**Dispatch rule (all three workers).** Model =
`tl_dispatch_model_arg <slot value>`, which the block prints as
`dispatch <worker> model=<arg>`. If that prints nothing (`model=` is
empty), dispatch the worker with **no model parameter** so it inherits
this session's model. Otherwise pass exactly that string. Never pass
`inherit` as a model.
+ **Escalation fall-back (FR-88)**, for each judgment worker (implementer,
+ reviewer, nontrivial runtime-verify) of a TDD whose sidecar `escalation`
+ is `escalated`. Read `base-sha` = `git -C "$WT" rev-parse HEAD` just
+ before the implementer dispatch.
+ 1. Apply the transient rule first (FR-41): a rate/usage-limit pattern or
+ rc 143/130 is `paused`, not a fall-back. Exception: a refusal saying the
+ escalation model itself is unavailable to this account (`requires usage
+ credits`, `credits_required`) is not transient, even when it carries
+ `rate_limit` / HTTP 429. It is a fall-back (step 2); pausing would
+ re-dispatch the same refused model on every resume.
+ 2. Otherwise, if the dispatch tool returned an error, the worker's
+ completion notification reports it failed (an async worker's API error
+ arrives there, not from the dispatch call), or
+ `tl_escalation_fellback_check <report> "$WT" <base-sha> <implementer|verify|review>`
+ prints `fell-back <reason>`:
+ - run `tl_run_set_escalation "$REPO" "$run" "$slug" fell-back <model> "<reason>"`
+ with the reason `dispatch error: <first line of the error>` or the
+ check's reason; show the line it prints;
+ - re-run the `tl:models-record` block (it now passes no escalation);
+ - re-dispatch the same worker with **no model parameter**, on a fresh
+ empty report.
+
+ A fall-back is never detected by an inactivity timeout. A check that
+ prints `ok` with an empty report is a real worker failure: apply the
+ normal rules below.
+
Dispatch **one** implementer worker. It **must not spawn children**.
- Working directory = `$WT`.
- Model = the `dispatch implementer` line (dispatch rule above).
- Prompt: read the TDD + cited PRD FRs + accepted ADRs. Follow
test-driven-development if that skill is present. Commit on the build
branch. Do not flip Status. Do not open a PR.
- - Parent creates an empty report file under the run dir and passes its
- path. The last line matching `^BUILD_RESULT: (OK|BLOCKED)$` is
- authoritative.
+ - Parent creates the empty report `<run-dir>/<slug>.build.txt` and
+ passes its path. The last line matching `^BUILD_RESULT: (OK|BLOCKED)$`
+ is authoritative.
`BLOCKED` → append `docs/tdd/BLOCKERS.md` (FR-17),
`tl_run_set_tdd "$REPO" "$run" "$slug" blocked design-escalation`,
unlock, **stop**.
If worker exit or stderr matches `rate.?limit|usage.?limit|ECONNRESET`
(or rc 143/130) → `tl_run_set_tdd … paused <cause>`, unlock, **stop**
(FR-41). Genuine non-zero without that pattern → `failed` + `gate-fail`,
unlock, **stop**.
Continue only on `BUILD_RESULT: OK`.
## 8. Mechanical gates
Run in `$WT`; write verdicts against `"$REPO"`.
8a. `tl_test_first_observe "$WT"` → `tl_verdict_write "$REPO" "$run"
"$slug" test-first` (`PASS` iff observe rc 0, else `FAIL`). FAIL →
`tl_run_set_tdd … failed gate-fail`, unlock, **stop**.
- 8b. `bash "$(tl_plugin_root)/scripts/ci-checks.sh"` in `$WT`. Transient
+ 8b. `bash "$(tl_plugin_root)/scripts/ci-checks.sh"` in `$WT`, with its
+ stdout and stderr saved to `<run-dir>/<slug>.ci-checks.txt`. Transient
retry (FR-42): re-run up to `THROUGHLINE_TRANSIENT_RETRY` (default 2) if
rc is 143/130 or stderr matches `rate.?limit|usage.?limit|ECONNRESET`.
After retries exhaust on a transient pattern → `paused` (never
`failed`), unlock, **stop**. Non-transient non-zero → write `ci-checks`
`FAIL`, `failed` + `gate-fail`, unlock, **stop**. rc 0 → write
`ci-checks` `PASS`.
## 9. Runtime-verify worker
Dispatch **one** runtime-verify worker (different process/context).
**must not spawn children.** Model = the `dispatch runtime-verify` line
from step 7 (dispatch rule). That slot is resolved from the TDD path
(`tl_resolve_models <tdd-path> <parent>`, not the plan text) and
recorded as the sidecar `verify` / `verify_class` keys. Before dispatch,
append `runtime-verify model=<verify> (plan=<verify_class>)` to the
per-TDD log (FR-52), with both values from the sidecar.
- Parent creates an empty report file and passes its path. Worker drives
- the TDD verification plan and writes a last line
+ Parent creates the empty report `<run-dir>/<slug>.verify.txt` and
+ passes its path. Worker drives the TDD verification plan and writes a last line
`^VERIFY_RESULT: (PASS|FAIL|BLOCKED|SKIP)$`. `SKIP` requires a following
`EVIDENCE: <non-empty>` line. Missing token → FAIL. Transient stderr/rc
→ `paused` (FR-41), do not write FAIL.
Parent then `tl_verdict_write "$REPO" "$run" "$slug" runtime-verify`.
FAIL or BLOCKED → halt (`failed`/`blocked`), unlock, **stop**. PASS or
SKIP (with evidence) → continue.
## 10. Reviewer worker
Dispatch **one** reviewer worker. Model = the `dispatch reviewer` line
from step 7 (dispatch rule): the `review=` slot, the implementer's
judgment model unless pinned (FR-15(d)); independence is the fresh
worker. Before dispatch, append
`reviewer model=<review> (src=<review_src>)` to the per-TDD log, with
both values from the sidecar. Read-only. **must not spawn children.**
Inputs: TDD path, `git diff` of the build branch vs integration,
optional `agents/security-reviewer.md` if that file exists.
- Parent creates an empty report file and passes its path. Accept the
- token only as a whole line `^REVIEW_RESULT: (PASS|FAIL)$` (last matching
+ Parent creates the empty report `<run-dir>/<slug>.review.txt` and
+ passes its path. Accept the token only as a whole line `^REVIEW_RESULT: (PASS|FAIL)$` (last matching
line wins — not a mid-prose scrape). No such line → FAIL.
Parent writes `review.json` via `tl_verdict_write`. FAIL → halt, unlock,
**stop**. PASS → continue.
## 11. Flip + PR (never merge)
`tl_verdict_require_flip "$REPO" "$run" "$slug"` must be 0. Then:
- Flip TDD `Status: implemented` on the **build branch** (not
integration).
- `gh pr create` (never merge) (NFR-1).
- Write the FR-19 report under the run dir.
- Create `docs/tdd/.implement-logs/.first-flip-done` (empty marker) on
the first successful flip (FR-84).
- `tl_run_set_tdd "$REPO" "$run" "$slug" done`
- `tl_run_set_pr "$REPO" "$run" "$slug" "<url>"`
- Unlock.
## Halt rule (ADR 0013 / FR-16)
After every terminal outcome: continue to the next step only on
implementer `BUILD_RESULT: OK`; `test-first` PASS; `ci-checks` PASS;
`runtime-verify` PASS or SKIP; `review` PASS. Otherwise:
`tl_run_set_tdd` to `paused` (transient class after FR-42 exhaust:
`ratelimit` / `usage-limit` / `transient`), `blocked`
(`BUILD_RESULT: BLOCKED`, FR-67 `structural-finding`, FR-17
`design-escalation` / `external-blocker`), or `failed` (`gate-fail` for
a non-transient FAIL); unlock; **stop**. Do not dispatch later workers.
In sequential mode, remaining queued TDDs become `blocked`.
+ After every `tl_run_set_tdd … failed gate-fail`, record the halt-time
+ TDD blob for FR-88 auto escalation:
+ `tl_run_set_halt_blob "$REPO" "$run" "$slug" "docs/tdd/<slug>.md"`. If
+ it fails, show its stderr; the halt still completes, and auto
+ escalation is then unavailable for that TDD (`--escalate` still works).
+
## Notes
- Progress snapshot is `/implement-status` (TDD 0063). This skill does
not render it.
- Integration branch: origin default → `main` → `master`; override
`THROUGHLINE_INTEGRATION_BRANCH`.
- Models (ADR 0015): judgment workers inherit this session's model
(dispatched with no model parameter) and run at the session effort;
mechanical runtime-verify uses the light binding in
`scripts/lib/models.sh`, never above the parent. Pins, as harness
model aliases: `THROUGHLINE_BUILD_MODEL`, `THROUGHLINE_REVIEW_MODEL`,
`THROUGHLINE_RUNTIME_VERIFY_MODEL`, and `CLAUDE_CODE_SUBAGENT_MODEL`
- (every slot). A judgment slot pinned to the light tier warns and
- continues. `THROUGHLINE_BUILD_EFFORT`, `THROUGHLINE_REVIEW_EFFORT` and
+ (every worker dispatched without a model parameter). A judgment slot
+ pinned to the light tier warns and continues. `THROUGHLINE_BUILD_EFFORT`, `THROUGHLINE_REVIEW_EFFORT` and
`THROUGHLINE_RUNTIME_VERIFY_EFFORT` are reported as ignored: there is
no per-worker effort. Record:
`docs/tdd/.implement-logs/<run>/<slug>.models.json`.
+ - Escalation (FR-88, ADR 0015): only `/build-tdds` escalates, per TDD,
+ all unpinned judgment slots together, to `tl_escalation_model`
+ (override `THROUGHLINE_ESCALATION_MODEL`). Triggers: `--escalate` /
+ `THROUGHLINE_ESCALATE=1`, or auto on a Retry whose halt has a gate
+ `FAIL` verdict with the TDD unchanged on the integration branch
+ (declined by `--no-auto-escalate` / `THROUGHLINE_AUTO_ESCALATE=0`).
+ Outcomes `escalated` / `already-top` / `fell-back`, one line each,
+ recorded in the sidecar. Effort is unchanged by escalation.
- Sequential stacked PRs: merge bottom-up; enable auto-delete of head
branches so GitHub retargets. Or use `--combined`.