Ticket Fetcher
Fetch only — no analysis, requirements, or planning. Output is a self-contained .TICKET.md.
Source & access
Identify the tracker from the input (URL host, or id shape) and fetch through the matching MCP
server — e.g. Azure DevOps MCP for ADO work items, Atlassian MCP for Jira issues, GitHub
MCP / gh for GitHub issues. Use whichever equivalent tools are connected; tool name prefixes
vary by config. Resolve once any handle the MCP needs (e.g. Atlassian cloudId, ADO project) and
reuse it. A bare id that fits several trackers: the one the project's governing docs name, else
the Source host all ticket files in the planning directory share, else the only connected
tracker MCP; still open → ask, naming your pick. If no matching MCP is connected: a public ticket
may be scraped from its URL as a fallback (you may still suggest installing the matching MCP when
it would do the job better); a private one can't — the URL returns a login page, not the ticket —
so stop and tell the user to install and authenticate the matching MCP server, then re-run.
Your task
-
Resolve the input. Accept a full ticket URL or a bare id/key; extract the identifier. If the tracker uses a key prefix (e.g.
XX-1234), keep it; otherwise use the bare number. If the input is unrecognizable, ask. -
Fetch the ticket. Get the work item/issue with its full field set, comments inline, and related links/relations expanded. Capture everything the ticket carries — don't pre-filter to a fixed set of fields. Convert whatever rich-text form you get (HTML, ADF, …) to clean Markdown.
-
Pick a slug. Short kebab-case slug, 3–5 words, from the title/summary — greppable, don't overthink it.
-
Decide the output directory. First resolve the project's planning directory: the one its governing docs name or already in use, default
.agents/plans/. Inside it, first match wins:- Re-fetch — the ticket already has a plan directory or flat
.TICKET.md: write to the next<id>-<slug>-v2,-v3, … name beside it, the file named like its directory (<id>-<slug>-vN/<id>-<slug>-vN.TICKET.md, or flat<id>-<slug>-vN.TICKET.md), and say so in the result — never overwrite; history per re-fetch is kept on purpose. - Existing family — an ancestor (parent/epic, nearest first) has a plan directory
(
<ancestor-id>-*, hyphen included; confirm via its.TICKET.mdthat it's really that ancestor's; latest-vNwhen several), or a group directory's## Ticket setlists this ticket or a sibling: write flat into it (<dir>/<id>-<slug>.TICKET.md) and update the## Ticket setsections per multiple-tickets.md. - Otherwise — a new
<id>-<slug>/subdirectory.
In doubt at any point (unsure a directory is really the family's, …): ask the user, proposing the answer you would pick. Never guess — better one extra question than a potential mistake.
- Re-fetch — the ticket already has a plan directory or flat
-
Write the ticket file at
<output-dir>/<id>-<slug>.TICKET.md,<id>-<slug>-vN.TICKET.mdon a re-fetch. -
Download attachments — if the ticket carries attachments, inline images, external linked assets, or design-tool links, read attachments.md first, then download into the output directory; warn on any that fail.
-
Print the result — project-relative paths and the next-step line (see Next step).
Multiple tickets
The input may list several tickets — before picking slugs and directories, read
multiple-tickets.md: it governs grouping into shared directories, the
## Ticket set section, and per-ticket launch commands.
Ticket file structure
Must stand on its own: a complete, readable version of the ticket with enough metadata that a fresh session can identify the source without re-fetching.
Header
Plain markdown header with source metadata; omit any line whose value is absent. Include whatever the tracker provides. Example:
markdown# <title / summary> > **Source** [<id>](<url>) > **Type** {type} > **Status** {status} > **Assignee** {display name or "—"} > **Reporter / Created by** {display name} > **Labels / Tags** {joined or "—"} > **Fetched** {today YYYY-MM-DD}
Body sections
A grouped ticket's ## Ticket set comes first, directly after the header — see
multiple-tickets.md. Then, in this order, only if present and non-empty:
## Description## Acceptance criteria— if the tracker has a dedicated AC field populated## <Other fields>— one section per other populated field carrying real content (technical notes, custom fields, …), headed by the field's display name. Capture everything the ticket holds.## Comments— oldest first, each### <author> — <date>then the body. Best-effort: if comments aren't returned, note it briefly or omit the section.## Related tickets— see below## Attachments— see attachments.md## Design references— see attachments.md
Preserve heading hierarchy, bullet structure, emphasis, and code blocks; map in-field subheadings to
####. Omit empty sections. Quote user-facing strings, code identifiers, file paths, i18n keys, and
URLs verbatim — never alter or translate them.
Related tickets
From the response's links/relations (and any remote-link tool), for each unique linked ticket (excluding this one) fetch only title + status + type via a cheap call — not the full body. The main goal is that there is a trace of all related tickets, so they can be fetched later if needed. One bullet each, with the relationship if available:
markdown- [<id>](<url>) — (<type>, <status>) <title>
For non-ticket remote links (e.g. a wiki/Confluence page), emit the title and URL as a plain bullet.
If a fetch fails, list the link with (unable to fetch) rather than failing the whole run.
Attachments & design references
The full protocol — numbering, cross-MCP fetching, auth, fallback recipes, integrity checks, and entry templates — lives in attachments.md. Three rules always hold:
- Attachments include inline images in the description/fields, external linked assets, and design-tool links — not just tracker-hosted files.
- Download each straight to disk (e.g.
curl -fSL <url> -o <file>); never route one as inline base64 through the model — output caps truncate it silently. - A file you cannot fetch is still referenced with its source URL and added to the warn list — never block.
Boundaries
- Do not analyze, plan, or explore the codebase for context — this does not exempt files the ticket explicitly links, which you still fetch (see attachments.md).
- Do not modify the ticket in its tracker — fetching is read-only (no comments, transitions, edits, worklogs).
- The only files you create: the
.TICKET.md, its attachments, and any linked docs you fetched; the only edits to existing files are group members'## Ticket setsections.
Next step
State clearly when done, using project-relative paths (never absolute): the output directory,
the ticket file, and any attachments. If any attachment or design preview could not be
downloaded, first emit a clear warning headed by ⚠️ listing each missing file's name and its
source URL, instructing the user to download it and save it under that exact name so the embeds
resolve — or, with /attach-to-ticket available, to paste it (or its saved path) and run that
skill. Without
refine-ticket available the result ends there; with it, hand off the next phase as a single
copy-pasteable launch command in a fenced block — session name and prompt combined, so one paste
starts the session — in the launch syntax of the agent tool in use (vendor-agnostic — claude
below is only the example), naming the session refine-<slug>; several tickets → one block per
ticket, in the suggested execution order, under a label naming it:
claude --name refine-<slug> "/refine-ticket <output-dir>/<id>-<slug>.TICKET.md"
Then offer the alternative: one line offering to clear the current session (vendor-agnostic —
/clear is only the example; use the clear command of the agent tool in use) and run the next
phase here, then the same block(s) with the bare prompt:
/refine-ticket <output-dir>/<id>-<slug>.TICKET.md
On a re-fetch, when the highest earlier version (-v(N-1) … down to the unsuffixed
<id>-<slug>) already has a .REQUIREMENTS.md: say those requirements predate the re-fetch and
put that .REQUIREMENTS.md in both commands in place of the new ticket file (session still
refine-<slug>), noting that refining from scratch means running refine-ticket on the new
.TICKET.md instead.


