CityRP Documentation Repository

Skill pipelines

Last updated

The engineering skills in .claude/skills/ are built to chain: each one leaves an artifact the next one starts from. This file names the chains, what each stage hands on, and the labels an issue carries between stages. The skills themselves own the detail of each step; this is the map between them.

Every handoff between stages is a durable artifact (an issue, a checklist, a dependency edge), never the transcript. A later session, or a different person, can pick up any stage cold from the tracker. Where the artifact lives is set by artifacts.md.

Idea to shipped code

/grilling → /to-spec → /to-tickets → /wave

For a change whose destination is clear enough to specify. When the destination itself is unclear, use /wayfinder instead: it maps decisions toward a destination rather than slicing known work.

Stage Starts from Leaves behind Issue state after
/grilling An idea or plan in conversation A shared understanding in the conversation, plus any GLOSSARY.md terms or ADRs /domain-modeling wrote along the way No issue yet
/to-spec The grilled conversation One spec issue type/spec, priority, status/awaiting-review
/to-tickets The spec issue Either the spec converted into a task, or the spec as an epic with a ## Tickets checklist and one ticket issue per slice, wired with native dependencies Spec: type/task or type/epic, status/triaged. Tickets: type/task, priority, effort, status/triaged
/wave The triaged task, epic or initiative One session per child that can move, each running the skill that advances it Each dispatched child: assigned

The first two stages have a one-session form. /grill-to-spec runs the grilling and then writes the spec out of it without stopping in between, which is the route for a subject that has to be interrogated before it can be written up — an accepted but undesigned suggestion, most often, which /triage leaves at status/awaiting-review with a condensed brief, or a type/stub. A stub is an idea filed by hand as a placeholder, often one of several under an initiative. /grill-to-spec writes the spec into a suggestion or a stub itself rather than a new issue, so it becomes the type/spec that stage 3 breaks down; a suggestion keeps the player's text and its Synced from: line below the spec.

/grill-to-tickets runs all three in one session: /grill-to-spec, then /to-tickets on the spec it published, putting the testing seams and the breakdown to the user in one check-in, so the spec moves straight on to type/epic or type/task at status/triaged. A stub or a suggestion carries through both in-place conversions. It is a route a person chooses; /wave dispatches to /grill-to-spec, not to it.

1. Grill

/grilling works the design tree in rounds until nothing is left silently assumed, with /domain-modeling loaded so glossary terms and ADRs are written as they settle. It offers to raise those as a docs PR once grilled; /grill-with-docs raises it without asking, and /grill-me is /grilling under its other name. No issue comes out of it, so the conversation it leaves is the input to the next stage: run /to-spec in the same session, before the context is lost.

2. Spec

/to-spec synthesises the conversation into a spec and publishes it as type/spec at status/awaiting-review. It does not size the work. A spec is input to a breakdown, not something to build directly, and it isn't triaged because it has no acceptance criteria (triage-labels.md). /grill-to-spec ends here too, having done the grilling itself.

A spec nobody has broken down yet is findable with q type/spec.

3. Tickets

/to-tickets <spec> is the one place that sizes. It proposes tracer-bullet slices and quizzes you on them, then does one of two things:

  • One slice: converts the spec in place. It appends acceptance criteria and blockers, re-types it type/task with an effort, and moves it to status/triaged. There is no child issue, and the waves stage is a single /deliver on that issue.
  • Several slices: publishes one type/task ticket per slice in dependency order, each opening Part of #<spec>., wires blockers as native dependencies, then re-types the spec type/epic, appends the ## Tickets checklist, and moves it to status/triaged.

4. Waves

The tickets are a task graph, not a list. A wave is the frontier of that graph: every child that can move right now. /wave <issue>… walks that graph and launches one wave:

  1. Resolve the frontier. frontier.py reads the tree the arguments reach — descending through every ## Tickets checklist, at any depth — and buckets each child on its state, its assignees, its open pull requests and its dependencies (gitea-issues).
  2. Claim the whole wave by assigning each frontier ticket before any session starts, so a parallel run doesn't take the same ones.
  3. Dispatch each one to the skill that advances it, in its own session: /deliver for a triaged ticket, but also /triage for a new report, /grill-to-spec for a suggestion that passed triage or a stub, /to-tickets for a spec or an unbroken-down epic, /research for a question. One run moves each child one rung. /wave --inline claims only the earliest frontier ticket and works it in the same session instead. To land a whole epic as one pull request rather than one per ticket, /deliver-epic <epic> builds every ticket in dependency order on one branch instead, once all of them are triaged.
  4. Land them. As each ticket's work merges, close the ticket; the next run ticks its line in the parent's checklist.
  5. Run it again. Closing tickets unblocks the ones that depended on them, and answering a parked session's questions moves that child on. Repeat until every checklist is ticked, then close the epic yourself — /wave never closes anything.

A wave doesn't have to wait for its slowest ticket: a ticket unblocked by an early merge can start straight away. Batching into waves is the minimum; starting each ticket as soon as it is unblocked is the same graph worked faster.

Tickets in a wave can still touch the same files. Merge conflicts between siblings are expected, and are resolved when the second one lands, not avoided by serialising the wave.

A UI feature on one branch

/build-ui → (/grilling) → /ux-design → /ux-review → spec issue → /to-tickets → sub-PRs → one main PR

For a panel, a flow or the HUD built end to end by the UI agents. It is the chain above with one difference that changes the mechanics: everything lands on one feature branch (dev/<person>-<agent>/<feature>) and reaches master through one main PR. The skill owns the detail; these are the parts that differ from waves, so they are not read off the section above.

Stage Leaves behind
Grill, only when starting from an idea A shared understanding in the conversation, which the spec is written from
Spec The UX spec as the feature branch's first commit, and the main PR open as a draft
Spec review /ux-review verdicts as comments on the main PR, repeated until approved
Publish A pointer spec issue: the document stays the source of truth, so /to-spec's template is not used
Break down The epic and its tickets, exactly as /to-tickets leaves them
Build One sub-PR per ticket, built by /deliver's stages in a worktree of its own, cut from the feature branch and merged back into it
Land The main PR, out of draft, carrying the closers and the ## Changelog section
  • A sub-PR references its ticket and never closes it. The closers (Closes #<n>. for every ticket and the epic) go in the main PR's body, so the tickets close when the feature reaches master and not before.
  • The frontier is read from ticks, not from closed issues. Tickets stay open until the main PR merges, so "every blocker is closed" is never true here. A ticket is ticked in the epic's ## Tickets checklist when its sub-PR merges into the feature branch, and the frontier is the unticked tickets with no open sub-PR whose blockers are all ticked.
  • A sub-PR merges only when its reviews are clean and its CI is green, one at a time, with the next ticket's branch updated onto the feature branch first.
  • public= appears nowhere until the main PR's merge message. Sub-PR merges are merge commits that reach master with the feature. The one exception is a Feedback close message, which goes at the end of the main PR's body when that PR closes a feedback-synced issue; a sub-PR never carries one, because it closes nothing.
  • Resume from the epic number. Every stage's state is on the tracker or in git, so /build-ui <epic> picks a feature up cold.