Coordinate implementation of a normalized ticket set — builds the dependency DAG, groups same-file tickets, dispatches each group to a sub-agent with a fresh context, runs a judge LLM against acceptance criteria, and commits one commit per ticket. Use when a batch of tickets should be implemented in parallel/sequential order with per-ticket commits and an end-of-run report. Do not use for a single ticket (implement it directly), tickets not yet generated (use spec-to-tickets first), or non-ticket work.
Installation
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Coordinate implementation of a normalized ticket set — builds the dependency DAG, groups same-file tickets, dispatches each group to a sub-agent with a fresh context, runs a judge LLM against acceptance criteria, and commits one commit per ticket. Use when a batch of tickets should be implemented in parallel/sequential order with per-ticket commits and an end-of-run report. Do not use for a single ticket (implement it directly), tickets not yet generated (use spec-to-tickets first), or non-ticket work.
license
MIT
Implement Tickets
Coordinates the implementation of a group of tickets produced by spec-to-tickets
(or any source that has been converted to the normalized ticket format).
The skill reads the ticket set, builds the dependency DAG, batches parallelizable
work, dispatches each batch to fresh-context sub-agents, runs a judge review
against each ticket's acceptance criteria, commits one commit per ticket, and
writes an end-of-run report. Live updates use a fixed category prefix
vocabulary so progress, errors, deviations, and escalations are scannable in
real time.
When to Use
The user has a set of tickets in the normalized format and wants them implemented end-to-end
The user asks to "run the ticket implementer", "implement the tickets in tickets/", or "execute the spec-to-tickets output"
The user wants parallel work across independent tickets, with a per-ticket commit history on one branch
The user explicitly opts into the local implement-tickets skill over any globally-installed version
When user input would clarify the request (workspace choice, circuit-breaker threshold override, partial-ticket set selection, attribution policy), invoke ask-questions
When Not to Use
Tickets have not been generated yet — use spec-to-tickets first to produce the normalized ticket set
The work is a single ticket — implement it directly with a single agent session; the coordination overhead is not justified
The user wants to implement one specific ticket by id — dispatch it directly, do not run the full skill
The source is an issue tracker that has not been converted to the normalized format — run the source adapter first, then invoke this skill
The work is non-ticket work (e.g., ad-hoc refactor, exploration, single-PR change)
Workflow
The skill runs in six steps. Each step has explicit completion criteria;
the next step does not start until the current one reports a stable state.
Step 1 — Mode and workspace resolution
Parse the user's natural language input for mode signals:
Phrases like "self-contained", "just do it", "no pausing", or "don't ask me" indicate Self-Contained mode.
Absence of any signal, or phrases like "check with me", "ask first", "let me confirm", indicates Collaborative mode (default).
Record the mode. All subsequent steps branch on this value only at user-facing decision points (Step 3 escalations, Step 6 report acknowledgement).
Resolve the workspace:
Default: create a new branch named tickets/impl-<run-id> from the current HEAD. Record the branch name; all work stays in this branch unless the user specifies otherwise.
Override: if the user specifies a worktree path, branch name, or "use a worktree", initialize a worktree at the resolved path. Record the worktree path.
Capture the circuit-breaker threshold override (default = 3 strikes); if the user does not specify, the default applies.
Capture the attribution policy ∈ {human-only, human+ai-coauthor, ai-only}.
Parse the user's input for an explicit policy signal. Accepted phrasings: "as me", "as me with the bot as co-author", "as the bot with me as co-author", "as the bot", or a literal policy name.
If the policy is not stated, invoke ask-questions with one question and three options: ai-only, human-only, human+ai-coauthor. The recommended default is human-only (lowest surprise, matches the default shell git config in most setups). Ask even in Self-Contained mode — identity is an irreversible, audit-visible decision.
Record the chosen policy before printing the mode banner. The skill does not advance to Step 2 without a recorded policy.
Before asking the user for the AI identity in sub-step 6, load references/commit-author-policy.md to surface canonical bot identities per host platform.
If the recorded policy is human+ai-coauthor or ai-only:
Ask the user for the AI name and email. The user may copy from references/commit-author-policy.md or supply their own. The skill does not propose a default value, does not invent a handle, and does not read the host's CLI version or env vars to auto-fill.
Record the AI name and email as captured strings, verbatim from the user's reply. Trimming whitespace only.
If the user has not provided a name and email, refuse to proceed: emit [DEVIATION] step=1.sub-step-6 reason=ai-identity-required policy=<recorded> and abort the run. The skill does not advance to Step 2.
For human-only, skip this sub-step. The shell's git config user.name / user.email is the implicit human identity; if the shell has none, the eventual git commit fails with git's own error and the failure routes through Step 4.
If the policy is human+ai-coauthor or ai-only, append a single space and ai=<name> <email> to the banner.
Completion criterion: mode recorded, workspace initialized and clean, breaker threshold recorded, attribution policy recorded, AI identity recorded (if policy involves AI), mode banner printed.
Step 2 — Ticket loading and dependency graph
Resolve the ticket source:
Default: load markdown files from tickets/ matching the normalized ticket format (frontmatter with id, title, status, Depends on:, Acceptance criteria).
If the user provides a path or glob, load from that location.
If the user names an external source (e.g., "GitHub Issues in repo X"), abort with a clear message: "External sources must be converted to the normalized format by a source adapter before this skill runs. Run the adapter for first, then re-invoke."
For each ticket file, parse the frontmatter and body. Validate that the ticket has all four required normalized fields: id, title, status, Depends on:. Reject tickets missing any field with a per-file error.
Filter the ticket set to those with status ∈ {ready, in-progress} and whose dependencies all resolve to other tickets in the set. Tickets with status: blocked or with unresolved dependencies are listed in the run summary as Skipped with a reason.
Build the dependency DAG from Depends on: edges. Detect cycles; if any cycle exists, abort with the cycle printed and a recommended resolution (re-derive the dependency graph via spec-to-tickets).
Compute topological batches: a batch is a maximal set of tickets with no inter-batch dependencies (all tickets in batch N depend only on tickets in batches < N).
For each batch, group tickets that share at least one file path (in their recommended workflow's Where: lines or in their Files context pointer). Each group becomes a single dispatch unit. Tickets with no file overlap remain individual dispatch units.
Print the batching result: [OK] loaded=<N> ready=<N> skipped=<N> batches=<N> dispatch-units=<N>. List the dispatch units with their constituent ticket ids and shared files.
Completion criterion: ticket set loaded, DAG acyclic, batches and dispatch units computed, batch summary printed.
Load references/sub-agent-dispatch.md before Step 3 for the full dispatch template variables and prompt shape; load references/judge-prompt.md before the judge-loop sub-step in Step 3.
Sub-Agent Contract (MANDATORY)
These rules are non-negotiable. They apply to every sub-agent dispatch regardless of
ticket content, batch, or mode. The coordinator enforces them; the sub-agent is bound
by them. Violations are contract breaches, not suggestions.
REQUIRED: All work happens inside <STAGING_PATH>. No file outside the staging area may be written. VIOLATION: return status: error with notes: wrote-outside-staging.
REQUIRED: No commits. The sub-agent must not run git add, git commit, git push, git checkout to a different branch, or any commit-related or branch-switching command. The coordinator owns all commits. VIOLATION: return status: error with notes: attempted-commit.
REQUIRED: The sub-agent must return the structured response format exactly as specified (status / files_changed / criteria_check / notes). VIOLATION: treat as status: error with notes: malformed-response.
REQUIRED: The sub-agent must not ask the coordinator questions mid-dispatch. One prompt in, one structured response out. VIOLATION: treat as status: error with notes: attempted-interaction.
REQUIRED: The sub-agent must not see other in-flight staging areas. Each dispatch gets a clean, pinned checkout.
REQUIRED: Changes must be scoped to the ticket's acceptance criteria. No refactoring unrelated code; no reformatting untouched lines.
Step 3 — Per-dispatch-unit execution
For each dispatch unit (in batch order; units within a batch may run in parallel
via multiple sub-agents), the following sub-steps apply. The per-ticket loop
inside the dispatch unit runs in ticket order within the group.
Extract completion criteria from the ticket body:
Read the Acceptance criteria section.
If absent, read the Definition of Done section.
If neither is present, treat the ticket's Goal + What to build sections as the criteria and surface a [DEVIATION] ticket=<id> no explicit criteria — using Goal+What to build line.
Create a per-sub-agent staging area:
Default: a directory at .implement-runs/<run-id>/staging/<ticket-id>/ reachable from the shared branch.
The sub-agent's working copy for this ticket is a clean checkout pinned to the commit at which the dispatch began, plus all already-applied staging areas from earlier (DAG-respecting) tickets in the same run.
The sub-agent does not see any other in-flight staging areas.
Dispatch a sub-agent with a fresh context:
Pre-dispatch checklist — before sending the dispatch prompt, verify every item:
Prompt includes the no-commit rule (verbatim from the Sub-Agent Contract above).
Prompt includes the staging-area-only rule (verbatim from the Sub-Agent Contract above).
Prompt includes the structured response format (status / files_changed / criteria_check / notes).
Prompt includes the no-interaction rule (one prompt in, one response out).
Prompt uses imperative language (REQUIRED / VIOLATION) for all constraints — not advisory language ("should", "please", "try to").
If any item is missing, abort the dispatch and fix the prompt before sending. Do not dispatch with a partial contract.
Load references/sub-agent-dispatch.md and follow the dispatch template.
The sub-agent is told: ticket id, normalized ticket body, the resolved completion criteria, the list of files it owns, and the staging-area path.
The sub-agent accumulates all changes silently. No WIP commits appear in the shared branch's history. Sub-agents are forbidden from committing during dispatch; the coordinator owns commits.
Capture the sub-agent's diff and validate:
Capture the staging area's git status + git diff once the sub-agent reports done.
Post-dispatch validation — verify every item before proceeding to the judge loop:
The sub-agent did not run any commit-related command (check the staging area's git log; only <STARTING_COMMIT> should be reachable before the coordinator commits).
All paths in files_changed are within <OWNED_FILES>. If not, treat as status: error with the offending path in notes and increment the strike counter.
The sub-agent returned the structured response format (status / files_changed / criteria_check / notes). If unstructured or missing required fields, treat as status: error with notes: malformed-response and increment the strike counter.
The sub-agent did not write outside <STAGING_PATH>. If it did, treat as status: error with notes: wrote-outside-staging and increment the strike counter.
If any check fails, do not proceed to the judge loop. Record the violation, increment the strike counter, and route through failure handling (Step 4).
Run the judge loop:
Load references/judge-prompt.md and follow the judge template.
The judge receives: the ticket's completion criteria, the sub-agent's diff, and the staging-area state.
The judge returns one of three verdicts: approve, reject-with-feedback, reject-with-ambiguity.
On approve: proceed to commit (step 6).
On reject-with-feedback: re-dispatch the sub-agent with the judge's feedback as additional context. Increment the strike counter for this ticket.
On reject-with-ambiguity: surface the ambiguity to the user immediately with [ESCALATION] ticket=<id> ambiguity=<one-line-summary> (Collaborative mode) or auto-skip with a recorded reason (Self-Contained mode).
Commit on the shared branch:
Subject line: starts with the ticket-id bracket prefix, followed by a plain-language description.
Single-ticket dispatch: prefix is [<ticket-id>]. Example: [TK003] add subject-quality gate to commit step.
Same-file-group dispatch (multiple tickets in one commit unit): prefix is [<ticket-id-1>,<ticket-id-2>,...] (comma-separated, no spaces). Example: [TK003,TK007] add subject-quality gate and post-commit identity check.
The bracket prefix stays in the subject. It is what makes commits greppable against the ticket set. It is not moved to the body footer.
Treat 72 characters as a soft cap, not a hard limit. A 90-character self-explanatory subject is preferable to a truncated cryptic one.
The rest of the subject (after the bracket prefix) reads as a public changelog entry. The reader has not read the ticket, the decision ledger, or any prior work.
The negative rules (no ledger IDs, no other-ticket IDs, no host-tool acronyms, plain-language verb/noun) are enforced by the subject-quality gate below; the format description does not restate them.
Body: bullet list of what was done (one bullet per discrete change).
Footer: references to related tickets and decision-ledger records (Refs: ticket-<id>, DECISIONS-<name>.md#Dxxx), and any BREAKING CHANGE: line if applicable.
Commit invocation by policy:
human-only: git commit -m '<subject>' -m '<body>' -m '<footer>'. The shell's git config user.name / user.email is the human identity.
human+ai-coauthor: the coordinator reads git config user.name and git config user.email immediately before the commit to obtain the shell human identity, then runs git -c user.name='<shell-human-name>' -c user.email='<shell-human-email>' commit -m '<subject>' -m '<body>' -m '<footer>' -m 'Co-authored-by: <ai-name> <<ai-email>>'.
In all three cases, the identity is supplied at commit time via the -c flag. The skill does not call git config user.name / git config user.email to mutate the shell's persistent git config.
Subject-quality gate (run before the commit invocation): the coordinator inspects the subject against the following rules; any failure rejects the subject. These are the canonical home for the negative rules — the subject-format description above does not restate them.
No ledger IDs in the subject: subject does not match the regex \bD\d{3}\b or \bT\d{3}\b outside the ticket-id bracket.
No other-ticket IDs in the subject: subject does not match TK\d{3} for any ticket id other than the one in the bracket prefix.
No host-tool acronyms in the subject: the subject must not contain host-tool acronyms that the repo glossary in GLOSSARY.md does not recognise as universal (e.g. MTP, VSTest, CPM, TFM, AOT).
Plain-language: subject contains at least one verb or noun phrase that names the user-visible effect of the change.
On rejection: the coordinator increments the strike counter for this ticket and re-dispatches the sub-agent with the gate's feedback (mirroring the reject-with-feedback re-dispatch path). A second rejection on the same ticket escalates to the user via the circuit breaker.
One commit per ticket. No WIP commits, no merge commits inside a ticket's staging area, no squash — the per-ticket commit is the source of truth.
Post-commit identity check (run immediately after the commit, before
reconciling the staging area):
Skip rule: if the attribution policy is human-only, skip this
sub-step entirely. The shell's git config user.{name,email} is the
contract.
Capture the expected author string:
ai-only → expected = <ai-name> <<ai-email>> (the strings
recorded at Step 1.6, byte-identical to the user-supplied values).
human+ai-coauthor → expected = <shell-human-name> <<shell-human-email>>,
where shell-human-name and shell-human-email are captured by
running git config user.name and git config user.email
immediately before the commit invocation in sub-step 6, and
stored in local variables for use here. If either git config
lookup returns an empty string, abort with
[DEVIATION] ticket=<id> reason=human-identity-missing and route
through references/failure-categorization.md as persistent.
Capture the recorded author: run git log -1 --format='%an <%ae>'
in the shared branch's working tree and trim trailing whitespace.
Compare the recorded author to the expected author (string
equality, case-sensitive on name, case-insensitive on email). On
match, continue. On mismatch, go to the "Mismatch" branch below.
Co-author trailer check (only if the policy is human+ai-coauthor):
run git log -1 --format='%B' and grep for the regex
^Co-authored-by: <ai-name> <<ai-email>>\s*$ (case-insensitive on
the name and email). On match, the post-commit check passes. On
miss, go to the "Mismatch" branch below.
Mismatch branch: emit exactly one line in this shape —
[DEVIATION] ticket=<id> author=<actual> expected=<expected>
— then call the failure handler with category=persistent,
task=commit-identity-verify, and the deviation line as the
signal. The strike counter for this ticket increments. The run
summary records the failure under Failures. The per-ticket loop
does not advance to the next ticket until the failure is routed
per references/failure-categorization.md.
Completion signal: either a recorded match on both checks and
the per-ticket loop advancing, or a recorded [DEVIATION] line and
a routed failure under Failures in the run summary.
Reconcile the staging area into the shared branch: the commit is the reconciliation artefact. Staging-area files are now on the shared branch in DAG order. Staging directories are retained until end of run, then removed.
Live update after each ticket:
[OK] ticket=<id> commit=<sha> batch=<n> on success.
[ERROR] ticket=<id> reason=<one-line> on judge rejection that hits the circuit breaker.
[DEVIATION] ticket=<id> <detail> on criteria-source fallbacks, conflict-resolution events, or other plan deviations.
[ESCALATION] ticket=<id> <detail> on user-facing interrupts.
Completion criterion (per ticket): either a commit on the shared branch, a recorded skip with reason, or a recorded escalation to the user. Strike count for the ticket is reset to 0 on commit.
Common Sub-Agent Violations
These are contract violations, not warnings. Each one increments the strike counter
and routes through failure handling (Step 4). The coordinator must detect and record
every instance.
Sub-agent committed: The sub-agent ran git add, git commit, or any commit-related command. Contract violation — the coordinator owns commits. Treat as status: error with notes: attempted-commit.
Sub-agent wrote outside staging: The sub-agent modified a file outside <STAGING_PATH> or <OWNED_FILES>. Contract violation — the staging area is the only writable surface. Treat as status: error with notes: wrote-outside-staging.
Sub-agent returned unstructured response: The sub-agent's response does not match the required format (status / files_changed / criteria_check / notes). Contract violation — the judge cannot evaluate an unstructured response. Treat as status: error with notes: malformed-response.
Sub-agent attempted interaction: The sub-agent asked the coordinator a question or waited for input mid-dispatch. Contract violation — one prompt in, one response out. Treat as status: error with notes: attempted-interaction.
Sub-agent refactored out of scope: The sub-agent changed code outside the ticket's acceptance criteria (unrelated refactors, reformatting of untouched lines). Not a hard error, but the judge should flag it; if the judge rejects, route through the normal re-dispatch path.
Step 4 — Failure handling and circuit breaker
Every failure is categorized and routed. The categorization rules live in
references/failure-categorization.md (load on first failure, not before).
Categorize each failure as one of: transient, persistent, dependency, ambiguous. Rules in the reference file.
Route by category:
transient → auto-retry, with exponential backoff capped at 3 retries before falling through to the circuit breaker.
persistent → auto-skip with a recorded reason; do not retry.
dependency → auto-skip and mark downstream tickets as blocked in the run summary.
ambiguous → escalate to the user (Collaborative) or auto-skip with a recorded reason (Self-Contained).
Circuit breaker: any task (commit, file edit, sub-agent dispatch, judge call) that fails more than N times for the same ticket — regardless of category — escalates to the user with [ESCALATION] ticket=<id> task=<name> strikes=<n> (Collaborative) or aborts the run (Self-Contained). The threshold N is the Step 1 value (default 3).
Visibility: every auto-handled failure and every circuit-breaker escalation appears in the run summary (Step 6) with its category, ticket id, and one-line reason.
Completion criterion: every failure has a recorded category, a recorded route, and either an action (retry / skip) taken or a recorded escalation. The strike counter for each ticket is consistent with the route taken.
Step 5 — Cross-dispatch conflict resolution
When two dispatch units within the same batch touch the same file, the rule
below applies. Conflict-resolution rules live in
references/conflict-resolution.md; load on first conflict.
Detection: at the end of each batch, the coordinator diffs the per-ticket commits in topological order and flags any overlapping file regions.
Default resolution: last-writer-wins (LWW) by commit order on the shared branch. The overlapping region in the earlier commit is preserved only if the later commit's diff did not touch that region.
Escalation: if the LWW rule would produce a result that contradicts a ticket's stated acceptance criteria, escalate to the user with [ESCALATION] ticket=<id> conflict=<file> reason=<one-line> and pause that batch.
Recording: every conflict, the rule applied, and the resulting resolution is recorded in the run summary under Conflicts.
Completion criterion: every detected conflict has a recorded resolution, and either a follow-up commit (if LWW is re-applied) or a recorded escalation. The shared branch's history contains no unresolved overlap.
Step 6 — End-of-run report
Compose a markdown report at tickets/.runs/<run-id>.md with these sections:
Run header: run id, mode, workspace, breaker threshold, start/end timestamps, total wall-clock.
Deviations: any plan deviations surfaced during the run (e.g., criteria-source fallbacks, skipped tickets, escalation events).
Next steps: tickets remaining in the ticket set, blocked tickets, and recommended follow-up actions for the user.
Commit the report to the shared branch with subject [run/<run-id>] implement-tickets end-of-run report.
In Collaborative mode: print the report to the conversation and ask the user to acknowledge before finalizing. In Self-Contained mode: print a one-line summary to the conversation and commit.
Clean up staging directories under .implement-runs/<run-id>/staging/ after the report is committed.
Completion criterion: report file exists at tickets/.runs/<run-id>.md, report is committed on the shared branch, staging directories are removed.
Output Mode
This skill writes a structured end-of-run markdown report to tickets/.runs/<run-id>.md and commits per-ticket changes plus the report to the shared branch. It does not draft in conversation by default; the conversation receives live updates and a final one-line summary. The user may request an additional tickets/.runs/<run-id>-draft.md in conversation before the report is committed by pausing after Step 6.3's report composition.
Transitions
write-changelog — may run after this skill to summarize the per-ticket commits as user-facing release notes. The per-ticket commit subjects ([<ticket-id(s)>] <plain-language description>) are designed to be changelog-friendly.
Validation
Step 1 resolved mode, workspace, breaker threshold, and attribution policy before any ticket work began.
Mode banner was printed at Step 1 completion in the form [OK] mode=... workspace=... breaker=... attribution=<policy>[ ai=<name> <email>].
Step 2 rejected tickets with missing normalized frontmatter fields with a per-file error.
Step 2 detected cycles in the dependency DAG and aborted with the cycle printed, or confirmed acyclic.
Same-file tickets were grouped into a single dispatch unit.
Each sub-agent received a fresh context (no shared scratchpad, no carry-over context from prior tickets).
Pre-dispatch checklist was completed before each dispatch (all five items verified; no dispatch sent with a partial contract).
Post-dispatch validation was completed after each sub-agent response (all four items verified; no judge loop started with a failed validation).
Common sub-agent violations (commit attempts, writes outside staging, unstructured responses, interaction attempts) were detected, recorded, and routed through failure handling.
No WIP commits appear on the shared branch between per-ticket commits (the per-ticket commit is the only commit authored for the ticket).
Each per-ticket commit subject matches the form [<ticket-id(s)>] <plain-language description>. The 72-character limit is a soft cap.
Each per-ticket commit's cross-references appear in the body footer in the Refs: ticket-<id>, DECISIONS-<name>.md#Dxxx format, never in the subject.
Step 1 captured the attribution policy before any ticket work began. The mode banner shows attribution=<policy> (and ai=<name> <email> when policy involves AI).
For human+ai-coauthor and ai-only policies, the user supplied the AI name and email; the skill did not auto-fill, did not invent a handle, and did not mutate the shell git config.
For human+ai-coauthor policy, the per-ticket commit carries a Co-authored-by: <ai-name> <<ai-email>> trailer; for ai-only policy, the commit's author is the AI identity.
references/commit-author-policy.md was loaded before sub-step 6 of Step 1 when the policy involved AI.
The post-commit identity check ran for every per-ticket commit where the policy involved AI; the recorded author matched the expected; for human+ai-coauthor the Co-authored-by: trailer matched the AI identity. The check did not run for human-only commits.
An identity mismatch produced exactly one [DEVIATION] ticket=<id> author=<actual> expected=<expected> line and was routed through references/failure-categorization.md as persistent; the strike counter for the ticket incremented; the run summary recorded the failure under Failures. The per-ticket loop did not advance past the failing ticket.
The subject-quality gate ran before every per-ticket commit and rejected any subject that violated the four rules in Step 3.6.
Each per-ticket commit body is a bullet list of changes; each footer has references or is explicitly empty.
Judge loop ran for every ticket; no ticket was committed without a judge approve verdict.
Sub-agents did not see other in-flight staging areas (verified by the staging-area path and the pinned starting commit).
Cross-batch conflicts were detected, resolved per the rule in references/conflict-resolution.md (default LWW until that reference overrides it), and recorded in the report.
Every failure has a category from {transient, persistent, dependency, ambiguous} and a recorded route.
Circuit breaker escalated or aborted after N strikes (default 3) for any task on any ticket, regardless of category.
Every auto-handled failure and circuit-breaker escalation is visible in the run summary.
Live updates used the four-prefix vocabulary ([OK], [ERROR], [DEVIATION], [ESCALATION]) consistently.
End-of-run report exists at tickets/.runs/<run-id>.md and is committed on the shared branch.
Staging directories under .implement-runs/<run-id>/staging/ are removed after the report is committed.
In Collaborative mode, Step 6.3 asked the user to acknowledge before finalizing; in Self-Contained mode, only a one-line summary was printed.
references/sub-agent-dispatch.md, references/judge-prompt.md, references/failure-categorization.md, and references/conflict-resolution.md exist as referenced (or the skill surfaces their absence as [DEVIATION] missing-reference ...).
External ticket sources (e.g., GitHub Issues) were converted to the normalized format by an adapter before the skill ran; the skill did not consume a non-normalized source directly.