Show every open flow change with its pipeline state, PR, next command, and last update. Read-only. Use for /flow-status.
Resources
2Install
npx skillscat add tweety53/agents/flow-status Install via the SkillsCat registry.
Report the pipeline state of every open (non-archived) spectre change. Read-only — never commits, never runs git write operations, never advances a state, and never writes state.
Announce at start: "Using flow-status."
Follow both contracts:
- States (
skills/flow-contracts/pipeline.md) - State file (
skills/flow-contracts/state-file.md)
Workflow
1. List open changes
Check guard presence. Per Guard presence check (skills/flow-contracts/pipeline.md),
confirm the one guard this command invokes — resolve-base-branch.sh — is present inskills/flow-status/scripts/. A complete set prints nothing; an absence prints that section's
block once, and the run continues under the guard's own hand-run fallback.
Enumerate the candidate set exactly as Change name resolution
(skills/flow-contracts/pipeline.md) defines it — through flow state resolve [-C dir], never a
hand-written HTTP call:
MAIN_CHECKOUT="$(cd "$(dirname "$(git rev-parse --git-common-dir)")" && pwd -P)"
BOARD="$(flow state resolve -C "$MAIN_CHECKOUT")"$BOARD is one JSON object: "source" ("store" or "fallback"), "complete"
(true only when "source":"store"), "candidates" (each name, state, updatedAt,updatedBy) and "unreadable" (the names of fallback records that could not be read). The table
below is built from candidates; print every unreadable name, by name, above it.
Report which source produced the set, before the table — jq -r .source <<<"$BOARD". When it
reads fallback, say so in one line — ⚠ store unreachable — reporting from local fallback files, which may be stale — so a report built during an outage is never mistaken for one built from the
live store. Say nothing extra when it reads store; that is the normal case.
With a <name> argument, restrict to that change and include the detail view (step 4). With no argument, report every non-archived change.
Zero open changes → say so and suggest /flow's creating run. Stop.
2. Resolve each change's state
For each change, resolve its record through the CLI, per State file
(skills/flow-contracts/state-file.md) — never a direct file read, never jq on a path:
MAIN_CHECKOUT="$(cd "$(dirname "$(git rev-parse --git-common-dir)")" && pwd -P)"
ERR="$(mktemp)"
RECORD="$(flow state get "<name>" -C "$MAIN_CHECKOUT" 2>"$ERR")"
STATUS=$?
WARNING="$(cat "$ERR")"; rm -f "$ERR"STATUS=0and$WARNINGempty — the record came from the store.STATUS=0and$WARNINGreads⚠ flow: store unreachable — read local fallback— the
record came from the on-disk fallback file. Report this change's row as source: fallback. If$RECORDis also empty (no fallback file exists either), treat this exactly likeSTATUS=1
below.STATUS=1— the store was reached and correctly holds no record for this change (flow: no state recorded for <project>/<name>on stderr): report the change by name as no state
recorded and omit it from the table.- Any other
STATUS, or a$RECORDthat is not valid JSON — the record is unreadable: name the
change in this command's own output and skip it. Never rebuild it by inference.
Then read the fields from $RECORD:
printf '%s' "$RECORD" | jq -r '.state, .branch, .prUrl, .artifactUrl, .jiraIssue, .planningEffort, (.models // {} | tojson), .updatedAt, .updatedBy, (.worktrees // {} | keys[])'Load skills/flow-contracts/worktree-resolution.md before resolving the merge-status report
below.
Merge status decides whether the next bare /flow integrates or archives. It is answered
once per worktree in the set resolved per Resolving a change's worktrees
(skills/flow-contracts/worktree-resolution.md) — never a raw read of the record's worktrees map,
which a {} or absent map would make a loop over its keys report on nothing. Per that same
section, a resolved set that comes back empty is never a vacuous pass: say so in this change's
detail view — merge status: unknown, no worktree recorded — rather than silently omitting the
row. Each worktree in the resolved set is answered in three steps, in this order:
- Resolve the merge base recorded for that worktree in the record's
worktreesmap.
No recorded merge base, and equally one that is recorded but does not resolve in that
worktree —git rev-parse --verify --end-of-options "<recorded>^{commit}"fails, because
history was rewritten, the clone is shallow, or the object was pruned — makes the merge status
inconclusive. Do not infer one, and do not run either test below. HEADagainst that resolved merge base. Equal → the branch has no commits of its own,
so it is not merged, and no ancestor test is run.- otherwise resolve
<base>, in the same worktree, by invokingresolve-base-branch.sh
exactly as Finish contract (skills/flow-contracts/finish-contract-run1.md) does — never a
hand-derived name, and neverHEAD@{upstream}, for the reason stated there; running it in this
worktree is also what satisfies its unconditional assertion that the base differs from the
current branch, sinceHEADhere is the change's own branch. A non-zero exit is
inconclusive — the same disposition this step already gives a git failure or an unresolvable
base ref. The report continues, and the detail view carries the resolver's own stderr message
rather than inventing one;/flow-statusis read-only and never blocks. On exit0, rungit merge-base --is-ancestor <branch> origin/<base>; a git failure there is likewise
inconclusive, never "not merged"
Steps 1 and 2 are not optional, and neither may be skipped because the ancestor test looks
decisive. Why an unresolved or unequal-to-HEAD merge base has to be settled beforegit merge-base --is-ancestor runs, and what answering them the other way round reports, is stated
once under The block each state renders (skills/flow-contracts/handoff-blocks.md), beside the
selection table this answer feeds.
A multi-repo change has one merge status per worktree, and the change's is the weakest of them.
The resolved set may carry more than one worktree, so the merge-status check is answered once per
worktree in it and the answers must then be combined. Combine them exactly as
Finish contract (skills/flow-contracts/finish-contract-run1.md) already combines the preflight's
verdicts — run 2 proceeds only when every worktree in the resolved set says so — which here
means: the
change reads merged only when every worktree is proven merged; any worktree proven not
merged makes the change not merged; otherwise, with at least one inconclusive and none proven not
merged, the change is
inconclusive. Never report the first worktree's answer as the change's. When two worktrees
disagree, say so in the detail view and name which is which: that disagreement is precisely what the
next bare /flow will stop on, and the operator should see it here rather than discover it
there.
3. Render the table
Sort by state order per States in skills/flow-contracts/pipeline.md (STARTED,IN_PROGRESS, FINISHED), then by updatedAt descending. Omit FINISHED changes — they are
archived.
## flow status
| Change | Jira | State | PR | Next | Updated |
|--------|------|-------|----|------|---------|
| kan-8-stats-ledger-render | KAN-8 | IN_PROGRESS | #42 | review the diff + run the apps, then `/flow <name>` | 2h ago (/flow) |
| active-workout-session-editing | — | STARTED | — | read the artifact, then `/flow`'s implement phase | 19h ago (/flow) |The absolute worktree path is given in the detail view, taken from the worktrees keys.
The PR column shows the number parsed from the recorded prUrl, or — when it is null. It
never reports whether the pull request is open, merged or closed — that answer needs a network call
this command does not make; the detail view says where to look instead.
The Jira column shows jiraIssue verbatim, or — when the change has no linked issue. This
is a read-only report: never call Jira, never transition an issue, never infer a key from the
change name.
Surface artifactUrl when present — the link to the published proposal artifact.
Surface planningEffort only when the record carries one, verbatim — it is a legacy field no run
writes, per The record in State file (skills/flow-contracts/state-file.md); omit the
line otherwise.
Surface models the same way: the recorded models.default verbatim, or not recorded when it
is null or the key is absent — the shape is canonical in State file
(skills/flow-contracts/state-file.md).
Next-command mapping:
| State | Next |
|---|---|
STARTED |
read the artifact, then /flow <name> (or re-run /flow's creating run to revise) |
IN_PROGRESS, branch not merged |
review the diff + run the apps, then /flow <name> (or re-run /flow's implement phase to fix) |
IN_PROGRESS, branch merged |
/flow <name> — it will archive |
FINISHED |
— |
The IN_PROGRESS row splits on merge status because bare /flow behaves differently either
side of it: it integrates before the merge and archives after. Say which run the operator is
about to get. A branch with no commits of its own is not
merged, and an inconclusive answer takes the not-merged row and says the check could not be
completed.
4. Detail view (only when <name> was given)
Add below the table:
- Linked Jira issue key, or "none linked"
- Task progress —
doneoftotalfromspectre list --json, which counts the change'stasks.mdcheckboxes and is the same countspectre archivegates on <name>-fix-Nsub-changes, if any- PR number and URL when one exists — not whether it is open, merged or closed, which this report
does not track; check the forge for that - Whether the branch has reached the base branch — i.e. which bare
/flowrun comes next. For a
change recording more than one worktree, give the combined answer and the per-worktree ones
whenever they differ, naming each worktree by its absolute path
Then regenerate the change's full handoff block for its current state and print it. Load Handoff
blocks (skills/flow-contracts/handoff-blocks.md) here — it is canonical for the per-state
templates, and this is the only step of any /flow* command that loads it — and render from the
template that matches the current state. Build it from the record and the artifacts as they now
stand; nothing is read back from a stored copy of an earlier run's text, because no command stores
one.
- A value the record does not carry is reported as missing, so a block whose artifact URL
reads missing is distinguishable from one whose URL was never printed. A run-only value is the
exception and is omitted instead:
The block each state renders (skills/flow-contracts/handoff-blocks.md) marks which fields those
are. IN_PROGRESSrenders two different blocks, and the merge status this command already computed
in step 2 chooses between them whenever it is conclusive — a branch proven to have reached the
base branch is integrated, so the branch-waiting-on-a-merge block is the right one for it, and its
heading says the merge has happened.prUrlis consulted only where that probe was inconclusive.
The selection table, and the reason the weaker signal must not run ahead of the stronger one, are
under The block each state renders (skills/flow-contracts/handoff-blocks.md); render from that
table.- Use the answer you already have, and never re-derive it. Step 2 already produced the
merge status — all three of its steps, combined across every worktree — for the next-command
column, and re-deriving it here with a bare ancestor test would reintroduce the
no-commits-of-its-own false positive that check names. - The
prUrltest that remains is one-way — anullprUrldoes not prove run 1 has not
happened — and what that costs, plus why it is accepted rather than replaced, is stated under
The block each state renders (skills/flow-contracts/handoff-blocks.md). FINISHEDchanges have no regenerated block, exactly as they have no row.- The
Running:section is resolved, never copied from a stored run. Follow Resolve the
run instructions (skills/flow/verify-and-handoff.md) — canonical for how those lines are produced —
and apply it here exactly as/flow's implement phase does: resolve from the worktree named in the record
and<project>/.flow/project.md— never the project's declared base — not from any text/flow's implement phase printed earlier. This command prints the resolved commands without re-probing
whether the stack is still running — it states that the stack's liveness is not re-checked, it
does not attempt to confirm it.
With no change name there is no block at all: the no-argument report stays the table above,
unchanged.
Guardrails
- Never create a worktree or branch.
- Never act on a regenerated handoff block — it is output, not a plan. No command it names is
executed, nothing is staged, and no state is written on account of it, however imperative its last
line reads. - No flags. The only argument is the optional change name.
Commands (user-facing)
| Intent | Say |
|---|---|
| Status of all open changes | /flow-status |
| Detail for one change | /flow-status <name> |