tweety53

flow-status

Show every open flow change with its pipeline state, PR, next command, and last update. Read-only. Use for /flow-status.

tweety53 0 Updated 4w ago

Resources

2
GitHub

Install

npx skillscat add tweety53/agents/flow-status

Install via the SkillsCat registry.

SKILL.md

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 in
skills/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=0 and $WARNING empty — the record came from the store.
  • STATUS=0 and $WARNING reads ⚠ flow: store unreachable — read local fallback — the
    record came from the on-disk fallback file. Report this change's row as source: fallback. If
    $RECORD is also empty (no fallback file exists either), treat this exactly like STATUS=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 $RECORD that 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:

  1. Resolve the merge base recorded for that worktree in the record's worktrees map.
    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.
  2. HEAD against that resolved merge base. Equal → the branch has no commits of its own,
    so it is not merged, and no ancestor test is run.
  3. otherwise resolve <base>, in the same worktree, by invoking resolve-base-branch.sh
    exactly as Finish contract (skills/flow-contracts/finish-contract-run1.md) does — never a
    hand-derived name, and never HEAD@{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, since HEAD here 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-status is read-only and never blocks. On exit 0, run
    git 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 before
git 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 — done of total from spectre list --json, which counts the change's
    tasks.md checkboxes and is the same count spectre archive gates on
  • <name>-fix-N sub-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 /flow run 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_PROGRESS renders 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. prUrl is 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 prUrl test that remains is one-way — a null prUrl does 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).
  • FINISHED changes 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>