plugins/dev/skills/ticket-retro/SKILL.md
Cross-ticket retrospective VIEW (CTL-789 Loop C / CTL-814). **ALWAYS use when** a ticket's PR has just merged (the workflow's compound closing step — runs automatically per ticket, CTL-831), or when the user says 'ticket retro', 'run a retro', 'retrospective', 'what did we learn lately', or 'how are the estimates calibrating'. Synthesizes everything the compound loops captured since the last retro — friction logs, learnings, compound-log calibration, catalyst.db / merged-PR actuals — into thoughts/shared/retros/ticket/<date>.md with a persisted watch-items block, and surfaces top patterns in the morning briefing's Plan today.
npx skillsauth add coalesce-labs/catalyst ticket-retroInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Loop C of compound engineering: a human-readable reflection across a SET of tickets. It mostly reads what Loop B (friction logs, learnings) and Loop A (compound-log, estimation corpus) captured, then writes ONE artifact: the retro document.
Runs automatically per ticket (CTL-831): merge-pr step 12b and phase-monitor-merge invoke
this skill right after the compound-log entry lands, so the system learns from every ticket it
ships without being asked. Best-effort in those contexts — a retro failure never blocks a merge
or a phase. Several merges per day are normal: same-day re-runs REGENERATE today's file
cumulatively (the gather floor skips today — see Step 3).
Hard contract — read-only VIEW:
thoughts/shared/retros/ticket/<YYYY-MM-DD>.md.thoughts/shared/CONCEPTS.md, or touch ADRs —
that is ticket-compound's job (per-ticket curator). No Linear writes, no corpus writes._none_ — empty stores are the normal early state, never an error./catalyst-dev:ticket-retro # since-last-retro (default scope)
/catalyst-dev:ticket-retro --since 2026-06-01 # explicit window floor
/catalyst-dev:ticket-retro --tickets CTL-1,CTL-2 # explicit ticket set (all time)
Default scope is since-last-retro, no time box (solo-dev rhythm — design decision, plan
line 114): the window floor is the date of the most recent retro in
thoughts/shared/retros/ticket/; the first retro ever falls back to 14 days.
All reads go through the gather helper — one JSON document, every section degrades to empty:
GATHER="${CLAUDE_PLUGIN_ROOT:-plugins/dev}/scripts/ticket-retro/gather-retro.sh"
RETRO_JSON=$(mktemp)
bash "$GATHER" --thoughts-dir thoughts "$@" > "$RETRO_JSON"
jq '{window, prior_retro: (.prior_retro != null), friction: (.friction|length),
learnings: (.learnings|length), calibration: (.calibration.entries // 0),
merged_prs: (.merged_prs|length), db_stats: (.db_stats|length)}' "$RETRO_JSON"
What it returns (see the script header for the full shape):
| Key | Source | Degrades to |
|---|---|---|
| window | latest retros/YYYY-MM-DD.md / --since / --tickets | 14-day default |
| prior_retro.watch_items | the previous retro's fenced yaml watch-items block | null |
| friction[] | thoughts/shared/friction/*.md (## <phase> · <TICKET> · <ISO-8601> records after the floor) | [] |
| learnings[] | thoughts/shared/learnings/** frontmatter, mtime after the floor | [] |
| calibration | compound-log.sh aggregate (estimate_at_start vs estimate_actual) | {} |
| merged_prs[] | gh pr list --state merged in-window, ticket id from branch/title | [] |
| db_stats[] | ~/catalyst/catalyst.db sessions⋈session_metrics per ticket (SPARSE — see note) | [] |
Actuals note: db_stats covers only orchestrator-run tickets with metrics rows (historically
~13 of 333). merged_prs[].additions/deletions (diff churn) is the universal actuals fallback —
use it for the aggregate stats; treat db cost/hours as a bonus column where present.
merged_prs by ticket; one line each. Failed/abandoned tickets that
show up in friction but not in merged_prs belong here too (often highest-signal).friction[].line entries that describe the same
underlying problem (same component, same failure shape — NOT necessarily same wording). A
pattern needs ≥2 records (across tickets or phases). One-off frictions are listed only if
severe. For each pattern: a name, the supporting records (ticket·phase), and one sentence of
synthesis.prior_retro.watch_items[] pattern, check whether this
window's friction/learnings show it again. Verdict per item: recurred (cite evidence),
quiet (no sighting), or resolved (a learning/ADR/fix landed that addresses it — cite it).calibration: count/exact/mean-signed-delta/median-abs-delta
plus a per-ticket start→actual table. When calibration.entries == 0, render _none_ and note
the sink fills at merge (merge-pr 12b / phase-monitor-merge).first_seen) and add
new patterns from (2) worth tracking. Cap at ~7 — a watch list longer than that is a backlog,
not a watch list.Path: thoughts/shared/retros/ticket/<YYYY-MM-DD>.md (today UTC). If today's file already
exists, OVERWRITE it — the gather floor deliberately skips today's retro (CTL-831), so a
same-day re-run covers the same since-prior-retro window plus whatever just merged; today's file
is always the cumulative day view, never a near-empty increment. Template:
---
date: <YYYY-MM-DD>
type: retro
generated_by: ticket-retro
window_since: <window.since>
window_source: <window.source>
tickets_shipped: <N>
---
# Ticket Retro — <YYYY-MM-DD>
Window: <window.since> → today (<window.source>)
## What we did
- `CTL-x` title — #PR (+adds/−dels)
- … (_none_ when empty)
## Aggregate stats
| Metric | Value |
|---|---|
| Tickets shipped | N |
| Diff churn (LOC) | +A / −D |
| Sessions / cost / hours (catalyst.db, sparse) | N / $C / H |
## Recurring friction patterns
- **<pattern name>** (N records: CTL-a·research, CTL-b·implement) — one-sentence synthesis.
- … (_none_ when empty)
## What we learned
- [component] title — `path` (_none_ when empty)
## Estimation calibration
entries: N · exact: N · mean signed delta: +X.X · median |delta|: X
| Ticket | start | actual | Δ |
|---|---|---|---|
… (_none_ when empty)
## Watch items from last retro
- ✅ resolved / 🔁 recurred / 💤 quiet — <pattern> (evidence)
… (_no prior retro_ on the first run)
## Watch items
```yaml watch-items
- pattern: "<short greppable description>"
component: <orchestrator|phase-agent|broker|monitor|cli|ci|worktree|linear|execution-core|estimation|website|plugins>
first_seen: <YYYY-MM-DD of when it first appeared — preserve across retros>
source: <TICKET the clearest record came from>
**The watch-items block is the only stateful contract.** The next retro and the morning briefing
both machine-parse it: keep the exact fence info string `yaml watch-items`, the exact four keys,
and `pattern` values double-quoted. `component` uses the learnings-store enum
(`ticket-compound/reference.md`).
## Step 4: Sync + report
```bash
humanlayer thoughts sync 2>/dev/null || true
echo "ticket-retro: wrote thoughts/shared/retros/ticket/$(date -u +%Y-%m-%d).md"
Report to the user: the retro path, top 3 recurring patterns, the calibration one-liner, and any
recurred watch-items. The next morning briefing surfaces the watch-items automatically
(Plan today → Retro signals).
| Skill | Owns | Scope |
|---|---|---|
| ticket-compound | learnings / CONCEPTS / ADR proposals (writes) | one ticket |
| compound-estimate | estimation numbers → compound-log (writes) | one PR |
| ticket-retro | the cross-ticket VIEW (reads both, writes only its retro doc) | a window of tickets |
testing
Phase-agent that fixes a failing verify verdict so the pipeline self-heals instead of stalling to needs-human (CTL-653). Reads `${ORCH_DIR}/workers/<ticket>/verify.json`, fixes the `findings[]` (every severity:"high" plus the regression_risk drivers) directly via Edit/Write, commits the remediation, and emits `phase.remediate.complete.<ticket>`. The scheduler's router then re-dispatches `verify` to re-check (the verify⇄remediate cycle, cap 3). Dispatched as a `claude --bg` job by `phase-agent-dispatch`, which invokes it via slash command — hence `user-invocable: true`.
tools
--- name: phase-triage description: Phase agent that triages a Linear ticket — expands acronyms, classifies (feature/bug/docs/refactor/chore), identifies genuine blockers (a semantic second-pass over the backlog — NOT a prose scrape; CTL-838), estimates scope, writes triage.json, and posts a triage analysis comment to Linear. Triage completion is signaled by that comment plus the local triage.json — there is no `triaged` label. Emits phase.triage.complete.<TICKET> on success and phase.triage.fai
tools
Phase agent for the research step of the 9-phase orchestrator pipeline (CTL-450). Wraps /catalyst-dev:research-codebase and produces thoughts/shared/research/<date>-<ticket>.md, then emits phase.research.complete.<ticket>. Reads triage.json from the worker dir as its prior-phase artifact. Spawned via plugins/dev/scripts/phase-agent-dispatch, which invokes it via slash command — hence `user-invocable: true`.
development
Phase-agent wrapper that opens the pull request after implementation completes (CTL-449 Initiative 1 Phase 3). Delegates to `/catalyst-dev:create-pr` (which already auto-runs `describe-pr` and transitions Linear to `inReview`), then writes the PR number + URL into the phase signal file so the downstream `phase-monitor-merge` agent can read it without re-querying GitHub. Dispatched as a `claude --bg` job by `phase-agent-dispatch`, which invokes it via slash command — hence `user-invocable: true`.