Imported from MuneerMohammed-EPAM/Implementor (
.claude/skills/sdlc-ticket-classification/SKILL.md). Install upstream withnpx skills add MuneerMohammed-EPAM/Implementor --skill sdlc-ticket-classification. Copyright stays with the author.
Active Client Tools
- pm-system: jira (token)
- compliance: soc2, pci-dss, gdpr
Phase 0 — Classification
Client Context (read at runtime)
Read the following files at the start of this phase:
| File | How to use |
|---|---|
client-kb.md |
Read fully. Apply domain knowledge, brand model, ticket prefix conventions relevant to this phase. |
client-notes/classification.md |
Read fully if present. Phase-specific — apply everything. |
Do not ask the user to repeat information already present in these files.
Purpose
Determine the ticket type and map it to the correct phase set before any development begins. The pipeline cannot proceed until classification is confirmed by the user.
Prime Directive
Classify first, build right. The wrong phase set wastes days. The right phase set ships confidently.
This skill runs as Phase 0 — before any artifact is created, before any phase starts. The classification drives the entire pipeline: which phases run, which models are used, and in what order work proceeds.
Arguments
| Argument | Required | Description |
|---|---|---|
--ticket=TICKET-ID |
Conditional | Ticket ID in the org's format (see client-kb.md → Workflow → Ticket Prefix). Required unless --local is passed. |
--local |
Conditional | Run without a PM-system ticket. Requires inline feature text or a file path. This skill mints a synthetic LOCAL-#### ID instead of fetching from Jira. Required if --ticket is omitted. |
--project-root=<path> |
No | Target project directory for all docs/artifacts/<ticket>/ paths in this skill. Defaults to the current working directory. |
Input Modes
Mode A — Ticket System API
When the user runs the pipeline with only a ticket ID, fetch the ticket via the ticket system.
Use the Jira REST API v2 with Basic auth (JIRA_EMAIL + JIRA_API_TOKEN env vars) to fetch the ticket by ID.
Mode A — Jira API:
JIRA_BASE_URL=$(python3 -c "import json;print(json.load(open('pipeline-config.json'))['pm_system']['base_url'].rstrip('/'))")
curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
"$JIRA_BASE_URL/rest/api/2/issue/<TICKET-ID>?expand=renderedFields,names,comments,remoteLinks"
Mode B — Inline text: If the user pasted ticket content directly, use it as-is. No API call needed.
After fetching (both modes), extract:
summary— ticket titledescription— full text (ADF format → strip to plain text for Mode A)issuetype.name— Bug / Story / Task / Epic / Spikestatus.name— current workflow statepriority.name— ticket prioritycomponents[].name— affected components (secondary signal foraffected_repos)labels— workflow or team labelscustomfield_[brand_scope]— brand/market scope (field ID frompipeline-config.json)customfield_[platform_scope]— platform scope (field ID frompipeline-config.json)parent.key/parent.fields.summary— parent epic
Error handling:
- API unavailable → switch to Mode B, ask user to paste
- Ticket not found → report, ask user to verify ID
- ADF parse failure → use raw description text as fallback
Mode B — Inline Text
When the user pastes ticket content directly, use it as-is. No API call needed.
If neither API access nor inline text is available, ask the user to paste the ticket content before proceeding.
Mode C — Local (No PM System)
Triggered by --local, or automatically when --ticket is omitted and the user has supplied inline feature text or a file path instead. This is a first-class, fully supported mode — not a degraded fallback for when Jira is unreachable.
No ticket fetch is attempted. There is no summary, issuetype, priority, components, labels, or parent to extract — classify directly from the supplied text/file content as if it were the description.
Mint a synthetic ticket ID:
python3 -c "
import re, pathlib
root = pathlib.Path('docs/artifacts')
existing = [int(m.group(1)) for p in root.glob('LOCAL-*') if (m := re.match(r'LOCAL-(\d+)$', p.name))] if root.exists() else []
next_n = max(existing, default=0) + 1
print(f'LOCAL-{next_n:04d}')
"
This ID matches the standard [A-Z]+-[0-9]+ format used everywhere else in the pipeline, so every downstream path (docs/artifacts/<ticket>/, pipeline_state_<ticket>.json, branch names, PR titles) works unchanged.
affected_repos resolution: skip the components-based primary source (no ticket, so no components) and go straight to the keyword-matching fallback (Step 7) against .codebase-index/index.json domain_concepts and repo names extracted from the feature text.
Process
Follow these steps in exact order.
Step 1 — Load Classification Config
Read config/execution_phases.json to load all phase sets and their descriptions.
Step 2 — Normalise Ticket Data
Strip null fields before analysis. Ticket system API responses include many null/empty fields that add noise without signal. Before extracting classification signals, remove:
- Any field whose value is
null,"",[], or{} - Nested objects where all child fields are null
- Fields irrelevant to classification (e.g.
watches,votes,worklog,attachment,timetracking)
For Mode A: apply normalisation to the raw API response before analysis. For Mode B: skip — user-pasted content is already trimmed. For Mode C: skip — there is no ticket object, only free text.
Step 3 — Analyse Ticket
From the normalised ticket content, extract and evaluate:
| Signal | What to look for |
|---|---|
| Issue type | Ticket system's own type field (Bug, Story, Epic, Task, Spike) — strong signal, not the only one. Absent in Mode C; rely on the other signals below. |
| Summary | Keywords: "fix", "broken", "regression", "crash" → Bug/Hotfix; "investigate", "research", "spike" → Spike; "add", "build", "implement" → Feature/Story |
| Description length | Short + urgent → Hotfix/Bug; long + detailed → Feature |
| Priority | Blocker/Critical + production impact → Hotfix; others → Bug or Feature |
| Acceptance criteria | Present and detailed → Story/Feature; absent → Bug/Hotfix/Spike |
| Components affected | Multiple brands or shared infra → Large Feature; single component → Small Feature/Story |
| Labels | Look for "hotfix", "spike", "tech-debt", "design-required" |
| Linked issues | Many dependencies → Large Feature |
Step 4 — Determine Classification and Confidence
Based on the signals, select the classification type and assign a confidence score (0.0–1.0):
| Type | Key indicators |
|---|---|
| Hotfix | Production broken, blocker/critical priority, needs immediate fix |
| Bug | Defect with known cause, not production-critical, no new functionality |
| Spike | Investigation question, time-box mentioned, no deliverable code expected |
| Story | User-facing behaviour change, clear ACs, no architectural uncertainty |
| Small Feature | New functionality, contained scope, single team, limited cross-service impact |
| Large Feature | Architectural change, cross-team dependencies, extensive ACs, broad scope |
Step 5 — Present Findings to User
Always show the classification findings before proceeding:
Classification: Large Feature (confidence: 0.91)
Reasoning:
- <key signal 1>
- <key signal 2>
- <key signal 3>
Phase set that will execute (N phases):
requirements → architecture → design-review → impl-planning →
implementation → simplify → review → verification → risk → pr
Confirm? (yes / or type a different classification: Hotfix | Bug | Spike | Story | Small Feature | Large Feature)
Step 6 — Handle User Response
- User confirms (yes/y): proceed with detected classification. Set
user_override: null. - User provides a different type: use the user-provided type. Look up its phase set from
execution_phases.json. Setuser_override: "<UserProvidedType>". - User asks why: explain the reasoning in more detail, then re-present the confirmation prompt.
Step 7 — Resolve Affected Repos
Before writing pipeline_state_<ticket>.json, identify which repos are affected.
Primary source — ticket components field (Mode A/B only; skip entirely in Mode C — there is no ticket, go straight to the fallback):
- Extract the
componentsarray from the normalised ticket data. - Each component value is a service/repo name.
- Verify each component exists as a directory under
PROJECT_ROOTusing Glob:<component>/package.json. - Set
confidence: "high"if one or more verified components found.
Fallback — keyword matching:
- If
componentsis empty or no components verified on disk (or Mode C, which always uses this path):- Read
.codebase-index/index.json→ checkdomain_concepts. - Extract service-name keywords from summary/description.
- Match against
domain_conceptskeys and repo names. - Set
confidence: "low".
- Read
- If no match found: set
repos: [],confidence: "unresolved".
Step 8 — Initialise Pipeline State
Mode C only: mint the LOCAL-#### ticket ID now (see Mode C above) before creating any directory — every path from this point on uses that ID.
Run mkdir -p docs/artifacts/<ticket>/.state then write pipeline_state_<ticket>.json.
The phases object must contain only the phases in the confirmed phase set — no others.
Model assignments — read at runtime from config/pipeline-models.json (never hardcode values):
python3 -c "import json; cfg=json.load(open('config/pipeline-models.json')); print(json.dumps(cfg))"
- For each phase in the confirmed phase set: assign
modelfromcfg['phases'][<phase>]['model'](orcfg['default']['model']if not listed). - For
Large Feature: override planning-phase models usingcfg['large_feature_overrides'][<phase>]['model']where present.
{
"ticket": "<TICKET-ID>",
"ticket_source": "jira | local",
"classification": {
"type": "<confirmed type>",
"confidence": 0.0,
"reasoning": "<one sentence summary of key signals>",
"phase_set": ["<phase1>", "<phase2>"],
"classified_at": "<ISO8601>",
"user_confirmed_at": "<ISO8601>",
"user_override": null
},
"affected_repos": {
"source": "ticket_components",
"repos": ["<repo1>", "<repo2>"],
"confidence": "high"
},
"mode": "gates",
"started_at": "<ISO8601>",
"completed_at": null,
"total_duration_ms": null,
"updated_at": "<ISO8601>",
"current_phase": "<first phase in phase_set>",
"pr_url": null,
"pr_urls": null,
"loop_counters": {
"design_review_to_architecture": 0,
"spec_review_to_implementation": 0,
"risk_to_implementation": 0,
"risk_to_architecture": 0
},
"phases": {
"<phase>": {
"status": "pending",
"model": "<model>",
"session_jsonl": null,
"started_at": null,
"completed_at": null,
"duration_ms": null,
"user_wait_ms": 0,
"active_duration_ms": null,
"active_wallclock_time_taken": null,
"iterations": 0,
"gate_history": [],
"halt_reason": null,
"tokens_used": { "cache_creation": null, "cache_read": null, "input": null, "output": null, "total": null, "cost_usd": null }
}
},
"tokens_summary": { "total_cache_creation": 0, "total_cache_read": 0, "total_input": 0, "total_output": 0, "total": 0, "total_cost_usd": 0 },
"active_wallclock_summary": { "total_active_duration_ms": 0, "total_active_wallclock_time_taken": "0m 0s" },
"model_transitions": {}
}
Step 9 — Initialise Artifact Digest
Create docs/artifacts/<ticket>/.state/artifact-digest.md:
# Artifact Digest: <TICKET-ID>
## Classification
- Type: <confirmed type>
- Phase set: <phase1> → <phase2> → ...
- Reasoning: <one sentence summary>
If the file already exists (pipeline resuming), update only the ## Classification section — do not overwrite other sections.
Step 10 — Hand Off to Pipeline
Return control to sdlc-pipeline with:
- Confirmed classification type and phase set
- Confirmation that
pipeline_state_<ticket>.jsonandartifact-digest.mdare initialised
Output Summary (Terminal)
After completing, display:
Phase 0 complete — Classification confirmed
Type: <type>
Confidence: <score>
Phase set: <phase1> → <phase2> → ...
Artifacts initialised:
docs/artifacts/<ticket>/.state/pipeline_state_<ticket>.json ✓
docs/artifacts/<ticket>/.state/artifact-digest.md ✓
Starting Phase 1: <first phase> ...
Output Artifact
pipeline_state_<ticket>.json
See schema in Step 8.
artifact-digest.md
Required section: ## Classification — type, phase set, reasoning.
Exit Criteria
- Ticket type confirmed by user (or user override accepted)
- Phase set derived and stored in
pipeline_state -
pipeline_state_<ticket>.jsonexists with all phases in phase set initialised aspending -
artifact-digest.mdcreated with## Classificationsection - Affected repositories resolved (at least attempted)
Handoff to Next Phase
- Writes
pipeline_state_<ticket>.jsonwith confirmedphase_set,affected_repos - Orchestrator reads
phase_setto determine which phases to execute current_phaseset to first phase inphase_set
Failure Modes
| Failure | Handling |
|---|---|
| Ticket system unavailable | Ask user to paste ticket inline (Mode B) |
| Ticket not found | Report and ask user to verify ID |
| Ambiguous classification | Present top 2 candidates with reasoning, ask user to choose |
execution_phases.json missing |
HALT — config is required, ask user to restore |
| Unknown user response | Re-present confirmation prompt with valid options |
Iron Laws (Tool-Specific)
Jira (Mode A/B)
- Never proceed without a valid ticket ID or
--local. In Mode A/B, ticket ID must match[A-Z]+-[0-9]+. If missing or invalid and--localwas not requested, stop and ask the user before any operation. - Never fabricate ticket content. If the API is unavailable and the user has not pasted content, ask — do not invent or assume field values.
- Always strip null/empty fields from the API response before analysis. Fields with value
null,"",[], or{}add noise without signal and must be removed before extraction. - Always support three input modes: API fetch (Mode A), inline-pasted text (Mode B), and local/no-ticket (Mode C). The skill must handle all three — never assume Jira is the only option.
- Never transition ticket status automatically without explicit instruction from the user or pipeline state machine.
Local (Mode C)
- Never invent a feature description. If
--localis used without inline text or a file path, ask the user to provide one — do not proceed on an empty description. - Never call the Jira API in Mode C. No
JIRA_EMAIL/JIRA_API_TOKEN/base-URL lookup should occur. - Always record
ticket_source: "local"inpipeline_state_<ticket>.jsonso every downstream phase can skip Jira-specific side effects correctly.
Pre-conditions
Jira (Mode A/B)
| Check | Failure handling |
|---|---|
Ticket ID matches [A-Z]+-[0-9]+ |
Stop — ask user to provide a valid ticket ID, or re-run with --local |
JIRA_EMAIL and JIRA_API_TOKEN present and valid (REST call returns 200) |
Fall back to Mode B — ask user to paste ticket content inline |
pipeline_state_<ticket>.json does not already exist |
If it exists: this is a resume — skip initialization, read existing state |
Jira base URL present in intake-sources.md |
Stop — setup is incomplete; ask user to re-run setup |
Local (Mode C)
| Check | Failure handling |
|---|---|
--local passed with inline text or a file path |
If neither is present, ask the user to describe the feature or provide a file |
pipeline_state_<ticket>.json does not already exist for the minted ID |
Should never collide — ID generation always takes max existing LOCAL-#### + 1 |