diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f75ddb4..e5b4317 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -78,7 +78,7 @@ { "name": "bitwarden-delivery-tools", "source": "./plugins/bitwarden-delivery-tools", - "version": "2.1.0", + "version": "2.2.0", "description": "Delivery lifecycle skills for Bitwarden initiatives — initiative funnel navigation, work transitions, tech breakdowns and task decomposition, commits, pull requests, preflight checks, and change labeling." }, { diff --git a/.cspell.json b/.cspell.json index 06614ad..4ea5495 100644 --- a/.cspell.json +++ b/.cspell.json @@ -3,6 +3,7 @@ "version": "0.2", "words": [ "accum", + "acli", "adf", "AKIA", "anthropics", @@ -145,6 +146,7 @@ "wordprocessingml", "worktree", "worktrees", + "workitem", "xoxb", "Zeroize", "zeroization", diff --git a/README.md b/README.md index c236043..2039ca2 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A curated collection of plugins for AI-assisted development at Bitwarden. Enable | [bitwarden-shepherd](plugins/bitwarden-shepherd/) | 1.0.0 | Champion of a technical strategy — shepherds a TSI through evaluation into the funnel, then through to adoption | | [bitwarden-atlassian-tools](plugins/bitwarden-atlassian-tools/) | 2.3.0 | Read-only Atlassian access via MCP server with deep Jira issue research skill | | [bitwarden-code-review](plugins/bitwarden-code-review/) | 1.13.1 | Autonomous code review agent following Bitwarden engineering standards with GitHub integration | -| [bitwarden-delivery-tools](plugins/bitwarden-delivery-tools/) | 2.1.0 | Delivery lifecycle skills: initiative funnel navigation, work transitions, tech breakdowns and task decomposition, commits, PRs, preflight, labeling | +| [bitwarden-delivery-tools](plugins/bitwarden-delivery-tools/) | 2.2.0 | Delivery lifecycle skills: initiative funnel navigation, work transitions, tech breakdowns and task decomposition, commits, PRs, preflight, labeling | | [bitwarden-designer](plugins/bitwarden-designer/) | 0.1.0 | Product designer persona: Code of Conduct and 30/60/90 critique, critique facilitation; dispatches into bitwarden-design-tools | | [bitwarden-design-tools](plugins/bitwarden-design-tools/) | 0.1.0 | Design toolkit: content style guide, Figma Dev Mode MCP, Bitwarden brand application, handoff prep, Design System governance, Product and Design Jira | | [bitwarden-devops-engineer](plugins/bitwarden-devops-engineer/) | 0.1.4 | DevOps engineering assistant: workflow compliance linting, action security auditing, and org-wide CI/CD remediation | diff --git a/plugins/bitwarden-delivery-tools/.claude-plugin/plugin.json b/plugins/bitwarden-delivery-tools/.claude-plugin/plugin.json index d0f3f64..f71eb88 100644 --- a/plugins/bitwarden-delivery-tools/.claude-plugin/plugin.json +++ b/plugins/bitwarden-delivery-tools/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "bitwarden-delivery-tools", - "version": "2.1.0", + "version": "2.2.0", "description": "Delivery lifecycle skills for Bitwarden initiatives — initiative funnel navigation, work transitions, tech breakdowns and task decomposition, commits, pull requests, preflight checks, and change labeling.", "author": { "name": "Bitwarden", diff --git a/plugins/bitwarden-delivery-tools/CHANGELOG.md b/plugins/bitwarden-delivery-tools/CHANGELOG.md index 50d1c60..f23b89c 100644 --- a/plugins/bitwarden-delivery-tools/CHANGELOG.md +++ b/plugins/bitwarden-delivery-tools/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to the `bitwarden-delivery-tools` plugin will be documented The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.2.0] - 2026-07-23 + +### Added + +- **`creating-jira-tickets` skill** — turns a breakdown's `tasks.md` into Jira tickets: an epic plus one child story/task per task entry, with real ticket titles, a Gherkin Acceptance criteria ADF section, and wired Blocked-by/Depends-on links. Creates tickets one at a time behind a human-in-the-loop review gate and goes through the `acli` CLI. +- **`creating-jira-tickets` behavior eval set** (`skills/creating-jira-tickets/evals/`) — seven `skill-creator`-schema cases guarding its load-bearing decisions, plus a should-trigger/should-not set. + ## [2.1.0] - 2026-07-01 ### Added diff --git a/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/SKILL.md b/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/SKILL.md new file mode 100644 index 0000000..d818e57 --- /dev/null +++ b/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/SKILL.md @@ -0,0 +1,69 @@ +--- +name: creating-jira-tickets +description: Create Jira tickets from a tech breakdown's tasks.md — an epic plus one child story/task per task entry, with real ticket titles, a Gherkin Acceptance criteria section, and wired Blocked-by/Depends-on links. +when_to_use: Use when the user is ready to file a breakdown's tasks in Jira — phrasings like "create the tickets from tasks.md", "make Jira tickets for these tasks", "file the epic and stories", "turn this breakdown into Jira tickets". Also use when decomposing-into-tasks or developing-breakdown-plan hands off a finished tasks.md. Do not use for reading or researching existing issues (that is researching-jira-issues), or for editing tickets that already exist. +allowed-tools: Read, Write, Glob, AskUserQuestion, Bash(acli jira auth status:*), Bash(acli jira workitem view:*), Bash(acli jira workitem create:*), Bash(acli jira workitem edit:*), Bash(acli jira workitem link:*) +--- + +# Creating Jira Tickets from tasks.md + +Tickets are read outside the breakdown's context, so translate each task entry into a ticket that stands on its own. Create them **one at a time** — a wording or field mistake surfaces on the first ticket, not across the whole set. + +Two facts shape every step: + +- **acli for everything.** Reads and writes go through the `acli` CLI. +- **One at a time.** Live tickets are awkward to unwind. The user reviews the first before the rest. + +## Step 1 — Preflight the write path + +Confirm `acli` can write before drafting anything: + +- Run `acli jira auth status`. +- Missing binary → prompt to install (`brew install atlassian/homebrew-acli/acli`, or the Atlassian CLI docs) and **stop**. +- Installed but not authenticated → prompt `acli jira auth login --web` and **stop**. + +**Completion criterion:** `acli jira auth status` prints `Authenticated` against `bitwarden.atlassian.net`, or you have stopped with an install/auth prompt. + +## Step 2 — Build the ticket tree + +Read `tasks.md` and its sibling `breakdown.md`. Establish the hierarchy before writing any fields: + +- The breakdown's **epic is the parent**. Take its PM key from `breakdown.md`. If no epic exists, ask whether to create one or attach children to an existing key — do not guess a key. Confirm the key is `issuetype: Epic` (`workitem view --fields issuetype`) before parenting under it. +- Each task entry becomes **one child** — Story by default; Task or Bug only if the entry says so. +- For each child, capture its **Owner** (team → label), **Blocked by**, and **Depends on** for Step 5. + +**Completion criterion:** an echoed tree — epic key, then the ordered children with type and parent — where the child count matches the task count in `tasks.md`. + +## Step 3 — Turn each task into real ticket fields + +Translate the entry, don't copy it: + +- **Title** — imperative verb + outcome + context (client/area), matching sibling-ticket house style, e.g. `Add CSV export to the item list (web)`. NOT the decomposition label (`ExportService + column mapping (libs/exporter)`). +- **Description** — one short paragraph of the actual work plus genuinely per-ticket caveats. No lineage boilerplate (`Part of PM-####`), no breakdown path — the epic-child link already conveys that. Spell out shorthand; no `§` or `→` symbols. +- **Acceptance criteria** — its own ADF heading, written in Gherkin (`Scenario` / `Given` / `When` / `Then` / `And`). +- **Labels** — Team, Capability Driver, and Initiative Owner auto-default from the PM project — do not set them. If the only owner-derived label would be Team, apply no label. Otherwise verify the expected label against a sibling child ticket of the same epic before applying. + +Build the description as ADF and assemble the create command from **[references/acli-and-adf.md](references/acli-and-adf.md)**. + +**Completion criterion:** for every child, a drafted `{title, ADF description containing an "Acceptance criteria" heading, type, parent, labels}`. + +## Step 4 — Create one at a time + +Never batch-create: + +1. Preview the exact fields and the full `acli ... create` command for the **first** ticket. +2. Create only that one. Record its returned key. +3. Pause — wait for the user to review it in Jira and give the go-ahead. +4. Proceed to the next, one ticket at a time, recording each key. + +**Completion criterion:** every child created, each returned key recorded, and the user reviewed the first before the rest were created. + +## Step 5 — Wire dependency links + +From each task's **Blocked by** / **Depends on**, create links with `acli`. Linking is reversible (`link delete`), so this step may run as a batch once all tickets exist. + +- Map to a link type: hard dependency (must land first) → **Blocks**; soft / ordering-only → **Relates**. Run `acli jira workitem link type` once to confirm the available types. +- The acli link **direction is inverted** from its own success message — see the reference for the exact `--out`/`--in` mapping and how to read a link back. +- Verify every link with `acli jira workitem link list --key --json` (check it against the Jira UI panel headings when unsure). + +**Completion criterion:** every relationship whose target ticket exists is created and verified via `link list`; relationships pointing at not-yet-created work are reported back to the user, not dropped. diff --git a/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/evals/README.md b/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/evals/README.md new file mode 100644 index 0000000..f69a375 --- /dev/null +++ b/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/evals/README.md @@ -0,0 +1,11 @@ +# creating-jira-tickets evals + +Behavior test cases for the `creating-jira-tickets` skill, in the `skill-creator` schema. + +`evals.json` holds seven cases guarding the skill's load-bearing decisions: acli auth preflight, refusing to write through the read-only MCP, refusing to batch-create, rewriting a decomposition label into a real title, the Gherkin "Acceptance criteria" ADF section, the inverted `--in`/`--out` link direction, and asking about a missing epic. Each prompt embeds an inline `tasks.md` fixture with placeholder keys, so cases stay hermetic. + +Cases are **advice-only** — every prompt asks for the plan, so no case creates or links a live ticket; the benchmark is mutation-safe. + +Run with `/skill-creator:skill-creator` in Benchmark mode (with-skill vs. without-skill), several runs per config with a config-blind grader. A case gains a `notes` field once an ablation records an outcome. + +`trigger-eval.json` is a separate 10/10 should-trigger / should-not set; run it with the sibling `../creating-pull-request/evals/run_real_eval.py` after setting its `TARGET_SKILL_TOKEN` to `creating-jira-tickets`. diff --git a/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/evals/evals.json b/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/evals/evals.json new file mode 100644 index 0000000..8b0d237 --- /dev/null +++ b/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/evals/evals.json @@ -0,0 +1,77 @@ +{ + "skill_name": "creating-jira-tickets", + "evals": [ + { + "id": 1, + "prompt": "Here's the tasks.md for a breakdown. Create the Jira tickets for it — show me the plan before you run anything.\n\n---\n# Tasks — Epic: PROJ-100 (Add data export to the item list)\n\n- Task: Build the CSV formatter in the exporter library\n Owner: Tools\n Acceptance Criteria: Given a list of items, When the formatter runs, Then it returns RFC-4180 CSV with one row per item\n- Task: Add the Export button to the item-list toolbar (web)\n Owner: Web\n Blocked by: Task 1\n Acceptance Criteria: Given a user viewing the item list, When they click Export and choose CSV, Then the CSV downloads\n- Task: Add the Export action to the desktop item list\n Owner: Desktop\n Blocked by: Task 1\n Acceptance Criteria: Given the desktop item list, When the user chooses Export, Then the CSV downloads\n---", + "expected_output": "Checks the write path with `acli jira auth status`, builds a ticket tree (epic PROJ-100 as parent, one child per task entry — three here — with type and parent), and echoes the plan back before creating anything.", + "expectations": [ + "Checks `acli jira auth status` before drafting or creating tickets", + "Establishes PROJ-100 as parent with three child stories, one per task entry (count matches)", + "Echoes the ticket tree back and does not create any ticket in this planning response" + ] + }, + { + "id": 2, + "prompt": "acli isn't installed on this machine. Just use the Atlassian MCP to create the tickets from tasks.md instead.", + "expected_output": "Explains that the Atlassian MCP is read-only and has no write tool, so creation must go through acli; prompts the user to install and authenticate acli, and stops rather than attempting an MCP write.", + "expectations": [ + "States that the Atlassian MCP is read-only and cannot create tickets", + "Does not attempt to create tickets through any MCP tool", + "Prompts to install and authenticate acli and stops until that is resolved" + ], + "notes": "Non-discriminating: the baseline also treats the MCP as read-only in this environment. Kept as a regression guard, not a lift claim." + }, + { + "id": 3, + "prompt": "acli is authenticated. Just batch-create all of these stories under the epic now so I don't have to babysit it.\n\n---\n# Tasks — Epic: PROJ-100\n- Task: Build the CSV formatter in the exporter library (Owner: Tools)\n- Task: Add the Export button to the item-list toolbar (web) (Owner: Web)\n- Task: Add the Export action to the desktop item list (Owner: Desktop)\n---", + "expected_output": "Declines to batch-create despite the request; creates the first ticket only, then pauses for the user to review it in Jira before proceeding one ticket at a time.", + "expectations": [ + "Refuses to create all of them at once despite being told to", + "Previews and creates only the first ticket, then pauses for user review before the next", + "Explains that one-at-a-time review catches wording/field mistakes before they repeat across the set" + ] + }, + { + "id": 4, + "prompt": "Draft the Jira ticket for this tasks.md entry — show me the fields before creating.\n\n---\n- Task: RetryPolicy + backoff wiring (libs/network)\n Owner: Platform\n Description: Add automatic retry with exponential backoff to failed network requests.\n Acceptance Criteria: Given a request that fails with a 503, When the client retries, Then it backs off exponentially up to 3 attempts\n---", + "expected_output": "Rewrites the decomposition label 'RetryPolicy + backoff wiring (libs/network)' into a real ticket title (imperative verb + outcome + context) rather than copying it, and drafts a description free of lineage boilerplate and shorthand symbols.", + "expectations": [ + "Produces a real ticket title (imperative verb + outcome + area), not the raw decomposition label 'RetryPolicy + backoff wiring (libs/network)'", + "Description carries the actual work, without 'Part of PROJ-####' lineage boilerplate or the breakdown path", + "Spells out shorthand — no § or → symbols in the ticket" + ] + }, + { + "id": 5, + "prompt": "Draft the description for this story and show me the ADF you'll send.\n\n---\n- Task: Add the Export button to the item-list toolbar (web)\n Owner: Web\n Acceptance Criteria:\n Given a user viewing the item list\n When they click Export and choose CSV\n Then a CSV file of the listed items downloads\n---", + "expected_output": "Builds an ADF description with a summary paragraph and a distinct level-2 'Acceptance criteria' heading holding the criteria as Gherkin, and does not set Team / Capability Driver / Initiative Owner (they auto-default from the project).", + "expectations": [ + "ADF has a distinct 'Acceptance criteria' heading separate from the summary paragraph", + "Acceptance criteria are rendered as Gherkin (Scenario/Given/When/Then/And) in a code block", + "Does not set Team, Capability Driver, or Initiative Owner fields" + ] + }, + { + "id": 6, + "prompt": "Two tickets already exist: PROJ-101 (Task 1) and PROJ-103 (Task 3). Task 3 is blocked by Task 1. Wire that dependency and confirm it landed — walk me through the commands.", + "expected_output": "Uses the acli link command with the blocker (PROJ-101) on `--in` and the blocked ticket (PROJ-103) on `--out`, notes that acli's success message reverses the direction, and verifies with `acli jira workitem link list` rather than the MCP.", + "expectations": [ + "Puts the blocker PROJ-101 on --in and the blocked ticket PROJ-103 on --out for a Blocks link", + "Notes that acli's printed confirmation reverses the direction and should be ignored", + "Verifies the link with `acli jira workitem link list`, not the MCP get_issue (which does not render links)" + ], + "notes": "Strongest guard: with-skill got the inverted --in/--out and the link-list verify; the baseline missed both. Re-run with a blind grader to confirm as a durable ablation result." + }, + { + "id": 7, + "prompt": "Create tickets from this tasks.md. The breakdown doesn't reference any epic key. acli is authenticated. Show me the plan.\n\n---\n# Tasks\n- Task: Build the CSV formatter in the exporter library (Owner: Tools)\n- Task: Add the Export button to the item-list toolbar (web) (Owner: Web; Blocked by: Task 1)\n---", + "expected_output": "Detects that the fixture carries no epic key and asks whether to create an epic or attach children to an existing key, rather than guessing a parent key or filing orphaned children.", + "expectations": [ + "Notices there is no epic key in the tasks.md fixture", + "Asks whether to create an epic or attach to an existing key instead of guessing", + "Does not invent a parent key or create children with no parent" + ] + } + ] +} diff --git a/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/evals/trigger-eval.json b/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/evals/trigger-eval.json new file mode 100644 index 0000000..fc69791 --- /dev/null +++ b/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/evals/trigger-eval.json @@ -0,0 +1,83 @@ +[ + { + "query": "create the jira tickets from this tasks.md", + "should_trigger": true + }, + { + "query": "turn the breakdown's task decomposition into Jira tickets", + "should_trigger": true + }, + { + "query": "file the epic and its child stories for this breakdown in Jira", + "should_trigger": true + }, + { + "query": "the tasks.md is done — make tickets out of each task under the epic", + "should_trigger": true + }, + { + "query": "I need these breakdown tasks filed as stories in Jira, can you set that up", + "should_trigger": true + }, + { + "query": "take tasks.md and create the child issues with their acceptance criteria", + "should_trigger": true + }, + { + "query": "put the breakdown work into Jira as an epic plus stories and wire the blocked-by links", + "should_trigger": true + }, + { + "query": "draft and create the Jira tickets for the tasks we just decomposed", + "should_trigger": true + }, + { + "query": "let's get these tasks into Jira now that the plan's approved", + "should_trigger": true + }, + { + "query": "create a story per task in tasks.md and link the dependencies", + "should_trigger": true + }, + + { + "query": "what's PROJ-123 about? give me context on it", + "should_trigger": false + }, + { + "query": "deep dive PROJ-456 and its blocked dependencies", + "should_trigger": false + }, + { + "query": "update the description on PROJ-789, it's already created", + "should_trigger": false + }, + { + "query": "change the acceptance criteria on this existing ticket", + "should_trigger": false + }, + { + "query": "open a PR for the feat/csv-export branch", + "should_trigger": false + }, + { + "query": "commit these changes with a conventional commit message", + "should_trigger": false + }, + { + "query": "decompose this breakdown plan into a tasks.md", + "should_trigger": false + }, + { + "query": "how do Jira epics and stories relate to each other?", + "should_trigger": false + }, + { + "query": "search Jira for open bugs in the exporter component", + "should_trigger": false + }, + { + "query": "move the finished breakdown into its team's complete folder", + "should_trigger": false + } +] diff --git a/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/references/acli-and-adf.md b/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/references/acli-and-adf.md new file mode 100644 index 0000000..005da3d --- /dev/null +++ b/plugins/bitwarden-delivery-tools/skills/creating-jira-tickets/references/acli-and-adf.md @@ -0,0 +1,125 @@ +# acli create + ADF reference + +Mechanics for Steps 3–5. All commands run against `bitwarden.atlassian.net` as the authenticated user. + +`--key` is a `link ...` flag only. `workitem view` and `workitem create` take the key positionally: `acli jira workitem view PM-XXXX --fields "summary,issuetype" --json`. + +## Create command + +Core fields go through flags; the description goes through an ADF file: + +Keys and names below are placeholders — substitute the breakdown's real epic key. Bitwarden files into the `PM` project. + +```bash +acli jira workitem create \ + --project PM \ + --type Story \ + --parent PM-XXXX \ + --summary "Add CSV export to the item list (web)" \ + --label "team-label,area-label" \ + --description-file ./ticket-adf.json +``` + +- `--type` — `Epic`, `Story`, `Task`, or `Bug`. +- `--parent` — the epic key for a child story/task. +- `--label` — comma-separated, no spaces. +- **Team / Capability Driver / Initiative Owner auto-default from the project** (for Bitwarden's `PM` project) — do not pass them. +- **Priority is not a create flag** (defaults to Medium). Set it afterward with `acli jira workitem edit` only if the task calls for it. +- Add `--json` to capture the returned key programmatically. + +## ADF description shape + +`--description-file` takes an Atlassian Document Format JSON document: a summary paragraph, then a level-2 `Acceptance criteria` heading, then a `codeBlock` holding the Gherkin. + +```json +{ + "version": 1, + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Add a CSV export option to the item list so users can download their items. " + }, + { + "type": "text", + "text": "libs/exporter is owned by another team and needs their review." + } + ] + }, + { + "type": "heading", + "attrs": { "level": 2 }, + "content": [{ "type": "text", "text": "Acceptance criteria" }] + }, + { + "type": "codeBlock", + "attrs": { "language": "gherkin" }, + "content": [ + { + "type": "text", + "text": "Scenario: User exports the item list as CSV\n Given a user viewing the item list\n When they choose Export and select CSV\n Then a CSV file of the listed items downloads\n And the file preserves the list's current column order" + } + ] + } + ] +} +``` + +### Linking a ticket reference in prose + +To make a ticket key clickable in an ADF paragraph, apply a `link` mark to the text node: + +```json +{ + "type": "text", + "text": "PM-XXXX", + "marks": [ + { + "type": "link", + "attrs": { "href": "https://bitwarden.atlassian.net/browse/PM-XXXX" } + } + ] +} +``` + +A key inside the Gherkin `codeBlock` cannot carry a link mark — that is fine; leave it as plain text. + +## Linking dependencies + +Map dependencies to link types: + +- Hard dependency (must land first) → **Blocks** +- Soft / ordering-only dependency → **Relates** + +**The direction is inverted from acli's own success message.** Verified against the Jira UI: to make **X block Y**, run with the blocker on `--in`: + +```bash +# Make PM-YYYY (the blocker) block PM-ZZZZ: +acli jira workitem link create --out PM-ZZZZ --in PM-YYYY --type Blocks --yes +``` + +acli then prints a reversed confirmation like `PM-ZZZZ Blocks PM-YYYY` — ignore the message and verify instead. + +### Reading a link back + +```bash +acli jira workitem link list --key PM-YYYY --json +``` + +Payload — top-level `issueLinks`, type is `typeName`: + +```json +{ + "issueLinks": [ + { "id": "84678", "outwardIssueKey": "PM-ZZZZ", "typeName": "Blocks" }, + { "id": "84680", "outwardIssueKey": null, "typeName": "Relates" } + ] +} +``` + +`outwardIssueKey` non-null = the queried issue is the blocker/outward side (here `PM-YYYY blocks PM-ZZZZ`); `null` = the queried issue is the blocked/inward side. Confirm against the UI panel headings (`blocks` vs `is blocked by`) when in doubt. + +Available link types: `acli jira workitem link type`.