flow-next-land

$npx mdskill add gmickel/flow-next/flow-next-land

Autonomously babysits PRs through CI, review, and merge.

  • Solves the problem of manually shepherding PRs through CI and review.
  • Depends on Read, Bash, Grep, Glob, Write, Edit, and Skill tools.
  • Walks each PR through a gate tree: CI, patience, review, merge.
  • Emits a LAND_VERDICT line and merges only after all gates pass.

SKILL.md

.github/skills/flow-next-landView on GitHub ↗
---
name: flow-next-land
description: Autonomous PR babysitter tick. Fixes CI, resolves feedback, merges when converged, closes the spec, releases. Emits LAND_VERDICT. Use when asked to land PRs.
user-invocable: false
allowed-tools: Read, Bash, Grep, Glob, Write, Edit, Skill
---

# /flow-next:land — cadence-tick autonomous PR babysitter

A tick is one invocation of `/flow-next:land`: discover the open PRs the build loop authored, walk each through the gate tree (CI tri-state → patience window → review-thread resolution → review signal → merge gates), take at most ONE action class per PR, and end with one terminal `LAND_VERDICT` line. It is intentionally not a runner; `/loop` in Claude Code owns the cadence (babysitting waits on external events — CI, reviewers — over hours).

Land is the ship loop to pilot's build loop: pilot (`/goal`-shaped) drains ready specs into draft PRs; land (`/loop`-shaped) wakes on a cadence, acts on those PRs, sleeps. Land never authors PRs and never touches in-flight specs — it only babysits PRs whose authoring spec has ALL tasks done (the pilot-concurrency interlock).

Land and Ralph are alternative autonomous drivers. Never nest them, and never reuse Ralph harness state inside land.

**Auto-merge override (confined).** Land intentionally overrides the standing "no `gh pr merge` from skills" rule — confined to this one opt-in skill. Land itself is the gate: it merges explicitly (`--squash --delete-branch --match-head-commit`) only after every gate passes in-tick, and NEVER uses `gh pr merge --auto` (on a repo with no branch protection `--auto` merges instantly; server-side gating adds nothing). Every other skill keeps the no-auto-merge rule.

## Preamble

**CRITICAL: flowctl is BUNDLED — NOT installed globally.** `which flowctl` will fail (expected). Define once; subsequent blocks (here and in `workflow.md`) use `$FLOWCTL`:

```bash
FLOWCTL="$HOME/.codex/scripts/flowctl"
[ -x "$FLOWCTL" ] || FLOWCTL=".flow/bin/flowctl"
```

`gh` (verified against gh 2.93.0 — re-verify `gh pr checks --json bucket`/exit-8, `--match-head-commit`, and `mergeStateStatus` on major gh bumps) and `jq` must be on PATH; `gh auth status` must pass.

## Pre-check: Local setup version

Non-blocking, never asks (autonomous). On mismatch, stash a `SETUP_STALE` line so the verdict contract can co-locate it with the terminal `LAND_VERDICT` (a stderr echo alone gets buried); `version_ack` never suppresses it. Detection logic is unchanged:

```bash
[[ -d .flow/tmp && ! -L .flow/tmp ]] && rm -f .flow/tmp/setup_stale 2>/dev/null
SETUP_MODE=$(jq -r '.setup_mode // empty' .flow/meta.json 2>/dev/null)
SETUP_VER=$(jq -r '.setup_version // empty' .flow/meta.json 2>/dev/null)
PLUGIN_JSON="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$HOME/.codex}}/.codex-plugin/plugin.json"
PLUGIN_VER=$(jq -r '.version' "$PLUGIN_JSON" 2>/dev/null || echo "unknown")
if [[ "$SETUP_MODE" != "plugin" && -n "$SETUP_VER" && "$PLUGIN_VER" != "unknown" && "$SETUP_VER" != "$PLUGIN_VER" ]]; then
 if [[ ! -L .flow/tmp ]]; then
 mkdir -p .flow/tmp 2>/dev/null
 rm -f .flow/tmp/setup_stale 2>/dev/null # drop a planted setup_stale symlink before tee follows it
 echo "SETUP_STALE: local v${SETUP_VER}, plugin v${PLUGIN_VER}, run /flow-next:setup" | tee .flow/tmp/setup_stale >&2
 else
 echo "SETUP_STALE: local v${SETUP_VER}, plugin v${PLUGIN_VER}, run /flow-next:setup" >&2
 fi
fi
```

Continue regardless (never blocks; fail-open - silent when setup was never run, versions match, or any read fails). **Symlink guard (autonomous-safety):** a repo-committed symlink at `.flow/tmp` (or at the `setup_stale` file) would redirect the `tee` write outside the workspace during an unattended tick. When `.flow/tmp` is a symlink land refuses to stash - stderr echo only, no file write - so the verdict-time `cat` simply finds nothing (acceptable degradation, never an out-of-tree write); the cleanup guard is likewise symlink-aware (`! -L`), and the pre-`tee` `rm -f` drops a planted `setup_stale` symlink so `tee` creates a fresh regular file. See the verdict contract for how the stashed line is emitted before the terminal verdict.

