Documentation menu
how-to read-only

Where the work stands,
read off the repo.

Two questions come up every time you sit down: what is waiting to ship, and what should you pick up next. /standup answers both from the repo itself. It reads git, the release config and the memory files, reports what it finds, and leaves everything as it was.

helper
gather.mjs
workflow phase
none
writes
nothing

Run the recap

Type /standup whenever you want it. It sits outside the eleven phases, so track_guard ignores it and it can never block a commit. Run it mid-workflow if you like.

claude code
> /standup

It runs in two steps. A helper gathers the mechanical facts, then Claude reads them and names the next pickup, with a one-line reason. The helper stops at the facts on purpose. Choosing what to build next is a judgement call, and Article II keeps those in the main session.

You can run the recap on its own from a shell. A single call returns all six sections, including the roadmap epics and their per-task tallies, and prints a readable summary by default, so you should not need to re-read the plan or re-run git log afterwards. Add --json when you want the raw record, --root to point it at a checkout other than the working directory, or --remote to check the release figures against origin before you trust them.

Everything in the release section is read from your local refs. On a checkout that has not fetched recently they are exactly as old as your last fetch, so the default run says so in its output and names git fetch --tags as the fix. --remote answers the question for you. It runs git ls-remote, fetches nothing, and changes no ref.

shell
$ node .claude/skills/standup/cli.mjs recap --json

A shorter version of the same recap greets you at session start, along with a pointer back here. The full read, and the recommendation, wait until you ask.

Read the release section

This is the pile that has built up behind your last tag.

You can tell which of these numbers to trust. The recap marks which figures it checked against origin and which came from a checkout that may not have fetched. Without that mark, a pile of 70 commits reads the same whether it is waiting to ship or shipped two weeks ago.

release
lastVersion      "0.20.0"
lastTag          "v0.20.0"
commitsSinceTag  [ { sha, type, scope, subject, bump } ]
aggregateBump    "none" | "patch" | "minor" | "major"
upstream         { state, ahead, behind }
remote           { probed, stale, remoteTag, remoteHead, headState, reason } | null

Every commit since the tag is sorted by its conventional-commit type, and each one carries the version bump it would cause. aggregateBump is the largest of them, which is the bump the whole pile triggers.

A four-commit pile can be judged on sight. Up to twenty commits, each one prints on its own line as type, bump and subject, and past twenty the list collapses to counts by type. The bound is set there because a seventy-commit dump would recreate the wall of text the recap exists to replace.

Those bump rules come from .releaserc.json, read fresh on every run. Change your release config and the recap follows it the same day.

upstream compares your branch against the copy of origin your last fetch left behind. Origin may have moved since. An ahead count above zero is work that exists on your machine alone. A behind count of zero only means nothing had arrived by the time you last fetched.

That state also prints as a line of its own. Four unpushed commits render as Unpushed: 4 commit(s) not on origin, and a branch with no remote behind it says so in full rather than reporting a match that never ran. On a project that releases on push, the line separates a finished release from a finished branch.

remote is null until you pass --remote. That null is load bearing. It is how you tell a recap that was never checked against origin from one that was checked and found current.

When the check does run, headState reports what happened to your branch head.

headState
diverged          origin carries a commit you do not have
matched           compared against origin, and equal
unreachable       your branch tracks a remote, the check failed
not-comparable    there was no remote branch to compare

The last two look similar and mean opposite things. unreachable says the comparison was possible and did not happen, so the answer is unknown. not-comparable says there was nothing to compare in the first place, which happens on a detached head, on a branch you have never pushed, and on a branch origin does not carry. Neither one is reported as a match.

Read the backlog

Backlog entries arrive in three buckets: open, picked-up and dropped. Epic children sit nested under their parent. A parent with four children reads as one line with four beneath it, so the shape of the work survives the listing.

Open questions come from pending-questions.md, boiled down to an id, a question, and what it blocks. Only entries keyed Q-1, Q-2 and so on are picked up.

If your project keeps a roadmap, the recap reads that too. It looks at project.json → roadmap.path, falling back to docs/roadmap-execution-plan.md. Each epic reports its number, title, tag and status, a count of its tasks by state, and the rows still open beneath it.

A tally reading planned 1 reports that an epic has one task left and stops there. Locating that task meant opening the roadmap file. The rows themselves now print beneath their epic with id and title, and a planned row and a started row will both count as open. Finished rows are dropped in the gatherer, since they make up most of a completed epic and say nothing about what can be picked up next.

Twenty open rows across the whole plan is the limit; past it the roadmap falls back to tallies. The count spans every epic rather than each one alone, because eight epics carrying four open rows fill a screen as fast as one epic carrying thirty-two.

✅ done

Finished and landed.

🟡 in progress

Started, still open.

⬜ planned

Written down, untouched.

Those three emoji are the legend the roadmap file itself uses. The recap counts them; it never edits them. Writing status back to the roadmap is /roadmap-sync's job, at phase 10.6.

Building the plan is a third skill's job again. roadmap-planner derives a roadmap from your vision and spec documents, then diffs that derivation against the plan you have to check the task order. Run it when the recap keeps showing you work in an order that feels wrong.

Choose what to ship next

Whether a pile of commits is ready to cut depends on how your project ships. Declare that in project.json → release and the recommendation follows your rules instead of a generic instinct.

ship continuously

release_trigger: on-push, release_cycle: continuous, or consumers who upgrade often. Push early and often. Sitting on a pile is the risk here, and fixing forward is cheap.

ship on a cut

release_trigger: on-tag or manual, sprint-based cycles, or consumers who upgrade rarely. Each cut is expensive to correct. Gate it on an audit that says the set is whole.

One rule bites in the second case. Set release.completeness_gate.half_wired_blocks_release to true and a half-wired feature holds the cut. A feature is half wired when it shipped dark and its opt-in path is still broken. Consumers who upgrade rarely would sit with that for months.

The rule that decides the cut ends up on screen beside the commits it governs. Either setting prints back under the release block, so the policy and the pile are read in one pass.

Say nothing about your release policy and you get the plain answer: here is the semver bump your commits imply, and the call is yours.

Check a partial repo

The helper runs on a tree that is missing files, and names each file it could not find. Every gap lands in degraded under a stable name.

degraded
no-git                 the tree is not a git repository
no-tags                git repo, no release tagged yet
no-backlog             backlog.md absent
no-pending-questions   pending-questions.md absent
no-roadmap-plan        no roadmap at the configured path
no-release-model       project.json declares no release policy
stale-remote-refs      origin is ahead of your local refs
remote-probe-failed    the check could not run, figures unverified

The last two appear only under --remote, and never together for the same branch. stale-remote-refs is a finding: origin has something you do not. remote-probe-failed is the absence of one, and it leaves your local figures exactly as they were. An offline run says so and leaves the verdict open.

A branch that tracks no remote surfaces as upstream.state. The recap describes that case in full, so it reads as an ordinary result.

A fresh clone on a brand-new project can trip most of these at once and still get a useful answer. The helper reports what it has and names the rest.

The default path is deterministic and offline. Identical repo and memory state give identical helper output, because nothing on that path reads the clock or opens a socket. Two people running the recap an hour apart compare the same numbers. --remote sits outside that guarantee by design, since its answer depends on a remote that can move between two otherwise identical runs. The guarantee got narrower, not weaker. Every run that does not pass the flag keeps the property it always had, including the shorter recap at session start.

last updated 2026-08-14 edit on GitHub →