Flight Plan
Flight Plan — Persisted, Ordered Warp Run Plan
Section titled “Flight Plan — Persisted, Ordered Warp Run Plan”A flight plan amortizes warp-drive’s discovery: derive the ordered “what to work on, in what order” once, persist it as a GitHub Issue, and run a warp session against it — instead of re-discovering the queue every cycle. It also leaves a durable, human-readable record of what a session set out to do.
Part of the amortized warp planning
capability (#1067). This page covers the artifact and its --flightplan consumer
(#1068); cheap mid-run revalidation of the plan is the sibling requirement (#1069).
/flightplan area:amortized-warp-planning # derive + persist the ordered plan → prints #Nwarp-drive --flightplan <N> # run it: in order, never widening, ticked as it goesThe plan is a GitHub Issue labelled flight-plan — never a local file.
/flightplan is a universal command (commands/flightplan.md, #1872):
bob init links it into every project alongside /warp-drive, so it needs no
manifest entry. A manifest that still names it is reported by bob provision as
[already-global] … (universal) and bob provision add command flightplan is a no-op — drop the
entry; nothing breaks either way.
What it is
Section titled “What it is”- An issue, labelled
flight-plan, whose body is an ordered checklist:plus machine markers (- [ ] #1068 Issue-backed flight-plan artifact — priority p3-medium, req- [ ] #1069 Cachable warp-drive discovery — priority p3-medium, req<!-- flightplan:scope … -->,fingerprint,cut, and — when declared — the integrationbranch, #1181). - A fixed scope + order a warp run consumes — the same “ordered list that never
widens” semantics as
--issue N,N,N, plus check-off and progress notes. - A live record: as each requirement completes, its checkbox is ticked and a one-line progress note is appended, so the issue shows the run’s progress at a glance.
Why it exists
Section titled “Why it exists”Warp-drive re-derives “what’s next” on every cycle. That is deliberate (project state changes as work lands), but it burns tokens, and the ordering/rationale it produces is thrown away each cycle — leaving no readable record of a session’s intent. A flight plan derives the order once, persists it in the existing source of truth (GitHub Issues), and lets the run consume it — cutting repeated discovery cost and leaving a durable trail.
It is not a new source of truth. The ordered queue already exists as approved
- priority +
serial-onlyin GitHub Issues./flightplanreuses that ordering (discover-queue.sh, the same queue/what-nextand/groomderive from) and only records it. It does not re-implement cluster reconciliation.
Creating a plan
Section titled “Creating a plan”/flightplan # whole approved project queue/flightplan area:<slug> # one workstream/flightplan 1068,1069 # a specific ordered issue listAn explicit issue list keeps its given order verbatim (#1508) — the list is
the ordering decision, which is exactly what you reach for when the label-derived
order is wrong (e.g. a body-level “Blocked by #NN” the queue rule can’t see).
Area and whole-project scopes order by the shared queue rule (priority → bug
before req → issue number). The membership filter still applies to an explicit
list: an issue that isn’t req/bug, carries blocked, or isn’t approved,
is dropped — and dropped issues are named, with the reason, never silently
omitted (#1794):
$ /flightplan 8,9 [dropped] #9 proposed item — not `approved` — pass --allow-unapproved to seat it anywayCreated flight-plan #4242 (issues:8,9) — 1 item(s), …, 1 issue(s) droppedNaming an issue number is not the same as approving it. A warp-drive --flightplan run works its items in order and never re-checks approval at pickup,
so an un-approved issue seated into a plan gets worked autonomously with no further
human touchpoint. The approved label is that touchpoint, and an explicit list does
not substitute for it.
When you genuinely intend to plan un-approved work, --allow-unapproved is the
opt-in — and it is loud rather than silent, reporting every issue it admits:
$ /flightplan 8,9 --allow-unapproved [un-approved] #9 proposed item — un-approved, seated by --allow-unapprovedIt relaxes only the approved half of the rule; blocked and the req/bug type
filter still apply. With --json, both accounts are in the payload as dropped[]
and unapproved[], so a caller can assert on them instead of parsing prose.
Under the hood the command calls the engine:
node ~/.claude/scripts/warp-drive/flightplan.js build --area <slug> --json# → { "plan": 1070, "scope": "area:<slug>", "items": 2, "fingerprint": "69c8…", "refreshed": false, "carried": 0 }build is idempotent per scope: it finds the existing open flight-plan issue
whose embedded scope marker matches and refreshes it in place rather than
creating a duplicate ("refreshed": true).
What a refresh carries across (#2283). Rows are re-derived from the live
issues, but every declaration the existing body already records is carried
over, matched by issue number: the integration branch (#1181), the gate mode
(#1415), depends_on edges (#1613), the recorded name (#2309) — and each
row’s tick. A ticked-but-open row is normal on an integration stream
(warp-drive ticks an item at requirement_complete; its issue closes only when
the stream is finalized), and a ticked-closed row is simply done; a refresh
keeps both ticked, so remaining never lists finished work a second time. A
newly derived row starts unticked. The JSON result reports the count as
carriedTicks (0 on a create). The L3+ kickoff auto-refresh of a stale plan
(#1667) runs this same build path, so a routine kickoff never resets a plan’s
progress either.
Growing an issues: plan in place (#2283)
Section titled “Growing an issues: plan in place (#2283)”An issues: plan’s scope token is its issue list, so re-building with a
longer list is a new token: build finds no matching plan and creates a
second one, while the running session stays bound to the old number. To add
(or drop) issues on the plan you already have, name it:
node ~/.claude/scripts/warp-drive/flightplan.js build --plan 2092 --issues 2034,2037,2275 --no-interview --json# → { "plan": 2092, "refreshed": true, "scope": "issues:2034,2037,2275", "rescopedFrom": "issues:2034,2037", "carriedTicks": 2, … }bob plan build --plan 2092 --from-issues 2034,2037,2275 # the same, from a shell--plan <N> refreshes that plan: its scope marker is rewritten to the new
list, every tick, the branch, gate mode, depends_on edges and name are carried
across as on any refresh, the new row is seated in list order and starts
unticked, and no second plan is created. rescopedFrom names the token it
replaced (null when the list did not change — a repeated call is an
idempotent refresh). It is refused, with nothing written, when the plan is
closed, is not an issues: plan (an area / project / farm plan is re-derived
by its own scope flags), or when the new list already belongs to a different
open plan — two plans never claim one scope (#1722, from the other direction).
Declaring an integration branch (#1181)
Section titled “Declaring an integration branch (#1181)”A plan can declare the stream its work accumulates on, so
warp-drive --flightplan <N> resumes the whole workstream — scope and branch —
from one flag:
node ~/.claude/scripts/warp-drive/flightplan.js build --area <slug> --branch stream/<slug> --jsonThe declaration is recorded in the plan body (a human-readable Integration
branch line plus a <!-- flightplan:branch … --> marker) — the plan issue stays
the single durable record of the workstream’s intent. A refresh preserves the
declaration when neither flag is passed; --no-branch clears it. /flightplan
asks about the branch as an interview question when the checkout already has a
stream bound (_branch.session) or you request one.
At kickoff, a flightplan-scoped run with a declared branch opens/resumes that
stream automatically — the same code path as
bob warp session start <branch> --flightplan <N> (#1177), including the
start-of-work freshness sync (#1178). A declaration conflicting with a
different already-pinned stream in the checkout is refused with a clear
message (never a silent rebind): end the stream (bob warp session end /
bob warp finalize), re-bind explicitly, or update the plan. A plan with no
declaration behaves exactly as before — per-requirement merge, or whatever stream
is manually bound. flightplan.js status reports the declared branch, and
bob warp status shows flight-plan #N → stream <branch> during the run.
Running after another plan (#2298)
Section titled “Running after another plan (#2298)”A plan can wait for another plan to finish. Declare the predecessor at build time:
node ~/.claude/scripts/warp-drive/flightplan.js build --issues 2233,2236 --after 2092 --json# → { "plan": 2285, "after": [2092], … }--after takes one number, a comma-separated list, or repeats. The plan body
gets a Runs after line and one <!-- flightplan:after #N --> marker per
predecessor. A refresh keeps the declaration when neither flag is passed, like
the branch marker. --no-after clears it.
build refuses three declarations and writes nothing. Each exits 2 with a named
error (predecessorErrors in --json):
| Code | When |
|---|---|
not-a-flight-plan |
the predecessor issue has no flight-plan label |
self-reference |
the plan names itself |
cycle |
a predecessor waits, directly or through its own predecessors, on this plan. The message shows the path, e.g. #2285 -> #2092 -> #2285 |
While any predecessor is open and still has unfinished items, the plan is waiting:
remainingreturns no runnable items.--jsonis[], and--json --allcarrieswaitingOn: [{ plan, done, total }].status,lifecycleandauditclassify the planwaiting, with the reasonwaiting on #2092: 38/57 done.
A predecessor releases the wait when it closes, whether finished or abandoned,
or when every one of its items is done. An unreadable predecessor keeps the
plan waiting: nothing is released on evidence that could not be checked.
reconcile reports each release once (see Plan lifecycle).
Groom first if needed.
/flightplanrecords the order that already exists — it does not compute it. If the cluster’s dependency chain,serial-only, orapprovedlabels aren’t settled yet, run/groom [area:<slug>]first.
Pre-flight interview (#1087)
Section titled “Pre-flight interview (#1087)”/flightplan does not record the scoped issues blindly: before the plan is cut it
scans them for open questions and interviews you to close as many as possible,
so warp-drive launches with maximum resolved context.
- What’s detected: “Open question(s)” / “Decisions needed” sections and
unchecked decision checkboxes (
flightplan.js scan), plus ambiguity the model spots while reviewing the issues (vague or contradictory ACs, unmade decisions in the Notes). - How you’re asked: one question at a time, with proposed answers where
inferable — via
ask_remotewhen RDB is enabled,AskUserQuestionotherwise. Every question can also be deferred. - Where answers land: in the target issue, never a local file. Scanned
questions get a dated
**Resolved (flight-plan interview YYYY-MM-DD):**marker on their own line (flightplan.js resolve); model-detected ambiguity is resolved via a clearly-attributed issue comment. - Idempotent: resolved and deferred questions carry their marker in the issue
body, so re-running
/flightplanfor the same scope never re-asks them. - Carried, not dropped: deferred questions (and still-open ones under
--no-interview) are listed in the plan body under “Open questions carried into this plan”. A deferral that awaits a human-only action (paid account, API key, sign-off) additionally gets a conforming assignedtodofiled — see Gating human-action todos below. - Headless:
/flightplan <scope> --no-interview(engine:build --no-interview) skips the interview. Without the flag,buildrefuses (exit 3) whileopenquestions remain — the gate that keeps interview-on the default.
Gating human-action todos (#1088)
Section titled “Gating human-action todos (#1088)”Some blockers surfaced at plan-build time are human-only actions — enable a
setting, provision an account, flip a switch. Instead of stalling the run,
/flightplan files each as a todo issue assigned to the operator (conforming
to the todo format contract, with a
Blocked by #NN link to each issue it gates), and build seats it in the plan as a
gating item:
- [ ] #900 **[human todo]** Enable Remote Login on the Mac — todo, gates #602- [ ] #602 Signal extraction — priority p2-high, req- Seating is automatic and idempotent. Every
buildre-derives gating rows from live opentodoissues: a todo gating an in-scope item lands immediately before the earliest item it gates; todos gating nothing in scope are left out (the plan never widens). - Gates follow the canonical TODO lifecycle. The
completedlabel is the only release signal. While the todo is open without it, the work it gates is skipped, not failed —remainingsimply doesn’t return it, and the run proceeds to non-gated items. Once the human addscompleted, the gated work is runnable again on the next cycle. A todo closed withoutcompletednever releases: the gated work is reported moot (cancelled), not resumed. - Release is recorded in the plan. When
remainingsees a released todo whose row is still unchecked, it ticks the row and appends a progress note (“Human action #NN completed — gate released”) — the same check-off treatment as a requirement item.statusreports gated vs runnable counts and each todo’s gate state (pending/released/cancelled).
Barrier vs skip: ordered plans (#1415)
Section titled “Barrier vs skip: ordered plans (#1415)”Skipping a gated item is right for a plan of independent items — one req parks while unrelated work proceeds. It is unsafe for an ordered chain: skipping the gated item hands the runner the next one, so the run walks straight past a human checkpoint into work that was supposed to be downstream of it.
A gated item labelled serial-only is therefore treated as a barrier. The
run order is truncated there, and every later item is withheld too — whatever its
own gate state:
#1845 stage 1#1846 stage 2#1847 stage 3(gated) #1848 stage 4 — waiting on todo #1854(withheld) #1849 stage 5 — downstream of barrier #1848(withheld) #1851 stage 7 — downstream of barrier #1848serial-only already declares “this must not run out of band” — it is what
excludes an issue from bob fork --from-issues fan-out. The flight-plan engine now
reads the same declaration one step further, so no authoring change is needed:
label the stages serial-only, as an ordered plan should be anyway.
A barrier is anchored to the gate’s plan position, not to the gated item’s
liveness. Closing an item does not release the chain below it while its todo is
still pending — the pending todo is the plan saying the human action has not
happened, and that is the fact the run order honours. This holds for every close
reason: completed (the stage’s work happened out of band; the checkpoint deploy
did not), not planned (dropping a stage from an ordered migration is a human
call, not a silent continuation), and duplicate (the work moved, the checkpoint
did not). To free the chain, clear the todo — closing the stage is not a
substitute.
| Status | Meaning | What clears it |
|---|---|---|
runnable |
Nothing holds it | — |
gated |
Held by its own pending todo | Add completed to that todo |
withheld |
Held by an upstream barrier — it names the barrier, not a todo of its own | Clear the barrier; the whole chain below it frees in one step |
moot |
Its gating todo was closed without completed, or it sits downstream of one that was |
Nothing — the chain is broken, not paused |
An item that is closed on GitHub but still holding is reported in its normal
bucket (gated or moot) with closed: true on the row and a [closed] marker
in the printed line — there is no fifth status, so the --all shape is stable
for consumers. The actionable fact is unchanged: by still names the pending
todo, and clearing that todo is still what frees the chain. A closed item that
holds nothing is simply skipped and is not reported at all.
Releasing a gate un-withholds everything down to the next barrier in one step —
you never clear a chain one item at a time. remaining --json --all prints the
full { gateMode, runnable, gated, withheld, moot } classification (plain
--json stays the runnable array the runner consumes).
status and remaining resolve gates from the same state. They share one
resolution path, so the operator’s read of a plan and what the runner actually
consumes cannot disagree: same gate map — including gates from open todos that
are not plan rows (a Blocked by #NN in any open todo counts) — same handling
of items closed outside the plan, same barrier position. If status shows a
chain withheld behind a barrier, the run will stop exactly there; if it shows an
item runnable, the runner will pick it up. status is read-only, so inspecting
a plan never changes it — the reconcile tick that ticks released todo rows
belongs to remaining alone.
Default is unchanged. A plan with no serial-only items keeps pure skip
semantics — a gated req parks and later work still runs, and a closed item there
is simply skipped, barrier anchoring included.
Overriding. Add a marker to the plan body to force either mode:
<!-- flightplan:gate-mode barrier --> <!-- every gated item is a barrier --><!-- flightplan:gate-mode skip --> <!-- nothing is a barrier (pre-#1415) -->Use barrier for an ordered plan whose items were never labelled serial-only;
use skip to opt a serial plan back out. The marker is hand-authored, and build
preserves it across an idempotent refresh — an unrecognised value is ignored and
the serial-only inference applies.
DAG ordering: depends_on (#1613)
Section titled “DAG ordering: depends_on (#1613)”By default a plan is a strict total order — item N waits for item N-1 even when they are independent. Declaring dependencies turns it into a DAG: only real prerequisites order the run, so independent items fan out in parallel and the plan completes in ~critical-path time instead of sum-of-items time.
An item line may carry depends_on: #NN [#MM] in its rationale:
- [ ] #401 Build the parser — priority p2-high, req- [ ] #402 Build the emitter — priority p2-high, req- [ ] #403 Wire parser to emitter — priority p2-high, req, depends_on: #401 #402This is the same grammar the fake-edge audit
(depends_on: #NN, Depends on #NN) treats as a real edge — a declared
dependency is never flagged as fake. Both parsers import the one definition in
scripts/warp-drive/depends-on.js (#2297), so they cannot diverge.
Grammar (#2297). A declaration is the keyword — depends_on or Depends on, any case, an optional colon — followed by a list of #NN refs. Refs may be
separated by whitespace, a comma, and or &, in any combination, so
these all declare every ref:
depends_on: #401 #402Depends on #401, #402Depends on #401 and #402Depends on #401, #402 and #403 (and the Oxford form: #401, #402, and #403)Depends on #401 & #402The list ends at the first token that is not a #NN ref. So and followed
by a word ends it: Depends on #2231 and farm tick (#2054) declares #2231
only — the parenthesised #2054 is a mention, not an edge. A declaration must
start with a ref (Depends on and #9 declares nothing), and a bare #NN
mention anywhere else is never a dependency.
A negated mention is not a declaration (#2310). A keyword directly preceded
by no longer, never, not, n't or nor declares nothing, and the whole
ref list after it is dropped:
Groomed 2026-09-13: no longer depends on #2054 (declares nothing)Depends on #2045, #2048 (declares #2045 and #2048)This matters because grooming records edge removals in prose. Without the
guard, a note saying an edge was dropped re-created it, which manufactured
depends_on cycle refusals out of the very notes that recorded the fix. Only
the words immediately before the keyword negate it, so a negative earlier in
the sentence is harmless: Not blocked by review. Depends on #12 still
declares #12.
Edges come from three places, all merged at build:
-
Hand-authored into the plan body (the natural response to a fake-edge audit finding) — preserved across an idempotent refresh, exactly like the
branch/gate-modedeclarations. -
Issue bodies: a
Depends on #NNline in a scoped issue’s own body seeds the edge automatically. -
Refs to items outside the plan are dropped — deps are plan-internal (out-of-plan ordering is the gating-todo domain) — but never silently (#2297):
buildprints one line per dropped ref, naming both ends and why —[dep-dropped] #2240: depends on #2234 — not in this plan, not recorded[dep-dropped] #8: depends on #8 — self-reference, not recordedand
--jsoncarries the same list asdroppedDeps([{ issue, dependsOn, reason }], empty when every declared edge landed); the summary line counts them (N depends_on ref(s) dropped) beside the recordeddepends_on edge(s). A dropped ref is therefore always distinguishable from one that was never declared: seat the target in the plan (or grow anissues:plan with--plan) if the edge matters.
Consumption. remaining classifies an item with unmet prerequisites as
waiting (its unmet siblings named in by) and excludes it from the runnable
set; a prerequisite counts as complete when its checkbox is ticked or its
issue is closed. The moment the last prerequisite lands, the dependent is
runnable — no re-build needed. Items with no unmet prerequisites are runnable
immediately, so warp-drive --flightplan consumes in dependency order and
parallel consumers (bob fork fork --from-flightplan <N>, see
cdfork.md) run the whole independent wave at once, up to
_workflow.fork_max_parallel live windows. When a forked item lands, bob fork watch re-reads the plan and starts each item it was blocking, so a fan-out
runs to the end of the plan without being re-run by hand (#3550).
Validation. Parsing rejects broken declarations loudly: a self-reference, a
ref to a non-sibling item, or a cycle (reported with the path spelled out,
e.g. depends_on cycle: #401 → #403 → #401) fails build, remaining, and
status with exit 1 — a DAG plan is never silently consumed in the wrong order.
Composition. A waiting item that is serial-only (or under
gate-mode barrier) is a barrier —
everything after it is withheld. An item both todo-gated and dep-waiting
reports gated (the human todo is the actionable fact). A plan with no
declarations behaves byte-for-byte as before: same body, same order, empty
waiting bucket.
status reports the DAG’s critical path (waves a parallel consumer needs
vs pending items a serial run needs) — with the plan’s per-item check-off
timestamps, that is the evidence a graphed run finished in ~critical-path time.
Running a plan
Section titled “Running a plan”warp-drive --flightplan <N> # from a terminal/warp-drive --flightplan <N> # from a Claude Code session--flightplan is the narrowest, most explicit scope — it wins over --issue
and --area. The run:
- Reads the plan’s remaining runnable items in order —
flightplan.js remaining <N> --json(already-checked or already-closed items are skipped; todo rows, reqs gated by a pending todo, and reqswaitingon unmetdepends_onprerequisites are excluded — see Gating human-action todos and DAG ordering). - Works the first remaining item; never widens beyond the plan.
- On each requirement’s completion, ticks its checkbox and appends a progress note
(
flightplan.js check <N> <issue> --note …). - Ends when the plan is exhausted — it never falls back to the approved queue.
A plan that is waiting on a predecessor does
not start. init prints {"status":"waiting","plan":N,"note":"Flight plan #N: waiting on #2092: 38/57 done — …"}, exits 0 and writes no state. This is a
clean end, not a failure. Re-kick once the predecessor finishes.
This is a strict extension of the existing --issue N,N,N fixed-ordered-list scope
(#992): same never-widen guarantee, sourced from the plan issue instead of the flag.
Fake-edge audit (#1612)
Section titled “Fake-edge audit (#1612)”A plan written top-to-bottom accumulates fake edges — sequential ordering between items that carry no data between them, so the run waits in sum-of-items time where critical-path time was available. The report-only detector surfaces that slack:
node ~/.claude/scripts/warp-drive/fake-edge-audit.js audit # every open flight-plannode ~/.claude/scripts/warp-drive/fake-edge-audit.js plan <N> # one plannode ~/.claude/scripts/warp-drive/fake-edge-audit.js --file <path> # a multi-step command/skill markdownConsecutive unchecked items with no data dependency — no declared
Blocked by #NN / depends_on edge, no reference to the earlier item, no
shared file/artifact token, and neither serial-only — are grouped into
mutually independent, disjoint parallelizable runs, each safe to fan out
as-is (e.g. via bob fork or a Workflow graph — see the
graph-orchestration
skill’s fake-edge test, which this detector mechanizes for plans). Exit codes
follow the detector convention: 0 clean, 3 candidates found (signal, not
failure), 2 error. It never rewrites a plan — acting on a flag is a
human/groom decision. Declared depends_on edges (#1613) are always respected,
never flagged.
How it composes
Section titled “How it composes”| With | Behaviour |
|---|---|
/groom |
Groom settles order/labels; /flightplan records that order as the plan. Groom first, then plan. Groom is also the plan’s reconciler of last resort: its Step 5f sweeps open plans via audit/reconcile --plan — mechanical convergence auto-applied, stale refreshes propose-only, in-flight plans skipped (#1668). |
--area <slug> |
/flightplan area:<slug> scopes the plan to that workstream’s approved req+bug queue (same membership as warp-drive --area). |
--issue N,N |
/flightplan N,N pins an explicit ordered list; warp-drive --flightplan then consumes it with check-off (where bare --issue has no persisted record or progress notes). |
| (farm) | flightplan.js build --from farm / bob plan build --from farm scopes the plan to what the unattended night shift may take (#2052) — the same membership discover-queue.sh --farm yields. No slash-command spelling: a farm plan is built by the tick, not by a human at a prompt. |
approved |
The gate applies to every scope, explicit lists included (#1794). Un-approved issues are dropped with a named reason; --allow-unapproved opts in and reports what it seated. |
From a shell: bob plan (#2050)
Section titled “From a shell: bob plan (#2050)”The plan lifecycle is a bob noun, so a timer (bob tick, #2054) or a human at a
terminal can plan without a Claude Code session. scripts/bob/plan.sh is a thin
router over the engine above — it maps flags and execs flightplan.js; it holds
no plan logic of its own, so the #1666 engine stays the single source of truth.
bob plan build # the approved queue → flightplan.js build --no-interviewbob plan build --from-issues 261,262 --json # → build --issues 261,262 --no-interview --jsonbob plan build --area warp-drive --branch stream/warp-drivebob plan build --from-issues 2225,2226 --name "Session kits" # → … --name "Session kits" (#2309)bob plan build --from-issues 2233,2236 --after 2092 # → … --after 2092 (#2298)bob plan remaining <N> [--json] [--all] # → flightplan.js remaining <N> … (verbatim)bob plan status <N> [--json] # → flightplan.js status <N> … (verbatim)bob plan items <N> [--json] # → flightplan.js items <N> … (verbatim)bob plan build flag |
Engine argv | Notes |
|---|---|---|
--from approved (default) |
(no scope flag) | the approved queue — the engine’s project scope |
--from farm |
--from farm |
the unattended scope (#2052): the approved queue narrowed by the eligibility rule in scripts/warp-drive/eligibility.js — farm label or _workflow.farm_eligible: true; never a cap, serial-only, blocked, needs-clarification, vision/CLAUDE.md narrative, or an unverified bug. Every exclusion is printed with its reason ([dropped] #N … — no farm label); the engine refuses --allow-unapproved for it. The rule itself is in farm-tick.md |
--from-issues N,N |
--issues N,N |
an explicit issue list |
--area <slug> |
--area <slug> |
one workstream |
--branch <name> |
--branch <name> |
declares the integration branch (#1181) |
--name <name> |
--name <name> |
names the plan — title Flight plan: <name> plus a flightplan:name marker; omitted, a new plan takes the derived name (its dominant area:* labels) and a refresh keeps its recorded one; on an existing plan it renames (#2309) |
--after <N> |
--after <N> |
the plan waits on flight plan #N (#2298); repeated values are joined N,N |
--no-after |
--no-after |
clears the declared predecessor(s) |
--allow-unapproved |
--allow-unapproved |
off by default: the engine never seats un-approved work and prints each [dropped] issue (stderr; dropped in --json) |
--json |
--json |
the engine’s document — plan, scope, items, dropped, unapproved, … |
| (always) | --no-interview |
a shell run has nobody to interview: open questions are carried into the plan body instead of blocking, so the engine’s exit 3 never happens here |
Exactly one scope source (--from approved / --from farm / --from-issues / --area); two is a
usage error (exit 2) before the engine runs. Without --json, build prints the
engine’s own line — Created flight-plan #N … / Refreshed flight-plan #N … — so the
plan number is on stdout either way. Exit codes are the engine’s own on exec; 2
from the wrapper itself means a usage error (unknown verb or flag, two scope sources,
a bad --from). tests/test-bob-plan.sh asserts the exact flightplan.js argv for
each verb against a stub engine.
Engine reference
Section titled “Engine reference”| Command | Purpose |
|---|---|
flightplan.js build [--area <slug>] [--issues N,N] [--from approved|farm] [--plan <N>] [--name <name>] [--branch <name> | --no-branch] [--after <N> | --no-after] [--no-interview] [--allow-unapproved] [--json] |
Derive once + write/refresh the plan issue (idempotent per scope; exit 3 while open questions remain, unless --no-interview); --plan <N> with --issues grows or re-scopes THAT open issues: plan in place — scope marker rewritten, ticks and declarations carried, rescopedFrom reported, refused for a closed or non-issues plan or a list another open plan claims (#2283); --name titles the plan Flight plan: <name> and records it in a flightplan:name marker — without it a new plan takes the derived name (its dominant area:* labels, else N issues / area:<slug> / whole project), a refresh keeps the recorded name and never rewrites a hand-edited title, and --name on an existing plan renames it (#2309) — flightplan.js name [scope flags] [--json] prints the name a build would write, read-only, as the /flightplan interview’s proposal; --from farm is the unattended scope — token farm, one plan per project, seated by eligibility.js with every exclusion reported and --allow-unapproved refused (#2052); --branch declares the integration branch (#1181), a refresh preserves it, --no-branch clears it; --after <N> declares a predecessor plan, preserved on refresh and cleared by --no-after, refused with exit 2 when it is not a flight plan, is the plan itself or closes a cycle (#2298); a refresh also carries each row’s tick across, matched by issue number, and reports the count as carriedTicks (#2283); depends_on refs are read with the shared grammar (and, &, commas, whitespace — #2297) and every out-of-plan or self ref that cannot be recorded is printed as a [dep-dropped] line and carried as droppedDeps in --json; --allow-unapproved seats issues lacking approved and reports each one (#1794) |
flightplan.js scan [--area <slug>] [--issues N,N] [--json] |
Detect open questions in the scoped issues (pre-flight interview input, #1087) |
flightplan.js resolve <issue> <idx> --answer "…" | --defer [--note "…"] |
Write a dated resolution/deferral marker back to the target issue body (idempotent) |
flightplan.js items <N> [--json] |
Print the plan’s ordered items (todo rows marked) |
flightplan.js remaining <N> [--json] [--all] |
Runnable items in run order — todo rows and gated/moot/withheld/waiting reqs excluded; a held serial-only item is a barrier that withholds everything after it (#1415), and the barrier holds at the gate’s position even if the item it names is closed (#1422); released todos reconcile-ticked; broken depends_on declarations rejected with exit 1 (#1613). --all widens --json to { gateMode, runnable, gated, withheld, moot, waiting } |
flightplan.js check <N> <issue> [--note "…"] [--boundary] |
Tick an item (req or todo) + append a progress note (idempotent — no double-post); --boundary, what the item-boundary tick passes, holds an open item labelled blocked or gated by an open todo (#3694) |
flightplan.js status <N> [--json] |
Progress incl. runnable vs gated vs withheld vs moot vs waiting, per-todo gate state, the declared gate mode, the declared integration branch, and the DAG criticalPath (#1613). Read-only, and resolves gates from the same state remaining does — the operator’s view is what the runner will consume, never a second opinion. Folds in the cheap lifecycle classification (#1666, no drift check) |
flightplan.js lifecycle <N> [--json] |
Full lifecycle classification (#1666) — see below |
flightplan.js audit [--json] |
Report-only lifecycle sweep of every open plan; exit 3 when any is non-active |
flightplan.js reconcile [--apply] [--json] |
Converge open plans toward their derived state — dry-run by default |
Plan lifecycle (#1666)
Section titled “Plan lifecycle (#1666)”A plan’s overall state is derived, never hand-maintained (mirroring cap-status, #989). Every open plan classifies as exactly one of:
| State | Meaning |
|---|---|
active |
At least one runnable item, per the same remaining semantics the runner consumes. |
exhausted |
Every item checked or closed — the plan is closeable. |
waiting |
A predecessor plan (--after, #2298) is open and not exhausted. Nothing runs. The reason names it and its progress: waiting on #2092: 38/57 done. |
drained |
Unchecked remainder exists but none of it is runnable — all gated / waiting / withheld / moot. The reason names each hold (the gating todo, the unmet dependency, the barrier). |
stale |
Scope drift: the recorded scope re-derives differently (items added, dropped, or reordered), or off-plan closures have left unticked-but-closed items. |
Precedence is exhausted > waiting > stale > active > drained. A waiting plan
is not re-derived for drift, because drift cannot change its state. Raw fingerprint
inequality is deliberately not the stale test — a mid-run plan
legitimately re-derives smaller as its items close; only a meaningful delta
over still-open items counts as drift.
reconcile [--apply] converges each open plan: it ticks released todos and
closed-off-plan items (with a note comment), closes an exhausted plan with a
dated final summary (item count + run span), stamps a drained plan with a
dated gate-naming comment plus the machine-readable
<!-- flightplan-state: drained --> marker (the plan stays open; the marker
clears if the plan comes back active), and reports a stale plan with the
exact build refresh command. A waiting plan is stamped
<!-- flightplan-state: waiting --> with a comment naming the predecessor. When
a predecessor closes (finished or abandoned) or has every item done, reconcile
comments the release once and records it as
<!-- flightplan:released #N -->. Destructive actions are propose-only (AC-04):
it never closes a plan with unchecked open items and never auto-rebuilds a
stale plan — those are surfaced for a human or groom’s propose-then-apply
flow. Idempotent: a second run against a converged repo makes zero mutations.
Consumers: warp-drive run boundaries and the /groom sweep call audit /
reconcile rather than reimplementing lifecycle logic (#1667, #1668).
See also
Section titled “See also”- warp-drive.md — the loop that consumes the plan
- kickoff-flags.md —
--flightplanalongside the other scope flags