## Hard guards (before anything else)

Run these guards before discovery, ledger writes, branch changes, or skill dispatch.

```bash
if [[ -n "${FLOW_RALPH:-}" || -n "${REVIEW_RECEIPT_PATH:-}" ]]; then
 echo "Ralph and land are alternative drivers — never nest them" >&2
 [[ -f .flow/tmp/setup_stale ]] && cat .flow/tmp/setup_stale
 echo 'LAND_VERDICT=NEEDS_HUMAN prs=0 pr=- reason="nested under Ralph harness (FLOW_RALPH/REVIEW_RECEIPT_PATH set) — refuse to run"'
 exit 1
fi

if git status --porcelain | grep -v '^.. \.flow/' >/dev/null; then
 [[ -f .flow/tmp/setup_stale ]] && cat .flow/tmp/setup_stale
 echo 'LAND_VERDICT=NEEDS_HUMAN prs=0 pr=- reason="dirty working tree at tick start"'
 exit 0
fi
```

Dirty tree means dirty outside `.flow/`; land leaves state untouched. No cleanup, no ledger write.

## Mode Detection

Parse `$ARGUMENTS` for the dry-run switch. Unknown flags warn to stderr and are ignored. The loop avoids bash positional parameters — the host's argument interpolation rewrites positional tokens inside skill code blocks (pilot dogfood finding, 1.13.0).

```bash
RAW_ARGS="$ARGUMENTS"
LAND_DRY_RUN=0

for ARG in $RAW_ARGS; do
 case "$ARG" in
 --dry-run) LAND_DRY_RUN=1 ;;
 -*) echo "Unknown flag: $ARG (ignored by /flow-next:land)" >&2 ;;
 *) echo "Unknown argument: $ARG (ignored by /flow-next:land)" >&2 ;;
 esac
done
export LAND_DRY_RUN
```

`--dry-run` stops after GATE: full discovery + per-PR classification report (CI tri-state read, review-signal state, would-be action) and the aggregated terminal line, with ZERO mutations — no checkout, no push, no label, no merge, no resolve-pr dispatch, no ledger write.

## The verdict contract (read this before the workflow)

Cadence drivers are transcript-blind: they read conversation output only and never run tools. Every tick therefore echoes its per-PR evidence (gate reads, action taken, verdict) into the output, one block per PR.

Per-PR verdicts are exactly: `MERGED | RELEASED | FIXING_CI | AWAITING_REVIEW | RESOLVING | BLOCKED | NEEDS_HUMAN`.

Every tick ends with exactly one terminal line, the last line of the response, with nothing after it:

```text
LAND_VERDICT=<verdict|NO_WORK> prs=<n> pr=<deciding-pr-url|-> reason="<one line>"
```

The tick-level verdict is the worst severity across PRs by priority `NEEDS_HUMAN > BLOCKED > FIXING_CI > RESOLVING > AWAITING_REVIEW > RELEASED > MERGED`; `pr=` is the URL of the PR that decided it (`-` when none). `NO_WORK` when discovery finds zero authored PRs. `prs=` is the number of PRs processed this tick.

**SETUP_STALE line.** Whenever the pre-check detected a setup-version mismatch it wrote `.flow/tmp/setup_stale`. At EVERY terminal `LAND_VERDICT` emission - the report line, each hard-guard exit, and the Preamble gh-auth failure - first print that file's `SETUP_STALE: local v<X>, plugin v<Y>, run /flow-next:setup` line, so it lands in the same output block immediately before the verdict and survives into driver logs. Emit it verbatim (`cat .flow/tmp/setup_stale` in bash blocks; a plain preceding line when the verdict is printed as text). It never blocks, is never suppressed, and fail-opens to nothing when the file is absent.

Driver condition examples:

```text
/loop 30m /flow-next:land
/goal keep running /flow-next:land until it prints LAND_VERDICT=NO_WORK or LAND_VERDICT=NEEDS_HUMAN
```

## Forbidden

