Skip to content
Back to skills

Wave Close

ASecurity

Use when finishing a wave — works out which order the open pull requests should land in, deletes the working copies the agents ran in, flags anything that got stuck so it is not lost, and files the wave's plan away. Safe to run again if it was interrupted. Add --auto and it also tells the pull requests that do not depend on each other to merge themselves once their checks pass, then stops. Triggers on "close the wave <slug>", "finalise wave <slug>", "archive wave <slug>".

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 22, 2026
ai-agentsgobashgit

Works with

  • terminal
  • cli

Security analysis

A100/100

Pro scans all 11 files and shows the line behind each finding

Scanned October 6, 2026

npx -y skills add formtrieb/flotilla --skill wave-close --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Wave Close?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Wave Close
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/formtrieb-wave-close/badge)](https://www.skillsdirectory.com/skills/formtrieb-wave-close)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: wave-close
description: Use when finishing a wave — works out which order the open pull requests should land in, deletes the working copies the agents ran in, flags anything that got stuck so it is not lost, and files the wave's plan away. Safe to run again if it was interrupted. Add --auto and it also tells the pull requests that do not depend on each other to merge themselves once their checks pass, then stops. Triggers on "close the wave <slug>", "finalise wave <slug>", "archive wave <slug>".
---

# wave-close

The operational terminator for a wave: confirm every row has reached `in-review`, clean up the wave's agent worktrees **before** anything merges (a worktree or a plain branch-checkout both silently break `--delete-branch`), recompute and **print** the advisory merge order (read-only — no parser-consumed section is mutated) so the human merges each PR and then verifies branch deletion as its own checked step, **land each merged row `done`** via the done-reconcile (the existing `issue-store close` verb) — **ticking the reviewer-met ACs it carries** (`--acked`, derived per-row from the FINAL Reviewer verdict via the engine's `verdict-acked` verb, FOR-17) — flag any closed-unmerged or stuck rows, **gate the archive on TWO fail-closed engine checks — both read by exit code alone, both blocking**: every disclosure carrying a disposition (`spine check-disclosures`, ADR-0027) *and* no human-gated row still holding the live `queued` claim it was never dispatched to release (`spine check-awaiting-human`, [reference/phase-6-archive.md](reference/phase-6-archive.md)) — and archive the spine to `_archive/`.

Read [`../wave-shared/SKILL.md`](../wave-shared/SKILL.md) and every file under [`../wave-shared/reference/`](../wave-shared/reference/) first, resolved against this skill's own base directory — no skill invocation, no namespace to guess (ADR-0040) — it owns the auth-preflight / atomic-spine conventions this skill obeys.

Your job is the **judgment** — the terminality gate, deciding when a closed-unmerged PR or a stuck row becomes a `needs-attention` flag (and when a row with *no* merge evidence is merely reported, not flagged), and calling the archive at the right moment. Each phase's worked sequence — exact invocations, live-finding annotations, Common Mistakes — lives with that phase under [reference/](reference/); the pieces genuinely common to every phase (CLI resolution, the full command reference, exit-code tables, shared JSON shapes) live in [reference/close-mechanics.md](reference/close-mechanics.md). You never write a tracker directly; everything goes through the engine CLI (`{{wave-cli}}`).

## When to Use

- Every Plan-Table row has reached `in-review` (the terminality gate below) and the Coordinator is ready to land.
- A prior `wave-close` run was interrupted (token outage mid-flight) — every phase is a guarded no-op when its work is already done; run as many times as needed.

