ticket-pickup
Skill3 starsChanged 9 days agoMITView on GitHub
Fetch a Jira ticket, enrich it with codebase context, classify its complexity, and route it to the right pipeline depth. Use when the user types /ticket-pickup, starts a prompt with 'Pickup', or asks to 'pick up a ticket', 'grab the next bug', 'work on PROJ-1234', or 'what's my next ticket'. Single-ticket entry point for the agent pipeline.
In this repository36 other files it gives its agents.
- agent-statsSkill
- aristotle-deconstructorSkill
- babysit-prsSkill
- briefingSkill
- code-auditorSkill
- core-editSkill
- create-jira-ticketSkill
- create-tech-specSkill
- cyrus-tdd-engineerSkill
- doctorSkill
- dotfiles-syncSkill
- forgeSkill
- google-docsSkill
- google-driveSkill
- grill-meSkill
- handoffSkill
- lessons-reviewSkill
- mermaid-diagramsSkill
- metrics-emitSkill
- obligationsSkill
- optimus-plannerSkill
- overlay-initSkill
- performance-reviewSkill
- pr-create-from-commitsSkill
- ranger-reviewerSkill
- record-demoSkill
- review-contextSkill
- review-respondSkill
- scout-reviewerSkill
- self-evaluateSkill
- smart-compactSkill
- smart-statuslineSkill
- swarm-retroSkill
- team-leadSkill
- ticket-swarmSkill
- to-prdSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
58 rules. About 0 are common, 32 are distinctive, and 26 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈32
Distinctive · estimate
This author's own choices. Worth a look.
26
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 85
If no ticket can be resolved, ask the user for a ticket key.
- Distinctive: line 102
Use the Atlassian MCP tools.
- Very specific: line 106
Call getJiraIssue with the ticket key.
- Very specific: line 131
If the fetched ticket's labels include exactly one repo:<name> label, the ticket is bound to a specific repository checkout (it is …
- Distinctive: line 156
Autonomous mode: treat as a blocker — stop this ticket and surface the failure.
- Distinctive: line 166
If the resolved path is outside the session's authorized working directories: warn that permission prompts are expected (gated mode) or …
- Very specific: line 170
If the ticket carries multiple repo: labels: warn and ignore them all (ambiguous binding) — continue in the current repo.
- Distinctive: line 180
After fetching the ticket, check if it's a parent ticket (Story or Epic) with children that should be swarmed instead of processed …
- Very specific: line 183
Check the issuetype field from getJiraIssue.
- Distinctive: line 184
If type is Story or Epic:
- Distinctive: line 195
If children exist and at least 2 are in a workable status ("To Do", "Open", "Reopened"):
- Distinctive: line 200
If no children exist (or all children are already Done/Closed):
- Distinctive: line 201
Treat the Story/Epic as a regular ticket (it may be a standalone task mislabeled as a Story)
- Very specific: line 204
If the Story needs sub-tickets created, use /create-jira-ticket which handles story points, Epic assignment, and description templates
- Distinctive: line 229
In gated mode: Wait for user input.
- Very specific: line 235
If the user picks swarm (or autonomous mode auto-selects it):
- Very specific: line 248
Add a comment to the parent Story: 🤖 Starting on this Story's sub-tickets in parallel: {list}.
- Distinctive: line 251
All Jira operations are best-effort.
- Distinctive: line 257
After hierarchy detection (Step 2.5), check whether an open handoff exists for the branch associated with this ticket.
- Distinctive: line 263
If the ticket-pickup branch resolution (Step 5.5) has already determined the branch (matched existing or about-to-create) → use that branch …
- Distinctive: line 265
If no candidate branch resolves → skip Step 2.6 silently.
- Very specific: line 271
Check ~/.claude/tasks/<project>/handoff/<task-key>.md.
- Distinctive: line 275
If present, run a lightweight version of the read flow (same predicate evaluation, same partitioning as the handoff read half) and append …
- Very specific: line 290
If N >= 3 (three or more holding rejections), nudge the complexity classification up one tier (Simple→Medium, Medium→Complex).
- Distinctive: line 305
Component names: Search for classes, modules, or services mentioned in the ticket.
- Very specific: line 307
Related PRs: If the ticket links to merged PRs, fetch the PR diff summaries via gh pr view <number> --json title,body,files.
- Very specific: line 314
When a registry is present, run bash ~/.claude/skills/ticket-pickup/scripts/repo-registry.sh list.
- Distinctive: line 327
If the registry helper fails (exit 2, 3, or 4), degrade to current-repo-only enrichment with a logged note — partial enrichment is better …
- Distinctive: line 355
Follow CLAUDE.md error handling defaults.
- Very specific: line 432
In autonomous mode: If any resolved repo is invalid (helper exit 2) or its path is outside the authorized working directories, treat it as …
- Very specific: line 478
After the gate resolves or the notice prints, emit a multi_repo_split event.
- Distinctive: line 496
Never block the pipeline on metrics.
- Distinctive: line 505
All of these must be true:
- Very specific: line 530
Only when Step 2 found a points field and the ticket has no value in it.
- Very specific: line 587
In gated mode: Wait for user input. Choosing g, s, or e approves the assignee change shown, including taking the ticket from a teammate.
- Very specific: line 596
After the classification gate resolves (user choice or autonomous auto-select), emit a ticket_classified event.
- Distinctive: line 606
If emit fails, log and continue.
- Distinctive: line 612
Before launching the pipeline, ensure the working branch is set up.
- Very specific: line 617
Detect if already on a matching branch: If the current branch name contains the ticket key (e.g., branch PROJ-1234-fix-npe matches ticket …
- Very specific: line 620
Check for a pre-existing branch: Run git branch --list "*{ticket-key}*".
- Distinctive: line 623
Create a new branch: if no matching branch exists:
- Very specific: line 638
Fallback: if newbranch is not available (non-dotfiles environment): detect the default branch manually via git symbolic-ref …
- Very specific: line 685
When the downstream pipeline (Cyrus / Optimus+Cyrus / Aristotle+Optimus+Cyrus) returns, emit a pipeline_complete event before proceeding to …
- Distinctive: line 707
If emit fails, log and continue.
- Very specific: line 721
Add a Jira comment: 🤖 Looked into this one and the answer isn't a code change.
- Distinctive: line 724
Leave the ticket status unchanged (it is already In Progress from Step 7, or from ticket-swarm Step 4.5 in swarm mode).
- Distinctive: line 725
Do not transition to In Review.
- Distinctive: line 730
Do not treat an advisory outcome as a failure, retry it, or escalate it through smart failure analysis.
- Distinctive: line 752
Already that person, or the user asked for no change: do nothing.
- Very specific: line 753
Unassigned: call editJiraIssue with fields={"assignee": {"accountId": "<id>"}}.
- Very specific: line 765
Call getTransitionsForJiraIssue to find the "In Progress" transition ID.
- Very specific: line 766
Call transitionJiraIssue with the transition ID.
- Very specific: line 767
Add a comment: 🤖 Picked this one up.
- Distinctive: line 771
Use the phrase matching the classification from Step 4.
- Distinctive: line 788
Autonomous mode still gates on ambiguous classification: When in doubt, escalate complexity one level.
- Distinctive: line 796
A picked-up ticket with no story points gets an estimate: when its issue type has a points field.
- Distinctive: line 798
Fallback gracefully: If Jira is unavailable, if codebase search finds nothing, if enrichment is thin — still present what you have and let …
- Distinctive: line 808
If you discover something during this task that would improve this skill, propose the change and ask me to confirm before saving it.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Audience for Jira comments
Jira comments posted by this skill are read by everyone on the ticket — PMs, designers, QA engineers, junior engineers, and the engineer who's actually doing the work. Write them in plain language. Avoid agent-internal vocabulary (no "Optimus", "Cyrus", "Ranger", "Scout", "pipeline", "team-lead", "sentinel", "max turns", "cap hit"). Describe what just happened and what comes next in concrete actions, not labels. Keep them short — these are Jira comments, not status pages.
Voice: write the assistant's comments in first person (e.g., "Picked this one up.",
"Had to stop on this one.") — not third person ("Automated assistant has…"). The 🤖
prefix is the machine-marker; the first-person voice keeps the comments readable as a
brief status update rather than a system notification. The 🤖 prefix on every comment is
required by the canonical rule in ~/.claude/AGENTS.md § "Automated Comment Marker — 🤖
prefix" (applies to all agent-posted Jira and GitHub comments); the example strings below
already carry it.
Ticket Pickup
Fetch a single Jira ticket, enrich it with codebase context, classify complexity, and route to the appropriate pipeline depth.
This skill is the entry point for ticket-driven work. It replaces manually copy-pasting ticket context into prompts.
Responsibility boundaries
Ticket Pickup is an orchestrator — it fetches, enriches, classifies, and routes. It never does any of the downstream agents' jobs.
<!-- BEGIN RESPONSIBILITY BOUNDARIES --><!-- EXTRA_ROWS: ticket-pickup -->| Agent | Sole responsibility | NEVER does |
| Ticket Pickup | Fetch ticket, enrich with codebase context, classify complexity, route to pipeline | Write code, produce plans, perform strategic analysis, review PRs |
| Aristotle | Strategic analysis — assumptions, first principles, highest-leverage direction | Name file paths, produce code, plan execution, review PRs |
| Optimus | Execution planning — file paths, step sequencing, risk assessment, architecture | Write code, make file edits, run commands, re-litigate upstream strategic decisions, review PRs |
| Cyrus | TDD implementation — write tests first, then code, hit 80%+ coverage | Redesign architecture, question strategic direction, skip tests, review PRs |
| Ranger | Staff-level PR review — analyze diffs, score issues, report findings | Write code, make edits, implement fixes, plan architecture, perform strategic analysis |
| Scout | PR review — analyze diffs, score issues, report findings | Write code, make edits, implement fixes, plan architecture, perform strategic analysis |
| Auditor | Code complexity analysis, reviewer routing | Write code, review code, post to GitHub, implement fixes |
<!-- END RESPONSIBILITY BOUNDARIES -->Execution modes
Ticket Pickup supports two execution modes, passed by the caller:
gated(default) — present classification to user, wait for approval at every pipeline transition. Used when invoked directly by the user.autonomous— present classification to user for batch approval, then auto-approve all pipeline transitions except PR creation. Used when invoked byticket-swarm.
Step 1: Resolve the ticket
Parse the user's input to determine the ticket:
- Explicit ticket key (e.g.,
PROJ-1234) — use directly. - Branch name — extract ticket key from the current git branch
(pattern:
ABC-1234). Rungit branch --show-currentif needed. - "Pick my next bug" — query Jira for the user's highest-priority
assigned bug:
searchJiraIssuesUsingJql( jql='assignee = currentUser() AND type = Bug AND status in ("To Do", "Open") ORDER BY priority ASC, created ASC', maxResults=1 ) - JQL override — if the user provides a JQL query, execute it and pick the first result.
If no ticket can be resolved, ask the user for a ticket key. If no ticket exists yet, offer to create one via /create-jira-ticket.
Also resolve the assignee target from the user's request. Step 7 applies it.
- Default: the user. Picking up a ticket means taking it.
- A named person ("assign it to Alex", "for @alex"): that person.
- No change ("don't assign it", "leave the assignee"): skip assignment entirely.
A caller-passed assignee (ticket-swarm hands one down) replaces parsing the
request.
Step 2: Fetch ticket context
Use the Atlassian MCP tools. All Jira operations are best-effort — never block the pipeline on a Jira failure.
Primary: Atlassian MCP plugin
- Call
getJiraIssuewith the ticket key. Extract:- Summary, description, acceptance criteria
- Comments (especially reproduction steps, stack traces, linked PRs)
- Status, priority, labels, components
- Current assignee (display name and account ID, or unassigned)
- Linked issues (blockers, duplicates, related)
- Attachments (note URLs for reference)
- Find the story points field. It is a custom field whose ID differs
between Jira sites, so look it up by name: call
getJiraIssueTypeMetaWithFieldsfor the ticket's project and issue type and match the field named "Story Points" or "Story point estimate" (case-insensitive; prefer "Story Points" if both exist). Record its ID and read the ticket's current value for it. No match means the issue type has no points field (sub-tasks often don't); skip story points for this ticket.
For company-specific Jira fallback, consult ## Jira fallback in ~/.claude/overlay-context.md. If that file is absent, proceed with the primary tool only.
<!-- BEGIN OVERLAY-FRAGMENT: ticket-pickup-jira-fallback --><!-- END OVERLAY-FRAGMENT: ticket-pickup-jira-fallback -->Step 2.4: Resolve repo label
If the fetched ticket's labels include exactly one repo:<name> label, the
ticket is bound to a specific repository checkout (it is typically one half
of an earlier repo split, picked up cold in a later session). Resolve the
binding:
bash ~/.claude/skills/ticket-pickup/scripts/repo-registry.sh resolve <name>Handle the result by exit code:
- Exit 0 (absolute path printed): if the path differs from
git rev-parse --show-toplevel, announce the binding — "This ticket is bound to<name>— running in/abs/path" — and use that path as the repo root for all subsequent git operations and for the Step 6 pipeline prompt (includeRepo root: /abs/path). If it matches the current repo, continue silently. - Exit 1 or 3 (name not in registry / no registry): warn that the
repo:<name>label cannot be resolved, naming the missing piece (no registry section vs. no entry for<name>), and continue in the current repo. - Exit 2 (entry invalid at resolution time — path missing, or not a
git repo):
- Gated mode: show the validation message and ask whether to continue in the current repo or stop.
- Autonomous mode: treat as a blocker — stop this ticket and surface the failure.
- Exit 4 (malformed registry entry — reachable even when the entry for
<name>is valid, because any malformed line in the section aborts the parse; the registry needs a human fix):- Gated mode: show the parser's message and ask whether to continue in the current repo or stop.
- Autonomous mode: treat as a blocker — stop this ticket and surface the failure.
If the resolved path is outside the session's authorized working directories: warn that permission prompts are expected (gated mode) or treat it as a blocker (autonomous mode).
If the ticket carries multiple repo: labels: warn and ignore them all
(ambiguous binding) — continue in the current repo.
In swarm mode: Skip this step — ticket-swarm resolves the repo root before dispatching to ticket-pickup.
Step 2.5: Detect ticket hierarchy
After fetching the ticket, check if it's a parent ticket (Story or Epic) with children that should be swarmed instead of processed individually.
- Check the
issuetypefield fromgetJiraIssue. - If type is Story or Epic:
- Fetch sub-tasks via JQL:
searchJiraIssuesUsingJql( jql='parent = {ticket-key}', fields=["summary", "description", "status", "priority", "issuetype", "components", "labels"], maxResults=50 ) - Also check for linked issues with "is parent of" or "has sub-task" link types in the ticket's linked issues.
- Fetch sub-tasks via JQL:
- If children exist and at least 2 are in a workable status ("To Do",
"Open", "Reopened"):
- Skip Steps 3-7 (enrichment + classification for a container ticket is meaningless)
- Present the story decomposition gate (below)
- If no children exist (or all children are already Done/Closed):
- Treat the Story/Epic as a regular ticket (it may be a standalone task mislabeled as a Story)
- Proceed with normal enrichment and classification at Step 3
- If the Story needs sub-tickets created, use
/create-jira-ticketwhich handles story points, Epic assignment, and description templates
Story decomposition gate
Story Detected: PROJ-1000 — Multi-account billing support
Type: Story | Status: To Do | Priority: P2
Sub-tickets: 5 found
# Ticket Type Status Summary
1 PROJ-1001 Sub-task To Do Add account selector to billing page
2 PROJ-1002 Sub-task To Do Update billing API for multi-account
3 PROJ-1003 Sub-task To Do Add billing_account_id to invoices table
4 PROJ-1004 Sub-task To Do Write migration for existing accounts
5 PROJ-1005 Sub-task To Do Update billing tests for multi-account
-> swarm = Swarm all sub-tickets (enrich, classify, parallel pipelines)
-> swarm 1,2 = Swarm specific sub-tickets
-> single N = Pick up a single sub-ticket normally
-> story = Treat the Story itself as a single complex ticket
-> x = CancelIn gated mode: Wait for user input.
In autonomous mode: Auto-select swarm for all workable sub-tickets.
Delegating to ticket-swarm
If the user picks swarm (or autonomous mode auto-selects it):
- Launch the
ticket-swarmskill with:child_tickets: the list of selected sub-ticket keysparent_story_key: the Story/Epic ticket keyexecution_mode: forward from caller (or defaultgated)swarm_mode: forward from caller if presentunbounded: forward from caller if presentassignee: the assignee target from Step 1 (the user, a named person, or no change)
- ticket-swarm skips its JQL harvest (Step 1 Source A) and uses the provided child tickets directly.
- Transition the parent Story to "In Progress" if available.
- Add a comment to the parent Story:
🤖 Starting on this Story's sub-tickets in parallel: {list}. Each one will post its own updates as it goes.
All Jira operations are best-effort.
Step 2.6: Handoff context check
After hierarchy detection (Step 2.5), check whether an open handoff exists for the branch associated with this ticket.
Resolve the candidate branch
Two paths:
- If the ticket-pickup branch resolution (Step 5.5) has already determined the branch (matched existing or about-to-create) → use that branch name.
- Else, derive a candidate branch from the ticket key:
git branch --list "*<ticket-key>*"— use the match if exactly one result. - If no candidate branch resolves → skip Step 2.6 silently.
Check for a handoff artifact
Resolve <project> as basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)". Slug-safe the candidate branch (replace / with -) to form <task-key>.
Check ~/.claude/tasks/<project>/handoff/<task-key>.md. If absent → skip.
Fold into enrichment brief
If present, run a lightweight version of the read flow (same predicate evaluation, same partitioning as the handoff read half) and append to the enrichment bundle:
Open handoff for this branch:
Last intent: <intent>
Holding rejections (gate the pipeline): <N>
[<kind>] <claim>
...
Surfaced rejections (ground shifted, reconsider): <M>
[<kind>] <claim>
...Complexity nudge
If N >= 3 (three or more holding rejections), nudge the complexity classification up one tier (Simple→Medium, Medium→Complex). Add a one-line rationale: "Handoff has 3+ holding rejections — empirically this ticket is harder than its scope suggests."
This enrichment is non-blocking: if the handoff file is malformed or the read fails, log it and continue.
Step 3: Enrich with codebase context
Launch an explore subagent to search the codebase for references from the ticket:
- Stack traces: Extract file paths, class names, line numbers from the ticket description and comments. Search the codebase to confirm they exist and pull surrounding context.
- Error messages: Grep for exact error strings to find the origin.
- Component names: Search for classes, modules, or services mentioned in the ticket.
- Related PRs: If the ticket links to merged PRs, fetch the PR diff
summaries via
gh pr view <number> --json title,body,files.
Cross-repo resolution
For cross-repo resolution, consult ## Repo registry in ~/.claude/overlay-context.md. If that file or section is absent, search only the current repo.
When a registry is present, run
bash ~/.claude/skills/ticket-pickup/scripts/repo-registry.sh list. For
each ticket reference (file path, class/component name, exact error string)
that was not found in the current repo, search each registered checkout
— scope grep/glob by that entry's prefixes when present, whole repo
otherwise — and record hits per repo.
In the brief's Codebase references found: list, qualify each non-current
hit with the repo it resolved in, e.g.
- [repo: other-repo] src/handlers/add_product.py — handler referenced in the stack trace.
Current-repo hits stay unqualified — a single-repo ticket's brief is
byte-identical with or without a registry.
If the registry helper fails (exit 2, 3, or 4), degrade to current-repo-only enrichment with a logged note — partial enrichment is better than no enrichment.
Compile the enrichment into a structured brief:
Ticket: PROJ-1234 — Campaign preview returns 404
Priority: P1 | Status: To Do | Components: campaigns, preview
Description:
[Ticket description, trimmed to essentials]
Reproduction:
[Steps from comments, if available]
Codebase references found:
- app/controllers/CampaignPreviewController.php (line 142 — route handler)
- app/lib/MC/Campaign/Preview.php (line 89 — render method)
- tests_phpunit/MC/Campaign/PreviewTest.php (existing test coverage)
Related:
- PROJ-1200 (duplicate, closed — previous fix reverted)
- PR #4521 (merged — original preview implementation)If gh or enrichment fails: Log the failure, continue with whatever
context was gathered. Partial enrichment is better than no enrichment.
Follow CLAUDE.md error handling defaults.
Step 3.5: Detect multi-repo scope
Decide whether this ticket spans more than one repository and, if so, gate a repo split into per-repo sub-tasks.
Skip conditions (checked first, in order)
- Swarm mode — skip this step entirely; splits are decided before swarm dispatch.
- The ticket already carries a
repo:label — it IS one half of an earlier split; never re-split it. - Step 2.5 fired — story decomposition takes precedence. If the ticket is a Story/Epic with workable children, Step 2.5 already skipped Steps 3-7, so this step is never reached; if both could ever apply, Step 2.5 wins.
Detection rule
Fire the gate only on resolution evidence: Step 3's cross-repo
enrichment recorded at least one reference hit in a registered,
non-current repo. Corroborating signals — ticket Components matching a
registry entry's components=, repo names in the description, linked PRs
in other repos — strengthen the evidence display but NEVER fire the gate
on their own.
Notice path
Corroboration-only signals, detections none of which resolved in a
registered non-current checkout, or signals with no registry take the
notice path instead of the gate. Partial resolution still gates: once the
detection rule fires on at least one resolved hit, remaining unresolved
repos appear on the gate's Unresolved: line (and in the event's
repos_unresolved) rather than forcing the notice path. Print one line,
e.g.:
Note: this ticket also references other-repo — no local checkout registered; handling my-repo only. To enable cross-repo splits, consult `## Repo registry` in `~/.claude/overlay-context.md` and add the missing repo there.Then emit the metrics event with mode: "notice" (see below) and continue
to Step 4 unchanged.
Repo decomposition gate
Multi-repo scope detected: PROJ-1234 — Fix product image sync
Current repo: my-repo (/Users/me/Development/my-repo)
# Repo Path Evidence
1 other-repo /Users/me/Development/other-repo 2 file refs, component match
2 (current) my-repo 3 file refs
Unresolved: legacy-api — no registry entry (notice only)
Proposed order (provider -> consumer): other-repo -> my-repo
! /Users/me/Development/other-repo is outside this session's working
directories — expect permission prompts, or add it before confirming.
-> split = Create one sub-task per repo, blocks-linked, then swarm
-> order 2,1 = Split with a different order
-> only N = Narrow to repo N (pick up normally there)
-> single = Treat as single-repo in the current checkout
-> x = CancelProposed order: registry depends_on hints first (a repo that others
depend on comes earlier), then cross-reference direction (the repo whose
symbols the other references comes first), else current-repo-last. The !
warning line appears only when a resolved path is outside the session's
authorized working directories.
In gated mode: Wait for user input.
In autonomous mode: If any resolved repo is invalid (helper exit 2) or
its path is outside the authorized working directories, treat it as a
blocker — stop this ticket and surface the failure. Otherwise auto-select
split.
On split (or order)
- Build one sub-task spec per repo, in the confirmed order:
- Summary:
<parent summary> (<repo>) - Description: the parent description, plus that repo's tagged references from the Step 3 brief, plus one sentence stating the interface expectation toward its counterpart repo.
- Summary:
- Invoke create-jira-ticket's "Batch mode: repo-split sub-tasks" with the
parent key, the project key, and the ordered
{repo, summary, description}list (provider first — the list order is the final merge order). - If batch mode clean-stops (any creation failed): surface its report exactly — which keys were created, which entries were not. Do NOT delegate to swarm. Do NOT report success.
- On success: delegate exactly like Step 2.5's "Delegating to
ticket-swarm" block —
child_tickets= the created keys,parent_story_key= the parent ticket key,execution_mode,swarm_mode,unboundedandassigneeforwarded from the caller. Transition the parent to "In Progress" and add a plain-language parent comment naming the split and merge order, e.g.:🤖 This ticket spans two repositories, so I split it into one sub-task per repo: PROJ-1235 (other-repo), PROJ-1236 (my-repo). Merge order: other-repo first, then my-repo. Each sub-task will post its own updates.All Jira operations are best-effort.
On only N
Continue this pickup normally, bound to repo N — reuse Step 2.4's repo-root binding mechanics (announce the path, use it as the repo root for all subsequent git operations and the Step 6 pipeline prompt).
On single
Continue unchanged — proceed to Step 4 as a single-repo ticket in the current checkout.
On x
Emit the multi_repo_split event below with gate_choice: "cancel", then
stop.
Metrics emit (both paths)
After the gate resolves or the notice prints, emit a multi_repo_split
event. See the metrics-emit library skill for the canonical event shape
and emit instructions.
Data to capture:
mode: "gate" or "notice"repos_detected,repos_resolved,repos_unresolvedgate_choice: "split" / "reorder" / "only" / "single" / "cancel" — null whenmodeis "notice". Menu mapping: theorder N,Mkeypress emits "reorder";xemits "cancel".subtask_keys: created keys, in final merge order (empty array when no split happened)proposed_order,final_order—final_orderis empty whenmodeis "notice"; on "cancel"/"only"/"single" emitfinal_order=proposed_order(no reorder happened); it differs fromproposed_orderonly on "reorder"
Emit via emit-metric.sh ... || true. If emit fails, log and continue.
Never block the pipeline on metrics.
Step 4: Classify complexity
Evaluate the enriched ticket to determine pipeline depth:
Simple (Cyrus direct)
All of these must be true:
- Single file or 2-3 closely related files affected
- Clear stack trace or error message pointing to the root cause
- Fix pattern is obvious (null check, off-by-one, typo, missing flag check)
- No architectural implications
- Existing test coverage exists to validate against
Medium (Optimus -> Cyrus)
Any of these:
- 2-5 files across multiple directories
- Needs a plan but the strategic direction is obvious
- Requires new test files or significant test additions
- Touches a well-understood pattern (controller + service + test)
Complex (Aristotle -> Optimus -> Cyrus)
Any of these:
- Root cause is unclear or debatable
- Multiple possible fix strategies with different trade-offs
- Architectural implications (changes how systems interact)
- Race conditions, concurrency issues, or timing-dependent bugs
- Cross-service or cross-system boundaries
- Previous fix attempts failed (linked duplicate/reverted tickets)
Story point estimate
Only when Step 2 found a points field and the ticket has no value in it.
Estimate on the same Fibonacci scale /create-jira-ticket uses, 1, 2, 3,
5, 8, and never other values:
- 1 pt: Trivial change — a single config update, copy change, or one-liner fix
- 2 pts: Small, well-defined task — a single file change with minimal risk
- 3 pts: Medium task — touches 2-3 files, straightforward implementation
- 5 pts: Larger task — multiple files, some complexity or unknowns
- 8 pts: Significant effort — cross-cutting changes, new patterns, or substantial unknowns
Base it on the enrichment from Step 3, not on the classification alone. As a sanity check, Simple tickets usually land at 1-2, Medium at 3-5, and Complex at 5-8. Never overwrite an existing value.
Step 5: Present and gate
Show the enriched ticket with classification:
Ticket Pickup: PROJ-1234
Summary: Campaign preview returns 404
Priority: P1
Assignee: Alex Kim -> you (reassigning)
Points: none -> 3 (estimated)
Complexity: Medium
Pipeline: Optimus -> Cyrus (2-3 files, needs a plan)
Codebase context:
- CampaignPreviewController.php:142 — route handler
- MC/Campaign/Preview.php:89 — render method
- PreviewTest.php — existing coverage
Enrichment:
- Previous fix (PR #4521) was reverted in PROJ-1200
- Stack trace points to null $campaign in render()
-> g = Go — launch the pipeline as classified
-> s = Simplify — I think this is simpler, route to Cyrus directly
-> e = Escalate — this is more complex, route through Aristotle
-> k = Keep the current assignee, then go
-> p N = Use N story points instead (1, 2, 3, 5, or 8)
-> c = Add context (provide additional information)
-> x = CancelThe Points line reads none -> N (estimated) when Step 7 will set an
estimate and N (no change) when the ticket already has one. Omit it, and
the p option, when the issue type has no points field.
The Assignee line shows the change Step 7 will make: Unassigned -> you,
Alex Kim -> you (reassigning), or you (no change). With an explicit
no-change request it reads Alex Kim (left as-is), and the k option is
omitted.
In gated mode: Wait for user input. Choosing g, s, or e approves
the assignee change shown, including taking the ticket from a teammate.
In autonomous mode: Log the classification and auto-select g unless
the complexity is ambiguous (e.g., borderline Simple/Medium). If ambiguous,
escalate one level rather than risk under-routing.
Metrics emit (after classification)
After the classification gate resolves (user choice or autonomous auto-select),
emit a ticket_classified event. See the metrics-emit library skill for
format and emit instructions.
Data to capture:
classification: the final classification (Simple/Medium/Complex)pipeline: the pipeline path (cyrus / optimus-cyrus / aristotle-optimus-cyrus)enrichment_sources: which sources produced enrichment (e.g. ["jira", "codebase", "gh"])override: null if no override, "simplified" if user choses, "escalated" if user chosee
If emit fails, log and continue. Never block the pipeline on metrics.
Step 5.5: Ensure branch exists
Before launching the pipeline, ensure the working branch is set up. This step handles branch creation automatically so the user never needs to manually create a feature branch.
- Check current branch:
git branch --show-current - Detect if already on a matching branch: If the current branch name
contains the ticket key (e.g., branch
PROJ-1234-fix-npematches ticketPROJ-1234), use it as-is. Skip branch creation. - Check for a pre-existing branch: Run
git branch --list "*{ticket-key}*". If a matching branch exists but is not currently checked out, check it out and pull latest. - Create a new branch if no matching branch exists:
- Determine the branch name:
{ticket-key}-{slug}(e.g.,PROJ-1234-fix-campaign-preview). Slugify from the ticket summary. - If a repo root was bound earlier (Step 2.4 resolution or a
Step 3.5
only Nchoice, and the path differs from the current working directory): do NOT use thenewbranchalias — it is an optional user-overlay alias (from.aliases.local, not part of core) and is cwd-bound, so it would act on the wrong repo. Use the fallback commands below with every command run viagit -C /abs/path. - Otherwise, use the
newbranchshell alias if available (from.aliases.local):newbranch {ticket-key}-{slug}. This handles default branch detection (queriesorigin/HEAD, supports any branch name), checkout,git pull origin, and new branch creation in one command. - Fallback if
newbranchis not available (non-dotfiles environment): detect the default branch manually viagit symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@'(fall back tomainthenmasterviagit show-ref), then:git checkout {default} && git pull origin {default} && git checkout -b {ticket-key}-{slug}
- Determine the branch name:
In swarm mode: Skip this step — ticket-swarm handles branch creation before dispatching to ticket-pickup.
Step 6: Launch pipeline
Based on classification and user choice:
Simple -> Cyrus direct:
Launch the cyrus-tdd-engineer skill. Pass:
- The enriched ticket brief (from Step 3)
- Jira ticket key for commit messages
- Execution mode (from caller or default
gated) swarm_modeflag if invoked by ticket-swarmunboundedflag (forward from caller if present)
Medium -> Optimus -> Cyrus:
Launch the optimus-planner skill. Pass:
- The enriched ticket brief as the problem statement
- Jira ticket key
- Execution mode
swarm_modeflag if applicableunboundedflag (forward from caller if present)
The Optimus skill handles its own gate and Cyrus handoff.
Complex -> Aristotle -> Optimus -> Cyrus:
Launch the aristotle-deconstructor skill. Pass:
- The enriched ticket brief as the problem to deconstruct
- Jira ticket key
- Execution mode
swarm_modeflag if applicableunboundedflag (forward from caller if present)
The Aristotle skill handles its own gates and downstream handoffs.
CI fix loop: When Jenkins MCP is configured, Cyrus includes an autonomous CI failure diagnosis and fix loop after pushing code. Default behavior: Cyrus reports "Pushed. CI running" and stops; the fix loop triggers on the next interaction that reveals a failure. In swarm mode (swarm_mode: true), Cyrus polls Jenkins via mcp__jenkins-mcp__check_build and enters the fix loop automatically if the build fails (up to 3 iterations). The wait for any one build is capped at about 30 minutes; if CI is still pending then, Cyrus reports "CI still pending" with the build link and stops. See the Cyrus agent definition for details and guardrails.
Metrics emit (after pipeline completes)
When the downstream pipeline (Cyrus / Optimus+Cyrus / Aristotle+Optimus+Cyrus)
returns, emit a pipeline_complete event before proceeding to Step 7.
Data to capture:
classification,pipeline: same as the ticket_classified eventduration_seconds: wall-clock seconds from pipeline launch to returntests_passed: true/false from Cyrus's reportcoverage_percent: coverage number from Cyrus's report (null if not reported)ci_fix_attempts: number of CI fix iterations (0 if none)first_pass: derived astests_passed && ci_fix_attempts == 0, per the metrics-emit skill's canonical rule — additionallyfalsewhen the pipeline needed a retry (ticket-pickup's better-information case, since it sees retries that predate CI and Cyrus's own report may not capture)files_changed: count of files modifiedoutcome:implementednormally.advisorywhen the pipeline ended deliberately with no code change (see below); thentests_passedandfirst_passarenull,ci_fix_attemptsis 0,files_changedis 0, andadvisory_reasoncarries the one-line verdict. The Aristotle skill returns that verdict under the keyverdict; the metrics event names itadvisory_reason. Copy the value across — an event that carriesverdictinstead ofadvisory_reasonfails schema validation.
If emit fails, log and continue.
Advisory outcome (pipeline ended with a correct no-code answer)
The Complex route can end without code and without failure: Aristotle's
Phase 0 judges first principles the wrong tool and answers by analogy, or
the Aristotelian Move states that no code changes are needed. The
aristotle-deconstructor skill reports either back as an advisory
outcome with a one-line verdict and the answer text.
This is the pipeline's third terminal outcome, alongside PR created and blocked. It is a success. Handle it as:
- Emit
pipeline_completewithoutcome: advisoryas described above. - Add a Jira comment:
🤖 Looked into this one and the answer isn't a code change. {verdict}. Full reasoning is in the pipeline output — leaving the ticket as-is for a human to decide what to do with it. - Leave the ticket status unchanged (it is already In Progress from Step 7, or from ticket-swarm Step 4.5 in swarm mode). Do not transition to In Review; there is no PR to review.
- In swarm mode, return
outcome: advisoryand the verdict to the caller instead of a PR URL, so ticket-swarm counts it under Advisory rather than Blocked.
Do not treat an advisory outcome as a failure, retry it, or escalate it through smart failure analysis.
IMPORTANT: Outside swarm mode, immediately after launching the pipeline, proceed to Step 7 (Jira transition). Do not skip it.
Step 7: Jira status transition (MANDATORY)
In swarm mode: Skip this step — ticket-swarm Step 4.5 assigns, sets story points, transitions and comments, so a story-points change made at its triage gate is not overwritten.
You MUST transition the ticket to "In Progress" IMMEDIATELY after launching the pipeline. Do not defer this. Do not skip it.
- Assign the ticket to the assignee target from Step 1:
- Resolve the account ID. For the user, call
atlassianUserInfo. For a named person, calllookupJiraAccountId. If the lookup returns zero or several matches, ask which person in gated mode. In autonomous mode, skip assignment and log why. - Already that person, or the user asked for no change: do nothing.
- Unassigned: call
editJiraIssuewithfields={"assignee": {"accountId": "<id>"}}. - Assigned to someone else: in gated mode, reassign as the Step 5
gate showed unless the user chose
k. In autonomous mode, reassign only when the caller named the assignee. Otherwise leave the teammate in place and log the ticket key and current assignee so the summary can list it. Nobody approved taking that teammate's ticket.
- Resolve the account ID. For the user, call
- Set story points if Step 4 made an estimate: call
editJiraIssuewithfields={"<points-field-id>": <N>}, using the field ID from Step 2 and the value from the gate (the user'sp Nchoice, else the estimate). Autonomous mode sets the estimate without asking. Skip this when the ticket already has points or has no points field. - Call
getTransitionsForJiraIssueto find the "In Progress" transition ID. - Call
transitionJiraIssuewith the transition ID. - Add a comment:
🤖 Picked this one up. {action_phrase}— where{action_phrase}is one of:- For Simple tickets:
Going straight to implementing. - For Medium tickets:
Planning the work first, then implementing. - For Complex tickets:
Doing some strategic analysis first, then planning, then implementing.Use the phrase matching the classification from Step 4. If classification is uncertain, omit the action phrase entirely (the comment still reads sanely as "🤖 Picked this one up.").
- For Simple tickets:
Verify: Confirm the assignment, the story points, and the transition succeeded by checking the responses. If any fails (no permission to assign, points field not on the edit screen, no valid transition, Jira unavailable), log the failure but do not block the pipeline.
This step is not optional. The user expects ticket status to reflect pipeline activity in real time.
Gate rules
- Ticket Pickup never writes code. It fetches, enriches, classifies, and routes. The downstream skill handles everything else.
- Classification is a recommendation. The user can override at the gate.
- Autonomous mode still gates on ambiguous classification. When in doubt, escalate complexity one level.
- Each downstream pipeline runs as a subagent to keep context clean.
- NEVER skip Jira transitions. They happen at pipeline launch (Step 7, or ticket-swarm Step 4.5 in swarm mode), not later. The user expects real-time status updates.
- A picked-up ticket is assigned to the user unless they named someone else or asked for no change. Take a ticket from a teammate only when the user approved it at the gate or named the assignee.
- A picked-up ticket with no story points gets an estimate when its issue type has a points field. Never overwrite existing points.
- Fallback gracefully. If Jira is unavailable, if codebase search finds nothing, if enrichment is thin — still present what you have and let the user decide.
- Three terminal outcomes, not two. PR created, advisory (correct no-code answer), blocked (something went wrong). Never file an advisory outcome as blocked; it pollutes the first-pass metric and tells a human something failed when it did not.
Maintenance
If you discover something during this task that would improve this skill, propose the change and ask me to confirm before saving it.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

