# 0.5.0 — Tickets are the unit of work

The headline shift in 0.5.0 is architectural: tickets become the
canonical unit of work, and feature lifecycle stops being a parallel
state machine. Post-design feature states (queued / in_progress /
ready_for_review) derive from the ticket rollup; only the pre-design
phase (proposed / in_design / designed) and operator terminals
(verified / rejected) live in the feature lifecycle log.

This is the "Jira-shape" rework we talked through after 0.4.0
landed — features become containers with a rolled-up status,
stories/tickets are where the state machine actually lives. Same
working operator model: queue what you want to ship, mark it done
or reject it. The substrate plumbing underneath now reflects that.

The Caterpillar-reviews-then-Tweedles-fix loop also closes for real
in this release. Previous shape couldn't deliver the revision phase
inside M8 (single-phase budget constraints); now request-changes
verdicts route through the ticket lifecycle and the operator drives
the retry via dashboard re-queue.

## Highlights

- **Feature state derives from tickets** (post-design). Queueing a
  ticket auto-queues its parent feature; all-DONE rolls up to
  ready_for_review. Single source of truth.
- **M8 review verdicts route to tickets.** `accept` marks IN_PROGRESS
  tickets DONE; `request-changes` marks them ABORTED. The feature
  state derives from the rollup; no explicit feature transition
  needed.
- **Per-feature dashboard buttons are bulk ticket operations.**
  Queue / Un-queue / Mark Ready / Re-design all walk the feature's
  tickets through the ticket lifecycle. Verify / Reject stay at the
  feature level (operator terminals override derivation).
- **Request-changes review surfaces on retry.** Tea-party and
  Implementation pull review artifacts into seeds, scoped to the
  parent feature, so Hatter and the Tweedles see what Caterpillar
  flagged when the operator re-queues an aborted ticket.

## What changed

### Feature lifecycle

- New helper `feature_lifecycle._derive_post_ticket_state` computes
  the rollup state from the ticket lifecycle log:
  - all DONE → `ready_for_review`
  - any IN_PROGRESS or ABORTED → `in_progress` (ABORTED rolls up to
    in_progress because the operator owes a retry call; the `⚠`
    ticket badge in the dashboard tree carries the "needs attention"
    detail)
  - any QUEUED, none active → `queued`
  - everything pristine → falls back to log state
- `get_state` now layers: operator terminals (verified / rejected)
  override → ticket-derived rollup → pre-design log entry as
  fallback.
- Legacy `feature-states.jsonl` files keep working — post-design
  entries are simply ignored when tickets exist. No file rewrite
  needed; one-way migration on read.

### Workflow integration

- `_apply_post_meeting_transitions` extended: when a meeting is
  `per_item: ticket` with `transition_iteration_to` naming a
  TicketState, the transition routes through `ticket_lifecycle`
  against the iteration's ticket — not the parent feature. M6
  (tea-party) uses this to flip QUEUED → IN_PROGRESS on iteration
  end.
- Review-verdict routing lifts out of the `transition_iteration_to`
  gate. Fires whenever the meeting is `per_item: feature` and a
  review utterance ships, regardless of whether `transition_iteration_
  to` is declared.
- `_complete_tickets_on_accept_review` mirrors
  `_abort_tickets_on_blocking_review` from the prior release —
  M8 accept marks IN_PROGRESS tickets DONE.
- `tdd-implement.yaml`: M8's `transition_iteration_to: ready_for_
  review` line removed (dead under derivation). M6's
  `transition_iteration_to: in_progress` now targets the ticket
  (semantic change, same line). Tea-party / Implementation seed
  bindings add `review` so prior verdicts surface on retry.

### Ticket lifecycle

- New helper `ticket_lifecycle.chain_transition` walks multi-step
  legal paths so any source state can reach any target state via
  intermediate ones. Hardcoded path table for the small state
  machine; `LEGAL_TRANSITIONS` still gates each individual step.
- Used by the dashboard's bulk operations for Mark Ready
  (`PENDING → QUEUED → IN_PROGRESS → DONE`), Re-design
  (`DONE → QUEUED → PENDING`), and the no-record back-fill case.

### Dashboard

- Per-feature buttons rewritten as bulk ticket ops:
  - **Queue** → bulk-queue all PENDING tickets of the feature.
  - **Un-queue** → bulk-PENDING all QUEUED tickets.
  - **Mark Ready** → bulk-DONE all PENDING/QUEUED/IN_PROGRESS
    tickets via chain transitions. ABORTED tickets are skipped
    with a notify "N aborted ticket(s) skipped — re-queue them
    to retry through review" because force-DONE'ing aborted
    tickets would bypass Caterpillar's gate.
  - **Re-design** → bulk-PENDING every non-PENDING ticket via
    chain paths.
- **Promote to Designed** unchanged — operates on pre-ticket
  feature lifecycle.
- **Verify / Reject** unchanged — write operator-terminal entries
  to the feature log; those override derivation.
- New `_bulk_ticket_op` helper centralises the iteration / filter /
  count logic; notifications report how many tickets moved and how
  many were skipped.
- Implement button label simplified back to `▶ Implement N queued`.
  Under derivation, "queued features" and "features with queued
  tickets" are the same set — no dual count needed.

## Migration

Existing projects with `feature-states.jsonl` entries for the
post-design phase keep working unchanged. The derivation layer
ignores those entries when ticket records exist; legacy entries
linger in the log but don't affect `get_state` output. Operator
terminals (verified / rejected) are honoured as before.

No file rewriting; no opt-in flag; the new behaviour is the
behaviour. If you want to clean up legacy log entries for
aesthetics, you can — but it's not required and we don't ship a
migration tool.

## Test plan

Run through these to validate end-to-end:

1. **Derived feature state**: queue a ticket on a designed
   feature → feature row badge flips to `queued`. Mark Ready
   on a feature → all eligible tickets transition to DONE,
   feature derives to `ready_for_review`. Re-design → tickets
   revert to PENDING, feature derives back to `designed`.

2. **Bulk ops with mixed states**: feature with some DONE
   tickets and some ABORTED. Press Mark Ready → notify reports
   "N done, M aborted skipped". Re-queue an aborted ticket from
   the dashboard, re-run implementation, confirm it iterates
   just that ticket.

3. **M8 verdict routing**: end-to-end implementation run with
   intentional incoherent code. Caterpillar should ship
   request-changes; tickets get marked ABORTED; operator re-queues
   from the dashboard. Run again and verify the review artifact
   shows up in M6/M7 seed context — Hatter should reference
   findings in his test scenarios; Tweedles should address them
   in their implementation.

4. **Operator terminals**: verify a feature with the Verify modal.
   Then queue one of its tickets. Confirm the feature stays
   `verified` (terminal override) even though the derivation
   would otherwise say `queued`.

5. **Cross-run continuity**: launch a design pass that generates
   tickets but don't run implementation. Re-launch the TUI;
   confirm tickets show up with `· pending` badges (no record
   yet, but visible). Queue some, re-launch again, confirm state
   persists.

## Known follow-ups

- **Auto-loop M7+M8 without operator intervention** (the "option 3"
  from the design conversation) — deliberately deferred. Operator
  drives the retry loop today via per-ticket queueing.
- **Two-phase review-then-revise inside M8 itself** — needs a
  conditional-phase primitive in the workflow runner that doesn't
  exist yet. Filed for follow-up.
- **Dashboard rendering polish**: the Features tree still shows
  features as the top level with ticket children underneath. A
  flatter view (or a toggleable group-by-feature flat list) might
  read more like a Jira board for power-user navigation. Out of
  scope for this release.