Do **not** use this to dispatch (`wave-start`), to plan (`wave-plan`), or to merge `main` by hand. flotilla lands every change through a PR on protected `main` — there is **no fast-forward of `main`** here (the Ur's §7.1 branch-sync is gone). This skill recommends a merge order; the merge itself is either a human action (the default) or, with opt-in `--auto`, a **partial-arm** through the engine's `host-pr` verb — the order-free rows are armed server-side and the skill exits (ADR-0023). PR *creation* rides the Worker terminator, through the shipped `host-pr create` verb (ADR-0023 decision 3, find-before-create) — wave-close itself never creates a PR, only lands one, and that stays out of scope here.

## THE FLOTILLA BOUNDARY — protected main, PR-only, spine never edits main

- **Never push to `main`.** Landing is per-PR through the protected-branch route. wave-close recomputes the advisory order and, opt-in via `--auto`, arms the order-free rows through the engine `host-pr` seam (ADR-0023) — it does not merge `main` itself and never fast-forwards it.
- **Archive to `_archive/`, never to `done/`.** flotilla has no `git mv`-to-`done/` close ceremony (that is an Ur binding). The spine and its sidecar folder move together into `.flotilla/waves/_archive/`.
- **Advisory merge-order is print-only.** The recomputed merge order is printed to stdout (advisory). It is **not** written into the spine's `## Conflict-Map` — that section is parser-consumed (ADR-0016 forbids skills hand-authoring parser-consumed spine content; there is no CLI verb to write an advisory order into `## Conflict-Map` separately from the conflict pairs, and `spine replace-closed-by` targets the `## Closed-by` section, not `## Conflict-Map`).
- **Idempotent — read before mutate.** Every mutating step checks state first: skip already-flagged rows, skip already-archived spines.
- **Self-repair hazard — every engine verb this skill calls predates the rows now closing.** `issue-store read-closing`, `merge-order`, `worktree-cleanup`, the `host-pr` routing verbs (`arm`/`merge`/`status`), and `issue-store close` all run from the **local checkout** — and that checkout sits at an **Anchor**: the code as it stood before these rows' own PRs landed, whether that is the one Anchor a wave that lands only at close was dispatched against, or the latest of several a wave that re-anchors between rounds has already pulled forward. Either way it predates *this* close's own merges. A wave whose own rows change any of that machinery cannot reconcile against its own fix until the merge is pulled locally. Phase 4a below gives the mechanical detection step and the required `merge → pull → reconcile` order (W4-F1 / W5-F3).

## Terminality gate

wave-close runs only once the wave is **all-in-review**: every Plan-Table row's reconstructed coarse rung is `in-review` (its Worker finished and its Reviewer approved — the row reached `approved`/`pr-created`). Check by reading each row's `State` cell via `spine read`:

**The terminal states are `TERMINAL_ROW_STATES`, the engine's own partition of the row-state vocabulary** (exported from the package root beside `ROW_STATES`): `pr-created`, `approved`, `failed`, `abandoned`, `parked`. That constant is the source of every list of the five in this skill — here, in [phase 1](reference/phase-1-load-gate.md), in [phase 3](reference/phase-3-worktree-cleanup.md) and in [phase 6](reference/phase-6-archive.md) — and it is the same set the `worktree-cleanup --orphans` terminal-wave verdict and the resume reconciliation branch on. Read it there rather than re-deriving it from the bullets below; the bullets say what each state *means* to this gate, not what the set *is*.

- A row still at `dispatched`/`re-dispatched`/`reviewing`/`verdict-in`/`report-in` means the wave isn't done → STOP: `wave not yet terminal — N row(s) still in flight; run wave-start (or resume) first`.
- `failed`/`abandoned` rows are terminal; include them in the gate check but they do not block closing — they will be flagged in phase 5.
- **`parked` rows are terminal and silent** (ADR-0022). They pass the gate and never block closing. A parked row was deliberately taken out of *this* wave and its claim is already released — it is **not** a problem to flag, has no branch and no PR, and gets its own report rubric in phase 5. It is the reason a held row no longer has to be recorded as `abandoned` (which would mean "never" for work that will be re-planned).
- **A row still at `planned` was HELD, never dispatched — report it, do not STOP on it.** `planned` is deliberately absent from the STOP list above: it is where both of `wave-start`'s holds park a row that this wave never sent out. Two shapes, distinguished by the row's `Worker` cell:
  - **awaiting-human** — the `Worker` is human-gated (`HITL-required` by default, ADR-0012) and no human has acted yet. The row is **awaiting a human, not stuck and not errored**: nothing failed, nothing is in flight, and no probe can tell you anything about it. Phase 5 has its rubric.
  - **HELD on an intra-wave blocker** — the `Worker` is ordinary; the row waited on a sibling that has since landed (or has not). Same treatment: report it, name what it waits on.

  Neither blocks closing, and neither is a `needs-attention` flag — but neither is finished either, so **read the `Worker` column when you read the `State` column**. A `planned` row reported as "stuck" or silently swept into the archive is the failure this bullet exists to prevent.

Do not open PRs or archive for a wave that has unfinished rows.

## Capture a find where it surfaces — the window closes at the archive

A find that surfaces while you are closing is captured into the spine **the moment it surfaces**, in the phase it surfaced in — not at the end of the run, and never out of your own memory. The capture window runs from verdict-routing through every close phase and ends **hard at the archive move in phase 6** (ADR-0038): while the spine is live it is a write target, once archived it is evidence, and a find that arrives after that goes straight onto the tracker as a bare issue with a provenance line instead.

One verb, two spellings — reach for whichever the find actually is:

- **Row-scoped** — a Plan-Table row and an iteration own the find (a stuck row's residue, a merged row's leftover branch):
  ```bash
  {{wave-cli}} spine add-disclosure <wave-file> <row-id> --iter <n> --source coordinator --text "<the gap>"
  ```
- **Wave-scoped** — the find is about the wave's OWN machinery and no row owns it: the worktree sweep, a phase-2 auth posture, the merge-order tool itself.
  ```bash
  {{wave-cli}} spine add-disclosure <wave-file> --wave-scoped --source coordinator --text "<the gap>"
  ```
  Do **not** hang such a find on an arbitrary "affected" row to make it fit the row-scoped form: that corrupts row-scoped counting and has no answer at all for a find with no affected row (the option ADR-0038 rejected by name).

Both forms land in the same table, both are counted by phase 6's disclosure gate, and `coordinator` is the source for anything you observed yourself. Both invocations are in the command table at [reference/close-mechanics.md](reference/close-mechanics.md).

**Why this is doctrine and not a gate.** The phase-6 gate forces a *disposition* onto what was recorded; it can never force *capture* — you cannot gate what was never written down, so this rule is capture's only carrier, deliberately (ADR-0038). What it protects is less the lost find than the **uncounted** one: the spine's Disclosures table is the occurrence counter for sub-ticket findings, and a find that lived only in one session's memory never reaches the recurrence that earns it a ticket.

**A close-visible measurement you can predict at routing is pre-captured there instead** — as a measurement-point disclosure, so the archive gate later forces the measurement itself to happen and its disposition carries the measured evidence. Pre-capture carries what you could foresee; this window carries what you could not (ADR-0038). Neither replaces the other.

## Coordination misfires — a self-reported line in the closing report

The operator block this skill ends with (Convention 16) carries one more line: **how many engine-CLI usage errors (exit 2) and hook/guard blocks you — the Coordinator, this session — hit across this wave's own coordination**, each with a one-line cause (a guessed flag, a config-miss `ENOENT`, an input shape read from source instead of `--help`, an assumed output shape — whatever it actually was). This counts only your own misfires, never a Worker's or Reviewer's — the harness has no way to see into another session's exits, so it is self-reported, and the line says so.

**State it explicitly even when it's zero:** `coordination misfires: 0`. Silence is not the same claim as "none happened" — an absent line reads as *never counted*, not *clean* (the same deferred-is-not-pass discipline the DoR gates hold elsewhere in this pipeline), so a clean wave spells out the zero exactly like a wave with misfires spells out the count.

This is deliberately **prose-tier** (ADR-0034 draft mode): the lightest possible counter, not new engine machinery, added at the one place every wave already reports. What accumulating this line across waves is *for* — the recurrence it feeds — is in [reference/close-mechanics.md](reference/close-mechanics.md).

## Procedure

Each phase's full worked body — guards, worked command blocks, live-finding annotations, and that phase's own Common-Mistakes bullets — lives in its own file under [reference/](reference/), named by phase number and linked from its own step below, loaded there rather than upfront. A cross-phase "phase N" citation inside a phase file still resolves once you reach that phase. The numbering below (`1`, `2`, `3`, `4`, `4a`, `4b`, `5`, `6`) is the stable identifier sibling skills cite (e.g. "wave-close phase 4b").

### 1. Load wave-shared + gate

[phase 1](reference/phase-1-load-gate.md). Read `../wave-shared/SKILL.md` and every file under `../wave-shared/reference/`, resolved against this skill's own base directory, read the spine, and confirm the terminality gate before doing anything else; a re-run on an already-archived wave is a guarded no-op past this point.

### 2. Auth preflight (skip when no host writes pending)

[phase 2](reference/phase-2-auth-preflight.md). Skip the network entirely when every row's `Closed-by:` already classifies as a real PR; otherwise detect the host and verify auth before any write.

### 3. Worktree cleanup — BEFORE the merge

[phase 3](reference/phase-3-worktree-cleanup.md). Clean up this wave's agent worktrees and any stamped Reviewer probe checkout routing missed, and sweep orphaned branches/directories — unconditionally, every time — so nothing still holds a wave branch locally by the time anyone reaches the merge step. **First**, read each PR-bearing row's reviewed head, `git rev-parse --verify --quiet refs/review/<id>`, and keep it for phases 4 and 4b: this phase's sweep removes a terminal wave's own review refs.

### 4. Advisory merge-order (print-only) — the merge happens here, verify branch deletion separately

[phase 4](reference/phase-4-advisory-merge-order.md). Recompute and print the advisory merge order, merge each PR through the engine host seam (every merge and arm passes `--commit-message`: `landing.commitMessage` from `wave.config.json`, else `pr`; and `--expect-head` with the row's reviewed head — [the rule below](#what-lands-is-the-reviewed-commit)), and verify branch deletion as its own checked step — the merge command's exit code alone is never evidence the branch is gone.

### 4a. Self-repair check + pull to completion before you reconcile (W4-F1 / W5-F3)

[phase 4a](reference/phase-4a-self-repair-pull.md). Detect whether this wave's own rows changed the engine surface wave-close depends on, then pull `main` to a verified completion before phase 5 reconciles against it. A mandatory pre-pull check against the three harness write-denied path classes (`.claude/skills/**`, `.claude/agents/**`, `.claude/settings.json`) decides sandbox-on vs sandbox-off *before* the pull runs, rather than diagnosing a half-applied pull after the fact.

### 4b. `--auto` — partial-arm confirm + arm-and-exit (opt-in)

[phase 4b](reference/phase-4b-partial-arm.md). Opt-in only: present one confirm for the wave, arm the order-free rows through `host-pr arm` (each with `--expect-head`), then exit without watching — the overlapping tail stays on the phase-4 advisory order.

### 5. Done-reconcile + needs-attention for stuck rows

[phase 5](reference/phase-5-done-reconcile.md). Probe each terminal row's closing state via the evidence hierarchy, then land it `done`, flag it, or report it — never guessing between merged and rejected. A never-dispatched row (`parked`, or `planned` in one of the two held lanes) is reported under its own rubric and never probed: there is nothing for a probe to find, and a flag would answer a question nobody asked.

### 6. Archive (the last phase — terminal-only, idempotent, layout-aware)

[phase 6](reference/phase-6-archive.md). **Two** fail-closed gates run first, in this order, each read by exit code alone. The disclosure gate: `spine check-disclosures` blocks on any `open` entry, and every open disclosure the wave surfaced must carry a disposition — one of exactly `resolved-in-slice | scope-extension | filed:<id> | dropped:<reason> | upstream:<ref>` — before the archive proceeds (ADR-0027). The gate checks existence only and never judges quality, so **which** disposition to reach for is your judgment, guided by the defaults in the phase file: a disclosure earns its own ticket only when it names a mechanism defect with an observed consequence, everything else bundles thematically via a shared `filed:<id>` (N:1, appends only while that bundle is still bare), and a `filed:` body carries the observation — symptom, evidence, provenance — never an unverified diagnosis (ADR-0027 Amendment 2026-07-31). Then the awaiting-human gate: `spine check-awaiting-human` blocks while any human-gated row still sits at `planned` holding the live `queued` claim nothing ever released, and it offers exactly two exits — the human acts and the row dispatches, or the row is parked and unclaimed ([reference/phase-6-archive.md](reference/phase-6-archive.md), park per ADR-0022). Both gates are fail-closed in both directions: an unreadable spine blocks exactly like a real finding. Once both are clear and every row is finalised, flip the spine to `closed` (`spine set-status <spine> closed`, exit code read, idempotent), then detect the consumer's actual git-tracked status and archive the spine plus its sidecar folder to `_archive/`, never to `done/`.

## What lands is the reviewed commit

Nobody commits to a row's branch after its verdict — not the Worker, not you, not an Operator. A fix, or a branch update to resolve a landing conflict, is a re-dispatch with its own review. Phases 4 and 4b (the `--auto` arm) enforce it: each landing passes `--expect-head` with the reviewed head, and a PR whose head moved is `refused` with both commits named. A row whose `refs/review/<id>` was missing lands without the flag, and the report says its head check abstained. A Coordinator-direct PR that is not a wave row is unaffected. Details: [close-mechanics.md](reference/close-mechanics.md#--expect-head-resolution-adr-0055).

## Common Mistakes

Phase-specific mistakes live with their phase in [reference/](reference/). These three are genuinely cross-phase:

- **Recording a held row as `abandoned`.** `abandoned` means "never"; a row held out of this wave for re-planning is `parked` ("later"). Recording it `abandoned` lies to the next planner and leaves the claim stuck on the board — this is the live-gate defect ADR-0022 exists to fix.
- **Merging `main` or fast-forwarding.** wave-close recommends an order and, opt-in via `--auto`, arms the order-free rows through the engine `host-pr` seam (ADR-0023) — it never touches `main` directly. The Ur's §7.1 branch-sync is gone.
- **Reaching for raw `gh` anywhere on the host path.** `gh` left the landing path entirely (ADR-0023): the shipped `host-pr` verbs (`create | arm | merge | status | preflight`) are the only host-write path. wave-close itself only ever calls the **landing/probe** ones (`arm`/`merge`/`status`/`preflight`) — PR **creation** rides the Worker terminator's own `host-pr create` call, not wave-close.

## Related

- [reference/close-mechanics.md](reference/close-mechanics.md) — the cross-phase remainder: CLI resolution, the full command reference, exit-code tables, and the JSON shapes shared across phases. Each phase's own worked sequence lives with that phase, not here.
- [../wave-shared/SKILL.md](../wave-shared/SKILL.md) — auth-preflight / atomic-spine conventions this skill inherits.
- [../wave-start/SKILL.md](../wave-start/SKILL.md) — the dispatch loop that brings rows to `in-review`, the precondition for wave-close.
- [../wave-create/SKILL.md](../wave-create/SKILL.md) — materialises the spine this skill terminates.

## Operator register (Convention 16)

**Everything you print for the person at this session is operator-directed output, and it holds one register.** Plain language, direct address ("du"/"you"), self-explaining. Translate every internal reference — a decision-record number, a convention number, a finding id, a wave slug, a retro path — into the one-line consequence it carries for them, instead of naming it. Introduce a domain term with a half-sentence the first time it appears in a session, then use it freely. End the run with an operator block: what happened → where it lives → what you do next. Operator-directed text follows the operator's own language; the artifacts you write — issues, PRs, decision records, spine entries — stay English. **Installed form is strict** — no internal token reaches the operator. **Source form**, flotilla's own repo, may append one compact reference pointer after the plain text. Full clause text, the operator mini-glossary, and the mistakes it closes: [wave-shared/reference/convention-16-operator-register.md](../wave-shared/reference/convention-16-operator-register.md), read as a file beside this skill's own directory — no skill invocation, no namespace to guess.

Files in this skill

  • SKILL.md19.5 KB
  • evidence/phase-3-worktree-cleanup.md14 KB
  • reference/close-mechanics.md20.7 KB
  • reference/phase-1-load-gate.md1.4 KB
  • reference/phase-2-auth-preflight.md4.4 KB
  • reference/phase-3-worktree-cleanup.md56.9 KB
  • reference/phase-4-advisory-merge-order.md7.4 KB
  • reference/phase-4a-self-repair-pull.md23.3 KB
  • reference/phase-4b-partial-arm.md9.8 KB
  • reference/phase-5-done-reconcile.md19.9 KB
  • reference/phase-6-archive.md27 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.