- Asking the user anything in the tick path. Land is autonomous; ambiguity maps to `NEEDS_HUMAN`.
- Authoring PRs, choosing/planning/implementing specs — that is the build loop (pilot). Land only babysits existing PRs.
- Acting on a PR without BOTH authorship signals (branch matches a spec's `branch_name` AND the make-pr breadcrumb in the PR body). Branch-only matches are reported `NEEDS_HUMAN`, never mutated.
- `gh pr merge --auto`, merge-queue enrollment, or any merge without `--match-head-commit`.
- Hand-resolving merge-conflict hunks. The conflict path is mechanical rebase only; any conflict hunk aborts → `BLOCKED`.
- Inventing release steps. Release-follow runs deterministic, non-interactive commands from the project's discovered release docs ONLY, or stops at merge.
- `git add -A` in the CI-fix path — stage only the files edited for the fix.
- Dispatching any skill other than `flow-next-resolve-pr` (with `mode:autonomous`) and `flow-next-tracker-sync` (opt-in `land.merged` touchpoint).
- Printing anything after the `LAND_VERDICT` line.
- Running under Ralph (`FLOW_RALPH` / `REVIEW_RECEIPT_PATH`).

## Workflow

Execute [workflow.md](workflow.md) in order:

1. **guards** — refuse Ralph nesting, refuse dirty non-`.flow/` start state, resolve the `.git` land ledger (read-only at this point), read `land.*` config.
2. **discover** — open specs with all tasks done → `gh pr list --head <branch_name> --state all`, OPEN-state filter, dual authorship signals, merged-but-unclosed re-entry candidates.
3. **gate** — per-PR read-only classification: durable-label skip, CI tri-state over ALL checks, patience window anchored to last push, unresolved review threads, review signal (`land.reviewSignal`), stale-approval detection, `mergeStateStatus`. `--dry-run` stops here.
 - Under the default `silence` signal, a review bot that posts a no-findings **issue comment** instead of a formal APPROVE (e.g. Codex's "Didn't find any major issues. Reviewed commit: `<sha>`") also satisfies the gate — land scans `issues/<n>/comments` for an automated-reviewer comment matching `land.cleanReviewCommentPattern` (a structured built-in default) that names the **current head SHA**. It only ever *adds* this evidence; CI, unresolved-thread, and window gates are unchanged, and a stale-SHA or non-automated comment is ignored. Set `land.cleanReviewCommentPattern` to an explicit empty string `""` to **disable** the comment path (pure reviews-API behavior); leaving it unset uses the built-in default.
4. **act** — at most one action class per PR: CI fix, resolve-pr dispatch, mechanical rebase/update, or ready→merge→post-merge tail (spec close → tracker touchpoint → release-follow).
5. **report** — per-PR verdict evidence, ledger writes, and the terminal `LAND_VERDICT` line (worst-severity rule).

## Unattended runs

Land is fully autonomous by design — there is no interactive mode. Wall-clock limits and cadence belong to the driver (`/loop <interval>`, `/goal` stop clauses). A land tick has no timeout machinery; the patience window (`land.patienceMinutes`, default 30) is gate state, not a sleep — a tick never blocks waiting for reviewers, it reports `AWAITING_REVIEW` and exits.

More from gmickel/flow-next

SkillDescription
flow-nextManage .flow/ tasks and specs. Use for show or list tasks, task status, what is ready, show fn-N. NOT for planning or executing (use the plan and work skills).
flow-next-auditAudit .flow/memory/ entries against current code and keep, update, consolidate, or delete each. Use when asked to audit or refresh memory.
flow-next-captureSynthesize the current conversation into a flow-next spec with read-back gating. Use when asked to capture this as a spec.
flow-next-depsShow spec dependency graph and execution order. Use when asking 'what's blocking what', 'execution order', 'dependency graph', 'what order should specs run', 'critical path', 'which specs can run in parallel'.
flow-next-driveDrive any UI surface like a real user - a web app, a Chromium-backed desktop app (Electron / WebView2, reached over CDP), or a genuinely native app (macOS AppKit/SwiftUI, or a non-CDP webview) reached via the Cua Driver / Computer Use. Detects the surface, picks the best available driver, degrades gracefully. Use to navigate sites, verify deployed UI, test web or desktop apps, capture baseline screenshots, drive a sign-in flow, scrape data, fill forms, run an e2e check, or inspect current page state. Triggers on "check the page", "verify UI", "test the site", "test this app", "drive the app", "automate this desktop app", "read docs at", "look up API", "visit URL", "browse", "screenshot", "scrape", "e2e test", "login flow", "capture baseline", "see how it looks", "inspect current", "before redesign", "Electron app", "native app".
flow-next-export-contextExport RepoPrompt context to a markdown file for review with an external LLM (ChatGPT, Claude web, etc.). Use when you want Carmack-level review but prefer an external model. Triggers on "export context", "export for external review", "export plan for ChatGPT", "export impl review context", "review with an external model", "export review context".
flow-next-impl-reviewCarmack-level implementation review of changes via the configured backend. Use when asked to review code or a diff in a flow-next repo.
flow-next-interviewIn-depth Q&A to refine a spec, task, or spec file before building. Use when asked to flesh out, refine, or interrogate requirements.
flow-next-make-prOpen a PR with a cognitive-aid body rendered from flow-next spec state via gh. Use whenever asked to make or open a PR in a flow-next repo.
flow-next-mapBuild a semantic feature index of the repo via clawpatch map (opt-in). Use when asked to map the repo.