Land and make-pr redesign: one named pull request, a rendered briefing, several specs in one body - #459
Merged
Conversation
* chore(flow): mint fn-251 implicit task * fix(work): worker waits out its own commands before returning - worker: never return while a started command is running; block on a host-backgrounded command and report its exit code - work conductor: check task status and live commands before accepting a return, on both rolling and wave routes - worker: temporary worktree, never git stash, to inspect another tree state - conduct checklist, generated mirrors, changelog Task: fn-251-worker-never-returns-with-its-own.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(work): attribute live commands to the returning lane, bound the wait by TIMEBOX Review round 1: scope the conductor's live-command check to the returning lane, exempt confirmed handovers, name TIMEBOX instead of an undefined cap, use the re-anchoring continuation worker, and keep the rolling loop moving. Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(work): single-worker lane attribution for the live-command check Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * chore(flow): complete fn-251.1 (receipt, completion review not required) Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(work): a handover with a live command still waits The handover exception covered every rolling return, so the default route never waited on a live gate. in_progress alone triggers nothing; an attributable live command always does. Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC
* chore(flow): mint fn-250 implicit task * feat(flowctl): spec close commits task statuses; task create/start reopens a closed spec - spec close writes each task's final status into the committed task files (R1) - task create (single and bulk) and start set a closed spec back to open (R3) - validate runtime-mismatch test injects the inconsistent state directly, since start now reopens Task: fn-250-land-redesign-one-named-pr-spec-closed.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * feat(make-pr): close the spec as the last commit before opening the pull request - a real create with every task done binds the spec branch to the head branch, closes and commits before the aid is composed (R2) - incomplete tasks open without closing and say so; dry-run and --update never close; a failed close or commit stops before the PR opens - after a close the HTML lens and body spill stay local so the close remains the last commit - the existing Ralph/autonomous refusal for incomplete specs and its pins are kept Task: fn-250-land-redesign-one-named-pr-spec-closed.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * feat(land)!: rewrite land as short prose over one named pull request - land takes one PR, reads the specs shipped at its head by branch name, and requires all of them closed (R4) - conflicts, then threads, then CI; one fix, one rerun, server-side catch-up only (R5) - merge needs current authorization, green checks, a non-blocking review decision and zero unresolved threads; squash pinned to the full head SHA (R6) - native stacks: link children, merge the lowest layer through the asynchronous call, delete the merged branch separately (R7) - nothing is written to the repository after a merge; tracker touchpoint through the API only (R8) - remove discovery, authorship probe, ledger, tick claim, post-merge tail, cascade, silence signal, reviewer request, merge-identity override, after-review patience and release-follow; retire eight land.* keys and the unused clean-review preset; legacy keys still load (R9) - land instruction text 1741 -> 185 lines; tests that pinned the deleted machinery are retired or rewritten - new close/make-pr tests use the canonical scripts path insert the package-import gate requires Task: fn-250-land-redesign-one-named-pr-spec-closed.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * feat(flow): landing stage passes land one pull request and the current authorization - flow no longer resolves source/base checkouts for land and reads no land ledger (R10) - a closed spec with an open pull request routes to the landing offer, or to landing under a merge destination - a confirmed merge ends the run; consent is re-checked at each dispatch; waits use the driver's cadence - a closed spec with no observed pull request stops at the host and is never given a replacement PR Task: fn-250-land-redesign-one-named-pr-spec-closed.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * docs(land): repo-wide landing recipe, review-gate options, chain-child rebase and upgrade notes - recipe that replaces repo-wide landing, the three ways to tighten the review gate, and the manual single-layer rebase (R11) - upgrade notes under Unreleased list every retired land.* key and behavior with the issue or PR it came from - docs, README, glossary and conduct notes corrected where R1-R10 made them false - test pins the upgrade notes against the retired key list Task: fn-250-land-redesign-one-named-pr-spec-closed.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(land): chain dependencies land on base evidence; close reports its writes Review round 1. A done dependency counts as landed only when the base reads it closed, so a parent closed at pull-request creation keeps its children chained. spec close reports modified_paths; make-pr stages from them, never closes a task-less spec and stops only on tracked changes in close paths; land reads a closed spec with no task files as unfinished. Retired land keys join the structural config advisory, and stale references to removed machinery are restated. Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(flowctl): base-evidence diagnostics, brief parity, memoized base ref Review round 2. A dependency closed locally but not recorded at the base says so and names the base; the no-base fallback is announced once per process; flowctl brief shares the landed-at-base answer; the spec chain and ready docs describe the base-evidence rule; the resolved base is memoized. Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(flowctl): brief keeps its zero-git contract; sibling diagnostics and per-cwd notice Review round 3. brief reads local dependency status again, as documented and tested, and the docs say how that relates to the schedulers' base evidence. A sibling's base diagnostic no longer reads as a dependency of the evaluated spec, and the no-base notice is tracked per working directory. Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * docs(flowctl): the no-base notice is per working directory Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * chore(flow): complete fn-250.1 (receipt, completion review not required) Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * chore(flow): close fn-250-land-redesign-one-named-pr-spec-closed Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(flowctl): a close on the base branch stands; reopen reports its write CI and review follow-up. Work closed directly on a checkout of the base branch has nothing left to merge, so dependents unblock as before (the functional smoke covers it). A task create or start that reopens a closed spec reports reopened_spec and modified_paths so the caller commits the spec file with the task. New tests read files as UTF-8, and the make-pr fence tests skip on Windows, where PATH shims cannot observe the spawns. Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC
* chore(flow): mint fn-249 implicit task Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * feat(make-pr): expand sparse aid input in validate and write - omitted changeType, additions, deletions and diffUrl are filled from the bound diff - rows inherit their group's source, requirement and task references per field - stored artifact stays the complete v1 object; complete input is unchanged Task: fn-249-make-pr-aid-artifact-flowctl-fills-the.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * feat(make-pr): aid validation reports every independent violation - validate and write collect violations with their field paths in traversal order - unparseable JSON reports the parse error alone; a row with no path is reported once - human output lists one violation per line; --json carries the same lines Task: fn-249-make-pr-aid-artifact-flowctl-fills-the.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * feat(make-pr): flowctl adds rows for unlisted paths and derives attention from path patterns - a changed path with no authored row gets an empty-summary row in an existing step group - attention defaults to mechanical or generated only for zero-judgment patterns, canonical otherwise - make-pr aid instructions ask for judgment fields and rows worth a sentence (line count unchanged) Task: fn-249-make-pr-aid-artifact-flowctl-fills-the.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * feat(flowctl): managed ignore block keeps PR aid generations and write locks local - ignore artifacts/*/pr-cognitive-aid/*.json and its .write.lock; lenses and other artifact kinds stay trackable - changelog gives the one-time untracking step; consumer docs state the per-clone limit - regression test: a broad stage no longer sweeps aid files in; tracked files are never untracked Task: fn-249-make-pr-aid-artifact-flowctl-fills-the.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * test(make-pr): sparse-written aid generations resolve as current at an unchanged head - reuse stays at make-pr's resolve step; a same-head successor remains a deliberate correction - table-driven current-seam cases: unchanged, moved head, missing artifact, failed validation - docs and changelog state where reuse happens Task: fn-249-make-pr-aid-artifact-flowctl-fills-the.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * docs(flow): recomputed authored-bytes figure for the make-pr measurement record (fn-249 R8) - authored_bytes.py proves each omission through the branch's own expansion - strict identity saves 2.56% over the 39 tracked artifacts; limits and negative findings kept Task: fn-249-make-pr-aid-artifact-flowctl-fills-the.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * docs(flow): add the assumption-labelled authored-bytes figure beside the strict one (fn-249 R8) - strict 2.56% stays the headline; 16.66% holds under two stated assumptions - neither figure counts rows an agent would now not write Task: fn-249-make-pr-aid-artifact-flowctl-fills-the.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * style(make-pr): clear lint findings in aid validation and its tests Task: fn-249-make-pr-aid-artifact-flowctl-fills-the.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(make-pr): one sparse expansion for every aid entry point; resolvable diff links Review round 1. render --file and html-input --file expand sparse input through the same call as validate and write, so a dry run renders what validate accepted. An omitted diff link is derived as the repo-relative blob form bound to the head, or left absent when it cannot be derived. The consumer contract states the derived fields, the link shape and the empty-summary rule. Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(make-pr): a deleted path keeps no derived link; name the forge assumption Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * docs(flow): make-pr measurement record, p1 after sparse input (fn-249 R8) Null result: medians 25,653 tokens, 20 tool calls, 273 s against 21,627, 22 and 251 s at p0, inside the run-to-run spread. Authored aid characters are unchanged on this fixture. Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * chore(flow): complete fn-249.1 (receipt, completion review not required) Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * chore(flow): close fn-249-make-pr-aid-artifact-flowctl-fills-the Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC
…ult base (#456) * fix(flowctl): read a squash-landed dependency as landed on a non-default base A closed dependency whose close is not recorded at the default base now falls through to branch ancestry: only a branch tip in HEAD's history keeps it chained. A non-ancestor tip, a deleted branch, or no recorded branch counts as landed. True merges onto a non-default base stay conservative. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * fix(flowctl): read the dependency close at the shared history, not the branch tip A stacked parent that gains a commit after the child branched no longer reads landed. The dependency's spec is read at merge-base(ref, HEAD); a close found there keeps it chained. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y
* chore(flow): mint fn-252 implicit task Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * feat(pr-aid): accept optional authored briefing fields - userImpact, blastRadius, unverifiedSteps are additive on changeWalkthrough - schema version stays 1; wrong types are rejected naming the field - consumer doc lists the fields as additive Task: fn-252-make-pr-body-a-short-briefing-rendered.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * chore(codex): regenerate mirror and tracker manifest for the aid fields Task: fn-252-make-pr-body-a-short-briefing-rendered.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * docs(flow): fn-252 R4 and R7 settle the source of Tradeoffs, Open items and proof outcomes Decision by the maintainer on 2026-09-21: four optional authored fields plus an optional pass, fail or unverified outcome on proof cells; no separate steps-not-verified field; the renderer ticks only outcome pass. Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(pr-aid): align authored fields and proof outcomes with amended R7 - drop unverifiedSteps; an unverified step is a proof cell with outcome unverified - add optional tradeoffs and openItems strings on changeWalkthrough - add optional proof-cell outcome (pass, fail, unverified); other values rejected naming the cell Task: fn-252-make-pr-body-a-short-briefing-rendered.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * feat(pr-aid): render one bounded seven-section briefing - retire the compact and full forms and the inline size threshold - Scope as numbered diff-fenced trees with counted remainders and one coverage line - Verification ticks only outcome=pass; fail and unverified stay unticked; no outcome is a plain item - identity in one HTML comment; 40-line budget with deterministic collapse - literal apostrophes and quotes; markup stays neutralized Task: fn-252-make-pr-body-a-short-briefing-rendered.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * refactor(make-pr): deliver the rendered briefing after spec close - cut the make-pr instruction text an ordinary run loads from 2,917 to 597 lines - delete hand-assembled body-section recipes, mermaid-rules.md and the live-QA section reference - compose the aid artifact after the spec-close commit; explicit --base resolves against origin - author guidance for userImpact, blastRadius, tradeoffs, openItems and proof outcomes - drop the unread marker comment emitters Task: fn-252-make-pr-body-a-short-briefing-rendered.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * chore(make-pr): regenerate mirror, restore the resolve-pr hint, changelog - sync-codex twice (second run no-op) and tracker manifest - retire two closer-literal rules whose canonical text is gone - Unreleased notes credit the body-length reporter (#447) Task: fn-252-make-pr-body-a-short-briefing-rendered.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(make-pr): drop a token the tracker caller oracle forbids Task: fn-252-make-pr-body-a-short-briefing-rendered.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * test(make-pr): smoke follows the creation seam into create-and-finalize Task: fn-252-make-pr-body-a-short-briefing-rendered.1 Claude-Session: https://claude.ai/code/session_015PwJLWvp9684RcQ5DgMVsC * fix(pr-aid): collapse the briefing lowest attention first, review round 1 Proof cells without an outcome collapse before pass cells, then described file rows, then the tail of multi-line authored fields; fail and unverified cells go last and a one-line field is never replaced by a count. Coverage includes file-level requirement citations, setext underlines of any length are neutralized, and the unused --no-mermaid flag is removed from make-pr. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * fix(pr-aid): apply a collapse step only when it shortens the briefing A step that would add its own blank and counted line without saving a line is skipped and the authored content stays. The fleet smoke harness stops passing the removed --no-mermaid flag. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * fix(pr-aid): hold the 40-line briefing bound by construction When the ordered steps leave the body over budget, a final pass hides proof cells across outcomes in attention order behind one shared checkpoint, then falls back to the remaining collapsible content. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * refactor(pr-aid): drop the unreachable collapse fallback and its assert Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * chore(flow): fn-252 task done, host review SHIP after 4 rounds Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * docs(flow): record the p2 make-pr measurement and correct the p1 reading p2 medians: 10,918 output tokens, 15 tool calls, 129.3 s, about half of the baseline. The p1 section gains the composing-call correction and the harness copy is the fixed one that parks run leftovers. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * chore(flow): close fn-252-make-pr-body-a-short-briefing-rendered Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * perf(pr-aid): assemble the briefing once per collapse candidate The shorten-or-restore collapse assembled the body three times per dropped row and recounted every group each time, which put the 500-file render over its p95 budget on slower runners. Counted lines are memoized per group. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * docs(make-pr): the optional memory entry follows the prose contract Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y
…t closes several specs (#458) * feat(pr-aid): render the briefing in full, readable over short The 40-line bound and its collapse machinery are gone. Authored prose, every described file row up to ten per group, and every proof cell render as written; mechanical, generated and undescribed files stay counted. With no declared requirements the coverage block is omitted. make-pr leaves an already closed spec alone. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * feat(make-pr): one briefing for a branch that closes several specs The export lists every spec the range closes (done at HEAD, not at the base). The artifact takes an optional specIds list; with several specs, requirement ids are qualified (fn-250:R4) and coverage renders one line per spec. With no branch match, make-pr hosts on the highest-numbered closed spec. Single-spec exports and bodies are byte-identical. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * fix(make-pr): bound the several-spec set to work that landed on this branch A sibling joins the set only when one of its tasks completed in the range and its close did not arrive through this branch's own history, so a record-only close and a stacked unmerged parent both stay out. Discovery costs one diff on the single-spec path. Short ids come from one helper, so tracker-keyed specs qualify. Adds spec closed-in-range. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * fix(make-pr): read the several-spec set without git's localized messages A missing path is read from the tree, not from stderr text. A sibling counts when the range touches one of its task files, since the tracked task status is persisted late and can read stale. Identity across a slug rename is the short id. The fence surfaces a failed closed-set read and skips it with no base. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * test(make-pr): seed the base commit in the stale-task-status regression Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * docs(make-pr): state the task-file membership rule; pin the body-only case Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * fix(make-pr): read a stacked parent's close by spec identity, not by path A spec record that moved between the spec directories, or changed slug, since the parent branch no longer reads as absent there, so a stacked unmerged parent stays out of the several-spec set. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y
…fing Medians 11,787 output tokens, 19 tool calls, 137.5 s: the halving against the baseline holds with the full-render body. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 06ded96. Configure here.
…on for authors, land on several specs (#460) * feat(make-pr, land): group summaries render, an artifact skeleton, land on a several-spec pull request Group summaries render under their titles and a row's own requirement ids win over inherited ones. The authoring guidance carries a validated skeleton and the limits authors used to learn from failed calls. Land selects the specs a pull request closes when no branch name matches. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * fix(make-pr, land): linked file rows that wrap, every group rendered, land's fallback excludes record-only closes File rows leave the code fence and link to their diff, fileless groups keep their title and summary, coverage names groups, Scope opens with a size line and the rest of the diff gets one attributed line. The skill states the same-record source rule and the limits authors met as errors. Land's several-spec selection needs a task entry changed against the base, names the recursive trees API, and gains a push-date fallback. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * fix(pr-aid): break mention and issue tokens instead of entity-encoding them The forge resolves mentions and issue references after decoding entities, so the entity form still pinged and linked. A zero-width space after the marker renders inert (checked against the forge renderer); URLs in prose stay intact. Land keeps its repair sentence in one place. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * fix(pr-aid): match the forge's mention boundary; keep URL punctuation linkable A mention after a full stop or a non-ASCII letter still pinged. Pipes and backticks inside a URL are percent-encoded so the link survives. Land's merged replay names its base on the no-match path. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y * refactor(land): drop the merged-replay base clause It covered a replay of a several-spec pull request on the no-match path: too rare to earn words in a skill that is meant to stay short. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y
Medians 8,174 output tokens, 13 tool calls, 100.1 s: about 60% under the baseline. Tool calls fell from 19 to 13 against p3. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y
Only a mention pings someone, so only mentions stay inert. A body that names the pull requests it rolls up should link to them. Claude-Session: https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
Land and make-pr had each grown to thousands of lines of instructions and shell that an agent re-executed on every run, and most defects reported against them were in that machinery, not in the decision to merge or in what a reviewer reads. This branch fixes the two design choices land was compensating for (finished work is now committed, and the spec closes at the pull request's head before the merge), rewrites land as short prose over one named pull request, and moves the pull-request body to a briefing that flowctl renders from one authored object. It closes four specs and carries four changes with no spec. 175 files change, 8,242 lines added and 12,997 removed; most of the removal is deleted instructions and the tests that pinned them.
What changes for a user or operator
Land no longer finds pull requests by itself: you name one, and it merges only with authorization given in the current session. Repo-wide landing, release-follow, the reviewer request and eight land config keys are gone; the changelog lists each with its replacement, and old keys load with a notice. A finished spec is closed on the branch before its pull request opens, so a protected base is no longer a problem. Pull-request bodies look like this one.
Scope
175 files changed; +8242/-12997 lines; 46 generated, 8 mechanical.
1. fn-250: land becomes short prose over one named pull request
Read this group first; it carries most of the risk. Check that spec close commits every task's final status before the pull request opens, and that land merges only a head-pinned squash with in-session authorization and writes nothing to the repository after the merge.
plugins/flow-next/skills/flow-next-land/workflow.md: The whole of land now: gate order, merge preconditions, stack layers and branch delete are prose here, and none of the merge path has run against a live pull request. Its spec selection gained a fallback in fix(make-pr, land): a body a reviewer can read on the forge, a skeleton for authors, land on several specs #460 for a pull request that closes several specs. [fn-250:R4, fn-250:R5, fn-250:R6, fn-250:R7, fn-250:R8]plugins/flow-next/skills/flow-next-land/SKILL.md: Arguments and the authorization rule: land merges only what the user or the calling flow authorized in this session. [fn-250:R4, fn-250:R6, fn-250:R9]plugins/flow-next/scripts/flowctl.py: One file, every group (1,279 added, 853 removed). Here: spec close commits task statuses, a new task reopens a closed spec. Later groups (fn-249, fn-252, no spec): sparse aid input, the closed-in-range read, and the briefing renderer with its tick rule. fix(make-pr, land): a body a reviewer can read on the forge, a skeleton for authors, land on several specs #460 rewrote the renderer (group summaries, linked file rows that wrap, one line for the rest of the diff) and _pr_aid_plain_text, which makes mention tokens inert; fix(pr-aid): keep issue and pull-request numbers as live links #461 keeps issue and pull-request numbers as live links. [fn-250:R1, fn-250:R3, fn-250:R9, fn-249:R1, fn-249:R3, fn-252:R1, fn-252:R4]plugins/flow-next/skills/flow-next-make-pr/workflow.md: The spec-close fence: branch name aligned, close committed last, failure stops before the pull request opens, dry run closes nothing. [fn-250:R2]plugins/flow-next/skills/flow-next-flow/references/tail.md: Flow's landing stage passes the pull request and the current authorization as arguments; no checkout resolution, no ledger read. [fn-250:R10]plugins/flow-next/tests/test_spec_close_reopen.py: Behavior tests for close and reopen, including a fresh clone with no runtime state. [fn-250:R1, fn-250:R3]plugins/flow-next/tests/test_make_pr_close.py: Runs the close fence against real repositories: incomplete task, failed close, dry run, already closed. [fn-250:R2]plugins/flow-next/tests/test_land_named_pr.py: Pins the land prose by substring, the fix(make-pr, land): a body a reviewer can read on the forge, a skeleton for authors, land on several specs #460 selection fallback included. It proves the words are present, not that the merge path works. [fn-250:R4, fn-250:R5, fn-250:R6, fn-250:R7, fn-250:R8, fn-250:R9]CHANGELOG.md: Upgrade notes: every retired key and behavior with the issue it came from. This is what an upgrading user reads. [fn-250:R11]plugins/flow-next/docs/troubleshooting.md: The recipe that replaces repo-wide landing, and the manual rebase for a conflicted chain child. [fn-250:R11]2. fn-249: the agent authors judgment, flowctl fills the rest of the aid artifact
Check that sparse and complete input store the same object and that a supplied value which disagrees with the diff still fails. The filling code is in flowctl.py (row in group 1); the tests here are the contract.
plugins/flow-next/tests/test_pr_cognitive_aid_sparse.py: Sparse and complete input must store identical objects, renames and copies included. [fn-249:R1, fn-249:R2, fn-249:R4, fn-249:R5]plugins/flow-next/tests/test_pr_cognitive_aid_violations.py: All violations in one call, stable order, no cascade from a row with no path. [fn-249:R3]plugins/flow-next/tests/test_pr_cognitive_aid_reuse.py: Unchanged base and head reuse the artifact; a moved head or an invalid artifact does not. [fn-249:R7].flow/.gitignore: Two ignore lines for aid generations and write locks. Files already in an index need the one-time step in the changelog. [fn-249:R6].flow/artifacts/fn-249-make-pr-measurement/README.md: The measurement record: method, five points, and the null result after this spec. [fn-249:R8]3. fn-252: the body is a briefing flowctl renders from the artifact
Check the tick rule in the renderer (only outcome pass ticks a cell) and that empty sections do not render. Then read the authoring guidance for a rule that was deleted and is still needed: an ordinary run now loads under 600 lines, down from about 2,800.
plugins/flow-next/skills/flow-next-make-pr/pr-cognitive-aid.md: What the agent is told to author, 75 lines. fix(make-pr, land): a body a reviewer can read on the forge, a skeleton for authors, land on several specs #460 added a validated artifact skeleton and the limits authors used to learn from failed calls. The quality of every future body depends on it. [fn-252:R7, fn-252:R9]plugins/flow-next/skills/flow-next-make-pr/create-and-finalize.md: Push, create or update, draft rules and linkage, cut from 765 lines to 211. Look for a rule that was dropped and still needed. [fn-252:R9, fn-252:R10]plugins/flow-next/docs/pr-cognitive-aid.md: Consumer contract for the artifact: the new fields are additive and the schema version stays 1. [fn-252:R7]plugins/flow-next/tests/test_pr_cognitive_aid_briefing.py: Rendered-output tests: section order, omitted empties, literal quotes, neutralized markup, and from fix(make-pr, land): a body a reviewer can read on the forge, a skeleton for authors, land on several specs #460 the linked rows, rendered group summaries and inert mention tokens. [fn-252:R1, fn-252:R2, fn-252:R3, fn-252:R4, fn-252:R5, fn-252:R8]plugins/flow-next/tests/test_pr_cognitive_aid_authored_fields.py: Type checks on the four authored fields and the proof outcome. [fn-252:R7]plugins/flow-next/scripts/make-pr_smoke_test.sh: End-to-end smoke for the new body. [fn-252:R1, fn-252:R9]4. No spec (#456, #458, #460, #461): landed dependencies, several specs in one body, a body that reads on the forge
Check the closed-in-range rule that decides which specs a branch ships: a sibling joins only when the range touches one of its task files and its close did not arrive through this branch's own history. For #460, check the rendered form of this body and land's selection fallback (rows in groups 1 and 3). For #461, check that issue and pull-request numbers stay live links and only mentions are made inert.
plugins/flow-next/tests/test_pr_cognitive_aid_multi_spec.py: Several-spec export and render, the record-only close and the stacked unmerged parent that must stay out.plugins/flow-next/tests/test_spec_chain.py: The dependency's close is read at the shared history, not the branch tip; true merges stay conservative.plugins/flow-next/tests/fixtures/pr-cognitive-aid/single-spec-export.json: Pins that a single-spec export and body stay byte-identical.plugins/flow-next/docs/flowctl.md: Reference for the new closed-in-range command and the several-spec membership rule.5. fn-251: a worker does not return while its own commands run
Prose only, safe to read last. Check that the worker rule and the conductor's check say the same thing: a return is accepted only when no command the worker started is still running.
plugins/flow-next/agents/worker.md: The worker rule and the worktree rule, three lines. [fn-251:R1, fn-251:R3]plugins/flow-next/skills/flow-next-work/phases.md: The conductor's check: in progress with a live command means wait and resume the same worker, no failed attempt counted. [fn-251:R2]agent_docs/conduct/work.md: Three checklist items covering the rules. [fn-251:R4]Rest of diff: 8 mechanical files; 46 generated files; 93 not described files
Coverage fn-249: R1 → groups 1, 2; R2 → group 2; R3 → groups 1, 2; R4 → group 2; R5 → group 2; R6 → group 2; R7 → group 2; R8 → group 2
Coverage fn-250: R1 → group 1; R2 → group 1; R3 → group 1; R4 → group 1; R5 → group 1; R6 → group 1; R7 → group 1; R8 → group 1; R9 → group 1; R10 → group 1; R11 → group 1
Coverage fn-251: R1 → group 5; R2 → group 5; R3 → group 5; R4 → group 5
Coverage fn-252: R1 → groups 1, 3; R2 → group 3; R3 → group 3; R4 → groups 1, 3; R5 → group 3; R6 → group 3; R7 → group 3; R8 → group 3; R9 → group 3; R10 → group 3; R11 → group 3
Blast radius
Everyone who lands with flow-next, and every tool that drives land or reads its verdict line, its ledger or its config keys. Anything that parses the old body sections breaks; the stored artifact keeps its schema. Read first: the land workflow (146 lines, all new), then spec close and reopen in flowctl, then the make-pr close fence. 46 of the 175 changed paths are generated mirrors and can be skipped. Unproven: land's merge, stack and branch-delete paths have never run against a live pull request.
Verification
Tradeoffs
Land's merge path is prose an agent follows, not code, so tests can pin its words but not its behavior; a flowctl land command was rejected to keep land small. Automated rebase of a conflicted chain child was dropped for a documented manual step. The 40-line body bound from fn-252 was reversed in #458: short bodies were not usable for review, so the body is now as long as its authored content. The diff-fenced file tree from the merged briefing spec was replaced in #460 by linked list rows, because fenced code does not wrap on the forge. Issue and pull-request numbers in authored prose stay live links (#461); only mentions are made inert. fn-249 alone made make-pr slightly slower; the result is kept because fn-252 depends on its input format.
Open items
This is a major release and it is not cut: no version bump, no changelog cut, no tag. The maintainer's isolated tests are pending. Land's merge, stack and branch-delete paths need a first live run. This pull request is the first real several-spec run.
https://claude.ai/code/session_01A4iyNtxjRyP4ig3Zuvi91Y