|
| 1 | +--- |
| 2 | +name: jira-features |
| 3 | +description: Pulls resolved Jira tickets via Atlassian MCP, groups them into features by Epic, uses LLM classification to determine user-facing vs internal, and writes features-released.csv. Use when the user asks to fetch Jira features, update the feature velocity CSV, track released features, measure feature velocity, or sync Jira data. |
| 4 | +--- |
| 5 | + |
| 6 | +# Jira Feature Velocity Tracker |
| 7 | + |
| 8 | +Fetches Done tickets from Jira boards (one per team), groups them into features by Epic, classifies each as user-facing via LLM reasoning, and writes `features-released.csv`. |
| 9 | + |
| 10 | +## Prerequisites |
| 11 | + |
| 12 | +- Atlassian MCP server connected (`user-mcp-atlassian`) |
| 13 | +- `jira-teams.yaml` in project root (template ships with the repo; board IDs populated via Step 1 below) |
| 14 | + |
| 15 | +## Workflow |
| 16 | + |
| 17 | +Follow **all five steps** in order. Step 1 is one-time setup; Steps 2-5 run each time. |
| 18 | + |
| 19 | +--- |
| 20 | + |
| 21 | +### Step 1 -- Board Discovery (one-time) |
| 22 | + |
| 23 | +Populate `jira-teams.yaml` with real board IDs. |
| 24 | + |
| 25 | +1. Call `jira_search_fields` via MCP with keyword `"story point"` to find the custom field ID for story points. Save it in `jira-teams.yaml` under `settings.story_points_field`. |
| 26 | + |
| 27 | +2. Call `jira_get_agile_boards` via MCP (limit 50, paginate if needed) to list all boards. |
| 28 | + |
| 29 | +3. For each team in `jira-teams.yaml` (Core, FullStack, Integration, CDC, Ninja, Devops), find the board whose name best matches the team name. Present the mapping to the user for confirmation: |
| 30 | + |
| 31 | +``` |
| 32 | +Proposed board mapping: |
| 33 | + Core -> board 42 "Core Team Board" |
| 34 | + FullStack -> board 43 "FullStack Board" |
| 35 | + ... |
| 36 | +``` |
| 37 | + |
| 38 | +4. After confirmation, update `jira-teams.yaml` with the real `board_id` and `board_name` values. |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +### Step 2 -- Fetch Done Tickets |
| 43 | + |
| 44 | +For each team/board in `jira-teams.yaml`: |
| 45 | + |
| 46 | +1. Build the JQL query: |
| 47 | + |
| 48 | +``` |
| 49 | +project = <board_project> AND status in ("Done", "Closed", "Released") AND resolved >= "-{days}d" |
| 50 | +``` |
| 51 | + |
| 52 | +Default `{days}` is from `settings.default_lookback_days` (90). Override with user-supplied value. |
| 53 | + |
| 54 | +2. Call `jira_search` via MCP with: |
| 55 | + - `jql`: the query above |
| 56 | + - `fields`: `summary,description,issuetype,status,resolutiondate,created,parent,labels,components,fixVersions,{story_points_field}` |
| 57 | + - `limit`: 50 |
| 58 | + - `start_at`: 0 (paginate until all results fetched) |
| 59 | + |
| 60 | +3. Collect all tickets across all teams. Tag each ticket with its `team` name from the board mapping. |
| 61 | + |
| 62 | +4. Optionally save raw results to `cache/jira-raw-{date}.json` for debugging. |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +### Step 3 -- Epic Grouping & Dedup |
| 67 | + |
| 68 | +Group the fetched tickets into "features": |
| 69 | + |
| 70 | +1. **Epic-parented tickets**: Group all tickets that share the same `parent` (Epic key) into one feature. |
| 71 | + - `feature_id` = the Epic key (e.g., `PROJ-100`) |
| 72 | + - `feature_name` = the Epic's summary (fetch via `jira_get_issue` if not already available) |
| 73 | + - `jira_keys` = list of all child ticket keys in this group |
| 74 | + - `released_date` = the latest `resolutiondate` among the group |
| 75 | + - `first_created` = the earliest `created` date among the group |
| 76 | + - `story_points` = sum of story points across all tickets in the group |
| 77 | + - `fix_versions` = union of all fix versions |
| 78 | + |
| 79 | +2. **Orphan tickets** (no Epic parent): Each is its own feature. |
| 80 | + - `feature_id` = the ticket key |
| 81 | + - `feature_name` = the ticket summary |
| 82 | + - `jira_keys` = just this one key |
| 83 | + |
| 84 | +3. **Bug/Defect handling**: If `issuetype` is `Bug` or `Defect`, set `category = bug_fix` regardless of Epic grouping. Bugs under an Epic are still grouped with that Epic but the category reflects the bug nature. If an Epic contains ONLY bugs/defects, the whole feature is `category = bug_fix`. Mixed Epics (bugs + stories) keep `category = feature`. |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +### Step 4 -- LLM Classification |
| 89 | + |
| 90 | +For each feature group, classify using your own reasoning (you ARE the LLM): |
| 91 | + |
| 92 | +1. **`is_user_facing`** (true/false): Does this feature directly impact what end users see or experience? Consider: |
| 93 | + - Changes to UI, API responses, user flows, notifications = user-facing |
| 94 | + - Internal tooling, CI/CD, refactors, monitoring, infra = NOT user-facing |
| 95 | + - Bug fixes that affect user experience = user-facing |
| 96 | + - Performance improvements users would notice = user-facing |
| 97 | + |
| 98 | +2. **`category`**: One of: |
| 99 | + - `feature` -- new capability or significant enhancement |
| 100 | + - `bug_fix` -- fixes broken behavior |
| 101 | + - `improvement` -- incremental enhancement to existing capability |
| 102 | + - `tech_debt` -- refactoring, cleanup, dependency updates, infra |
| 103 | + |
| 104 | +3. **`description`**: One-line summary of what was shipped, written for a non-technical audience. |
| 105 | + |
| 106 | +4. **`llm_reasoning`**: One sentence explaining the classification decision (audit trail). |
| 107 | + |
| 108 | +Present the classification results to the user in a summary table before writing. Example: |
| 109 | + |
| 110 | +``` |
| 111 | +| feature_id | feature_name | team | category | user_facing | released | |
| 112 | +|------------|------------------------|-----------|----------|-------------|------------| |
| 113 | +| PROJ-100 | New onboarding flow | FullStack | feature | yes | 2026-03-01 | |
| 114 | +| PROJ-205 | Fix login timeout | Core | bug_fix | yes | 2026-02-28 | |
| 115 | +| PROJ-310 | Upgrade Redis driver | Devops | tech_debt| no | 2026-02-25 | |
| 116 | +``` |
| 117 | + |
| 118 | +Ask user to confirm or adjust any classifications before proceeding. |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +### Step 5 -- Write CSV |
| 123 | + |
| 124 | +1. Build the JSON array of classified features matching the schema expected by `scripts/jira_features_to_csv.py`. |
| 125 | + |
| 126 | +2. Write the JSON to a temp file or pipe to stdin: |
| 127 | + |
| 128 | +```bash |
| 129 | +python scripts/jira_features_to_csv.py --input features.json |
| 130 | +``` |
| 131 | + |
| 132 | +Or for a preview first: |
| 133 | + |
| 134 | +```bash |
| 135 | +python scripts/jira_features_to_csv.py --input features.json --dry-run |
| 136 | +``` |
| 137 | + |
| 138 | +3. Report the result: how many features were written (new vs updated). |
| 139 | + |
| 140 | +--- |
| 141 | + |
| 142 | +## CSV Schema: `features-released.csv` |
| 143 | + |
| 144 | +| Column | Type | Description | |
| 145 | +|--------|------|-------------| |
| 146 | +| `feature_id` | string | Epic key or standalone issue key (primary key) | |
| 147 | +| `feature_name` | string | Epic summary or LLM-generated name | |
| 148 | +| `jira_keys` | string | Pipe-separated constituent ticket keys | |
| 149 | +| `ticket_count` | int | Number of tickets in this feature | |
| 150 | +| `category` | enum | `feature` / `bug_fix` / `improvement` / `tech_debt` | |
| 151 | +| `is_user_facing` | bool | LLM-classified: impacts end users? | |
| 152 | +| `llm_reasoning` | string | Classification rationale | |
| 153 | +| `team` | string | Team name from jira-teams.yaml | |
| 154 | +| `released_date` | date | Latest resolution date in the group | |
| 155 | +| `first_created` | date | Earliest creation date in the group | |
| 156 | +| `lead_time_days` | int | `released_date - first_created` (computed) | |
| 157 | +| `quarter` | string | e.g., `2026-Q1` (computed) | |
| 158 | +| `iso_week` | string | e.g., `2026-W10` (computed) | |
| 159 | +| `story_points` | float | Sum of story points | |
| 160 | +| `description` | string | LLM-generated one-liner | |
| 161 | +| `fix_versions` | string | Jira fix versions (pipe-separated) | |
| 162 | + |
| 163 | +## Velocity Queries This Enables |
| 164 | + |
| 165 | +- **Features shipped per week by team**: group `iso_week` + `team` where `is_user_facing = true` |
| 166 | +- **Throughput by quarter**: count by `quarter` + `team` |
| 167 | +- **Feature vs bug ratio**: `category` breakdown per team |
| 168 | +- **Average lead time**: mean `lead_time_days` per `quarter` + `team` |
| 169 | +- **Story points velocity**: sum `story_points` per `iso_week` + `team` |
| 170 | + |
| 171 | +## Options |
| 172 | + |
| 173 | +| Option | Default | Description | |
| 174 | +|--------|---------|-------------| |
| 175 | +| `--days` | 90 | Lookback window for resolved tickets | |
| 176 | +| `--team` | all | Restrict to a single team | |
| 177 | +| `--dry-run` | false | Preview CSV changes without writing | |
| 178 | + |
| 179 | +## Notes |
| 180 | + |
| 181 | +- The Bots team from `teams.cfg` is excluded (no Jira board for bots). |
| 182 | +- Derived columns (`lead_time_days`, `quarter`, `iso_week`) are computed by `jira_features_to_csv.py`, not by the agent. |
| 183 | +- Re-running is safe: features are upserted by `feature_id`, so re-classification updates existing rows. |
0 commit comments