diff --git a/.cursor/skills/jira-dmtools/SKILL.md b/.cursor/skills/jira-dmtools/SKILL.md new file mode 100644 index 00000000..edfed2fc --- /dev/null +++ b/.cursor/skills/jira-dmtools/SKILL.md @@ -0,0 +1,51 @@ +--- +name: jira-dmtools +description: Perform JIRA operations via dmtools CLI. Flow: (1) verify JIRA connection via verify script; (2) run script without --verify to create/refresh .dmtools/jira.cfg if missing; (3) select command from Command index by task, read commands//SKILL.md, run dmtools. JIRA config in dmtools.env (JIRA_BASE_PATH, JIRA_EMAIL, JIRA_API_TOKEN). +--- + +# JIRA operations with dmtools CLI + +## Agent flow (follow in order) + +1. **Verify JIRA connection** + Run from repo root: `python .cursor/skills/jira-dmtools/scripts/verify_jira_connection.py --verify` + This checks that `.dmtools/jira.cfg` exists and is valid. Connection is driven by **dmtools.env** (or env vars): `JIRA_BASE_PATH`, `JIRA_EMAIL`, `JIRA_API_TOKEN`. + +2. **Create or refresh config if missing/invalid** + If step 1 fails (no jira.cfg or invalid), run: `python .cursor/skills/jira-dmtools/scripts/verify_jira_connection.py` + This uses dmtools.env, calls JIRA, and writes `.dmtools/jira.cfg` on success. Optionally add `--project PROJ` to cache issue types, statuses, components. + +3. **Select and run the command** + Once jira.cfg is present, match the user's task to the **"When to use"** column in the Command index table below. Open the indicated `commands//SKILL.md`, then run the corresponding `dmtools` command (positional args or `--data ''`). + +**On 401/403/timeout** during a dmtools call: re-run step 2 to refresh config, then retry the command. + +**Identifying failures quickly:** For workflow (move/transition) commands, 404 with a mangled URL (e.g. path containing `statusName/:/Partial` with the status name cut off) usually means the shell split the `--data` JSON—use single-quoted JSON. For other commands, see the "Avoiding malformed API URLs" / "Identifying issues faster" section in the relevant `commands//SKILL.md`. + +## How to invoke + +Run `dmtools ` in the terminal. Use positional args for simple calls or `--data ''` for complex ones. Output is JSON on stdout. + +**List all JIRA tools:** `dmtools list | jq '.tools[] | select(.name | startswith("jira_"))'` + +**Subskills:** For each group below, read `commands//SKILL.md` when the task matches the "When to use" description; that file has the exact command and parameters. + +## Command index + +| Group | When to use | Tools | +|-------|-------------|-------| +| **Search** | Search or list tickets by JQL, project, status, or with paging. | `commands/search` → `jira_search_by_jql`, `jira_search_by_page`, `jira_search_with_pagination` | +| **Read** | Get one ticket, its subtasks, comments, or available transitions. | `commands/tickets-read` → `jira_get_ticket`, `jira_get_subtasks`, `jira_get_comments`, `jira_get_transitions` | +| **Create** | Create a ticket (basic, with custom fields, or as subtask under parent). | `commands/ticket-create` → `jira_create_ticket_basic`, `jira_create_ticket_with_json`, `jira_create_ticket_with_parent` | +| **Update** | Change fields, description, parent, or clear a field on a ticket. | `commands/ticket-update` → `jira_update_ticket`, `jira_update_field`, `jira_update_description`, `jira_update_all_fields_with_name`, `jira_update_ticket_parent`, `jira_clear_field` | +| **Delete** | Delete a ticket (irreversible). | `commands/ticket-delete` → `jira_delete_ticket` | +| **Workflow** | Move ticket to a status, set resolution, or assign to a user. | `commands/workflow` → `jira_move_to_status`, `jira_move_to_status_with_resolution`, `jira_assign_ticket_to` | +| **Profile** | Resolve account ID by email, get current user or another user's profile. | `commands/profile` → `jira_get_account_by_email`, `jira_get_my_profile`, `jira_get_user_profile` | +| **Comments** | Add a comment or add only if not already present; read comments is under Read. | `commands/comments` → `jira_post_comment`, `jira_post_comment_if_not_exists` | +| **Fix versions** | List, set, add, or remove fix version(s) on a ticket or project. | `commands/fix-versions` → `jira_get_fix_versions`, `jira_set_fix_version`, `jira_add_fix_version`, `jira_remove_fix_version` | +| **Links & labels** | List link types, link two issues, add label, or set priority. | `commands/links-labels-priority` → `jira_get_issue_link_types`, `jira_link_issues`, `jira_add_label`, `jira_set_priority` | +| **Fields data** | Get field definitions or custom field ID(s) by display name for a project. | `commands/fieldsdata` → `jira_get_fields`, `jira_get_field_custom_code`, `jira_get_all_fields_with_name` | +| **Metadata** | Get issue types, statuses, or components for a project. | `commands/metadata` → `jira_get_issue_types`, `jira_get_project_statuses`, `jira_get_components` | +| **Attachments** | Attach a file to a ticket or download an attachment by URL. | `commands/attachments` → `jira_attach_file_to_ticket`, `jira_download_attachment` | +| **Xray** | Xray test/precondition operations: search tests, get steps/preconditions, create or add to tests. | `commands/xray` → `jira_xray_*` (search, get details/steps/preconditions, create precondition, add to test) | +| **Advanced** | Run a custom GET request to the JIRA API with auth. | `commands/advanced` → `jira_execute_request` | diff --git a/.cursor/skills/jira-dmtools/commands/advanced/SKILL.md b/.cursor/skills/jira-dmtools/commands/advanced/SKILL.md new file mode 100644 index 00000000..50686abf --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/advanced/SKILL.md @@ -0,0 +1,40 @@ +--- +name: jira-dmtools-advanced +description: Use when the user needs to run a custom GET request to the JIRA API with auth. Covers jira_execute_request. Run dmtools in the terminal. +--- + +# JIRA custom API request + +## When to use this skill + +Use when the user needs to: +- Call a JIRA REST API endpoint that is not covered by other jira_* tools +- Perform a custom GET request with the same authentication as dmtools (JIRA_BASE_PATH, JIRA_EMAIL, JIRA_API_TOKEN) + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_execute_request` | Custom HTTP GET to Jira API with auth | `url` (required) | + +## Steps + +1. Build the full JIRA API URL (e.g. `https://company.atlassian.net/rest/api/3/issue/PROJ-123?expand=changelog`). Base is typically from JIRA_BASE_PATH. +2. Run `dmtools jira_execute_request --data '{"url":"https://..."}'`. +3. Parse JSON (or other) stdout; check stderr on failure. + +## Examples + +```bash +# Custom GET (e.g. issue with expand) +dmtools jira_execute_request --data '{"url":"https://company.atlassian.net/rest/api/3/issue/PROJ-123?expand=changelog"}' +``` + +## Notes + +- Only GET is supported; no POST/PUT/DELETE. For other operations use the dedicated jira_* tools where possible. +- URL must be reachable with the configured JIRA credentials (same host as JIRA_BASE_PATH typically). + +## Avoiding malformed API URLs + +`jira_execute_request` uses `--data` with a single `url` field. Ensure the JSON is valid and passed as a **single argument** (correct shell quoting); bad quoting can cause mangled URLs and 404s. diff --git a/.cursor/skills/jira-dmtools/commands/attachments/SKILL.md b/.cursor/skills/jira-dmtools/commands/attachments/SKILL.md new file mode 100644 index 00000000..f74ffc2f --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/attachments/SKILL.md @@ -0,0 +1,47 @@ +--- +name: jira-dmtools-attachments +description: Use when the user needs to attach a file to a JIRA ticket or download an attachment. Covers jira_attach_file_to_ticket, jira_download_attachment. Run dmtools in the terminal. +--- + +# JIRA attachments + +## When to use this skill + +Use when the user wants to: +- Attach a local file to a JIRA ticket (skipped if a file with the same name already exists) +- Download an attachment from JIRA by URL to a local file + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_attach_file_to_ticket` | Attach file from path | `name`, `ticketKey`, `filePath` (required), `contentType` (optional) | +| `jira_download_attachment` | Download by URL | `href` (required) | + +## Steps + +1. **Attach:** Have the ticket key, file path (absolute), and display name. Run `dmtools jira_attach_file_to_ticket --data '{"name":"doc.pdf","ticketKey":"PROJ-123","filePath":"/path/to/doc.pdf"}'`. Add `"contentType":"application/pdf"` if needed (default may be image/*). +2. **Download:** Use the attachment href from ticket or search response: `dmtools jira_download_attachment --data '{"href":"https://company.atlassian.net/..."}'`. +3. Parse stdout; check stderr on failure. + +## Examples + +```bash +# Attach PDF +dmtools jira_attach_file_to_ticket --data '{"name":"spec.pdf","ticketKey":"PROJ-123","filePath":"/tmp/spec.pdf","contentType":"application/pdf"}' + +# Attach image (contentType optional) +dmtools jira_attach_file_to_ticket --data '{"name":"screenshot.png","ticketKey":"PROJ-123","filePath":"/tmp/screenshot.png"}' + +# Download +dmtools jira_download_attachment --data '{"href":"https://company.atlassian.net/rest/api/3/attachment/12345/content"}' +``` + +## Notes + +- Attach only adds if no attachment with the same name exists on the ticket. Use absolute paths for `filePath`. +- Download href is typically from the ticket's attachment list (Jira REST API or jira_get_ticket with attachments). + +## Avoiding malformed API URLs + +These commands use `--data` with JSON. Ensure the JSON is valid and passed as a **single argument** (correct shell quoting); bad quoting can cause mangled URLs and 404s. diff --git a/.cursor/skills/jira-dmtools/commands/comments/SKILL.md b/.cursor/skills/jira-dmtools/commands/comments/SKILL.md new file mode 100644 index 00000000..69f2f50f --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/comments/SKILL.md @@ -0,0 +1,53 @@ +--- +name: jira-dmtools-comments +description: Use when the user needs to add or read comments on a JIRA ticket. Covers jira_post_comment, jira_post_comment_if_not_exists, jira_get_comments. Run dmtools in the terminal. +--- + +# JIRA comments + +## When to use this skill + +Use when the user wants to: +- Add a comment to a ticket +- Add a comment only if it does not already exist (idempotent) +- Read all comments on a ticket + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_post_comment` | Add comment (Jira markup supported) | `key`, `comment` (required) | +| `jira_post_comment_if_not_exists` | Add only if comment not present | `key`, `comment` (required) | +| `jira_get_comments` | Get all comments | `key` (required), `ticket` (optional) | + +## Steps + +1. Have the ticket key and comment text ready. +2. To **add:** `dmtools jira_post_comment PROJ-123 "Comment text"` or `dmtools jira_post_comment --data '{"key":"PROJ-123","comment":"Comment text"}'`. Use `jira_post_comment_if_not_exists` with the same args when you want to avoid duplicates. +3. To **read:** `dmtools jira_get_comments PROJ-123`. +4. Parse JSON stdout; on error check stderr. + +## Examples + +```bash +# Post comment (positional) +dmtools jira_post_comment PROJ-123 "Deployed to staging." + +# Post comment (JSON, useful for long or multi-line) +dmtools jira_post_comment --data '{"key":"PROJ-123","comment":"h2. Update\n* Item 1\n* Item 2"}' + +# Post only if not already present +dmtools jira_post_comment_if_not_exists --data '{"key":"PROJ-123","comment":"Automated sync completed"}' + +# Get comments +dmtools jira_get_comments PROJ-123 +``` + +## Notes + +- Jira markup in comments: `h2.` for headings, `*text*` for bold, `{code}...{code}` for code, `*` for bullet lists. +- Escape single quotes in JSON (e.g. `'"'"'` in shell) or use heredoc for complex content. + +## Avoiding malformed API URLs + +Prefer **positional** form for get and simple post: `dmtools jira_get_comments PROJ-123` and `dmtools jira_post_comment PROJ-123 "Comment text"`. Use `--data` only for long or multi-line comments. Ensure `--data` JSON is valid and passed as a single argument; bad quoting can cause mangled URLs and 404s. If you get a 404 or mangled path, retry with positional. diff --git a/.cursor/skills/jira-dmtools/commands/fieldsdata/SKILL.md b/.cursor/skills/jira-dmtools/commands/fieldsdata/SKILL.md new file mode 100644 index 00000000..b16a9212 --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/fieldsdata/SKILL.md @@ -0,0 +1,45 @@ +--- +name: jira-dmtools-fieldsdata +description: Use when the user needs JIRA field definitions or custom field IDs. Covers jira_get_fields, jira_get_field_custom_code, jira_get_all_fields_with_name. Run dmtools in the terminal. +--- + +# JIRA fields data + +## When to use this skill + +Use when the user wants to: +- List all fields for a project +- Resolve a human-readable field name to custom field ID (e.g. "Story Points" -> customfield_10021) +- Get all field IDs that share a display name + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_get_fields` | All fields for project | `project` (required) | +| `jira_get_field_custom_code` | Custom field ID for a name | `project`, `fieldName` (required) | +| `jira_get_all_fields_with_name` | All field IDs with same name | `project`, `fieldName` (required) | + +## Steps + +1. Have the project key (e.g. `PROJ`). +2. Run the needed command: `dmtools jira_get_fields PROJ`, or `jira_get_field_custom_code` / `jira_get_all_fields_with_name` with `--data`. +3. Use the returned IDs in update/create/clear operations (e.g. customfield_10021 in `jira_update_field` or `jira_clear_field`). + +## Examples + +```bash +dmtools jira_get_fields PROJ +dmtools jira_get_field_custom_code --data '{"project":"PROJ","fieldName":"Story Points"}' +dmtools jira_get_all_fields_with_name --data '{"project":"PROJ","fieldName":"Dependencies"}' +``` + +## Notes + +- Use `jira_get_field_custom_code` before updating or clearing a custom field by ID. +- Some projects have multiple custom fields with the same display name; `jira_get_all_fields_with_name` returns all; `jira_update_all_fields_with_name` (ticket-update) updates all of them. +- **Cached in jira.cfg:** Field data (e.g. from `jira_get_fields`, `jira_get_field_custom_code`) can be stored in `.dmtools/jira.cfg` per project. The verification script (run with `--project PROJ`) can populate this; agents may read `jira.cfg` for known field IDs when appropriate to avoid repeated API calls. + +## Avoiding malformed API URLs + +Prefer **positional** for single-param: `dmtools jira_get_fields PROJ`. For `jira_get_field_custom_code` and `jira_get_all_fields_with_name` use `--data`; ensure the JSON is valid and passed as a single argument (correct shell quoting). Bad quoting can cause mangled URLs and 404s. diff --git a/.cursor/skills/jira-dmtools/commands/fix-versions/SKILL.md b/.cursor/skills/jira-dmtools/commands/fix-versions/SKILL.md new file mode 100644 index 00000000..7691617f --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/fix-versions/SKILL.md @@ -0,0 +1,55 @@ +--- +name: jira-dmtools-fix-versions +description: Use when the user needs to list or change fix versions on JIRA tickets or project. Covers jira_get_fix_versions, jira_set_fix_version, jira_add_fix_version, jira_remove_fix_version. Run dmtools in the terminal. +--- + +# JIRA fix versions + +## When to use this skill + +Use when the user wants to: +- List fix versions for a project +- Set or add a fix version on a ticket (set replaces, add keeps existing) +- Remove a fix version from a ticket + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_get_fix_versions` | List fix versions for project | `project` (required) | +| `jira_set_fix_version` | Set fix version (replaces existing) | `key`, `fixVersion` (required) | +| `jira_add_fix_version` | Add fix version (keep existing) | `key`, `fixVersion` (required) | +| `jira_remove_fix_version` | Remove a fix version | `key`, `fixVersion` (required) | + +## Steps + +1. **List:** `dmtools jira_get_fix_versions PROJ` to see available version names. +2. **Set (replace):** `dmtools jira_set_fix_version --data '{"key":"PROJ-123","fixVersion":"1.0.0"}'`. +3. **Add (keep others):** `dmtools jira_add_fix_version --data '{"key":"PROJ-123","fixVersion":"1.1.0"}'`. +4. **Remove:** `dmtools jira_remove_fix_version --data '{"key":"PROJ-123","fixVersion":"1.0.0"}'`. +5. Parse stdout; check stderr on failure. + +## Examples + +```bash +# List versions +dmtools jira_get_fix_versions PROJ + +# Set single version (replaces any existing) +dmtools jira_set_fix_version --data '{"key":"PROJ-123","fixVersion":"2.0.0"}' + +# Add version (multi-version ticket) +dmtools jira_add_fix_version --data '{"key":"PROJ-123","fixVersion":"2.1.0"}' + +# Remove +dmtools jira_remove_fix_version --data '{"key":"PROJ-123","fixVersion":"1.0.0"}' +``` + +## Notes + +- Fix version names must exist in the project; use `jira_get_fix_versions` first. +- `set` clears other fix versions on the ticket; use `add` to keep them. + +## Avoiding malformed API URLs + +Prefer **positional** for list: `dmtools jira_get_fix_versions PROJ`. Set/add/remove use `--data`; ensure the JSON is valid and passed as a single argument. Bad quoting can cause mangled URLs and 404s. diff --git a/.cursor/skills/jira-dmtools/commands/links-labels-priority/SKILL.md b/.cursor/skills/jira-dmtools/commands/links-labels-priority/SKILL.md new file mode 100644 index 00000000..ce74fa46 --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/links-labels-priority/SKILL.md @@ -0,0 +1,22 @@ +--- +name: jira-dmtools-links-labels-priority +description: Link issues, add labels, or set priority. jira_get_issue_link_types, jira_link_issues, jira_add_label, jira_set_priority. +--- + +# JIRA links, labels, priority + +Use when linking two issues, adding a label, or setting priority. + +**Commands:** `jira_get_issue_link_types` (no args), `jira_link_issues` (sourceKey, relationship, anotherKey), `jira_add_label` (key, label), `jira_set_priority` (key, priority). + +**Examples:** +```bash +dmtools jira_get_issue_link_types +dmtools jira_link_issues --data '{"sourceKey":"PROJ-123","relationship":"blocks","anotherKey":"PROJ-456"}' +dmtools jira_add_label PROJ-123 "mylabel" +dmtools jira_set_priority --data '{"key":"PROJ-123","priority":"High"}' +``` + +Get valid relationship names from the first command. Priority values depend on project. + +**Avoiding malformed API URLs:** Prefer **positional** for add_label: `dmtools jira_add_label PROJ-123 "mylabel"`. For link_issues and set_priority use `--data`; ensure JSON is valid and passed as a single argument to avoid mangled URLs and 404s. diff --git a/.cursor/skills/jira-dmtools/commands/metadata/SKILL.md b/.cursor/skills/jira-dmtools/commands/metadata/SKILL.md new file mode 100644 index 00000000..362f06bc --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/metadata/SKILL.md @@ -0,0 +1,41 @@ +--- +name: jira-dmtools-metadata +description: Use when the user needs JIRA project metadata: issue types, statuses, components. Covers jira_get_issue_types, jira_get_project_statuses, jira_get_components. For fields and field IDs see fieldsdata subskill. Run dmtools in the terminal. +--- + +# JIRA project metadata + +## When to use this skill + +Use when the user wants to: +- List issue types for a project +- List statuses for a project +- List components for a project + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_get_issue_types` | Issue types for project | `project` (required) | +| `jira_get_project_statuses` | Statuses for project | `project` (required) | +| `jira_get_components` | Components for project | `project` (required) | + +## Steps + +1. Have the project key (e.g. `PROJ`). +2. Run: `dmtools jira_get_issue_types PROJ`, `dmtools jira_get_project_statuses PROJ`, `dmtools jira_get_components PROJ`. +3. Use the returned names in create/update or workflow (e.g. status names for transitions). + +## Examples + +```bash +dmtools jira_get_issue_types PROJ +dmtools jira_get_project_statuses PROJ +dmtools jira_get_components PROJ +``` + +## Notes + +- For field definitions and custom field IDs use the fieldsdata subskill. +- These commands use **positional** args only (`dmtools jira_get_issue_types PROJ` etc.); no `--data`, so URL-mangling from JSON is not a concern. +- **Cached in jira.cfg:** Issue types, statuses, and components can be stored in `.dmtools/jira.cfg` per project. The verification script (run with `--project PROJ`) can populate this; agents may read `jira.cfg` for metadata when appropriate to avoid repeated API calls. diff --git a/.cursor/skills/jira-dmtools/commands/profile/SKILL.md b/.cursor/skills/jira-dmtools/commands/profile/SKILL.md new file mode 100644 index 00000000..f610607d --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/profile/SKILL.md @@ -0,0 +1,49 @@ +--- +name: jira-dmtools-profile +description: Use when the user needs JIRA user or account info. Covers jira_get_account_by_email, jira_get_my_profile, jira_get_user_profile. Run dmtools in the terminal. +--- + +# JIRA profile and account + +## When to use this skill + +Use when the user wants to: +- Get account ID by email (e.g. for assigning tickets) +- Get current user's profile (verify auth) +- Get a specific user's profile by ID + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_get_account_by_email` | Get account ID by email | `email` (required) | +| `jira_get_my_profile` | Current user profile | None | +| `jira_get_user_profile` | User profile by ID | `userId` (required) | + +## Steps + +1. For account by email: `dmtools jira_get_account_by_email "user@company.com"` (use returned accountId with `jira_assign_ticket_to`). +2. For current user: `dmtools jira_get_my_profile` (e.g. to verify JIRA config). +3. For another user: `dmtools jira_get_user_profile --data '{"userId":"123456:uuid"}'`. +4. Parse JSON stdout; check stderr on failure. + +## Examples + +```bash +# Resolve account ID for assignment +dmtools jira_get_account_by_email "user@company.com" + +# Verify JIRA config +dmtools jira_get_my_profile + +# User profile by ID +dmtools jira_get_user_profile --data '{"userId":"123456:uuid"}' +``` + +## Notes + +- Use `jira_get_account_by_email` before `jira_assign_ticket_to` when you only have the assignee's email. + +## Avoiding malformed API URLs + +`jira_get_account_by_email` and `jira_get_my_profile` use **positional** args only—no `--data`. For `jira_get_user_profile`, `--data` is required (userId). Ensure the JSON is valid and passed as a single argument; bad quoting can produce mangled URLs and 404s. diff --git a/.cursor/skills/jira-dmtools/commands/search/SKILL.md b/.cursor/skills/jira-dmtools/commands/search/SKILL.md new file mode 100644 index 00000000..85df0a15 --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/search/SKILL.md @@ -0,0 +1,53 @@ +--- +name: jira-dmtools-search +description: Use when the user needs to search or list JIRA tickets by JQL. Covers jira_search_by_jql, jira_search_by_page, jira_search_with_pagination. Run dmtools in the terminal. +--- + +# JIRA search (JQL) with dmtools + +## When to use this skill + +Use when the user wants to: +- Find tickets by project, status, assignee, or other JQL criteria +- List issues matching a JQL query +- Page through large result sets + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_search_by_jql` | Search by JQL, return all results | `jql` (required), `fields` (array, optional) | +| `jira_search_by_page` | Search with paging (nextPageToken) | `jql`, `fields` (required), `nextPageToken` (required) | +| `jira_search_with_pagination` | [Deprecated] Search with startAt | `jql`, `fields`, `startAt` (required) | + +## Steps + +1. Build a JQL string (e.g. `project = PROJ`, `project = PROJ AND status = Open`, `parent = PROJ-123 OR key = PROJ-123`). +2. Choose fields to return (optional). Omit or use `["key","summary","status"]` etc. +3. Run in terminal: + - **All results:** `dmtools jira_search_by_jql "project = PROJ" "key,summary,status"` or `dmtools jira_search_by_jql --data '{"jql":"project = PROJ ORDER BY key ASC","fields":["key","summary","status"]}'` + - **Paged:** `dmtools jira_search_by_page --data '{"jql":"project = PROJ","fields":["key","summary"],"nextPageToken":""}'` (use `nextPageToken` from response for next page). +4. Parse JSON stdout for `issues` array or equivalent; check for `error` on failure. + +## Examples + +```bash +# All open tickets in project +dmtools jira_search_by_jql "project = PROJ AND status = Open" "key,summary,status" + +# With JSON (e.g. for complex JQL or optional fields) +dmtools jira_search_by_jql --data '{"jql": "project = PROJ ORDER BY key ASC", "fields": ["key", "summary", "status", "assignee"]}' + +# Parent and children (context for a story) +dmtools jira_search_by_jql --data '{"jql": "parent = PROJ-123 OR key = PROJ-123", "fields": ["key", "summary", "status"]}' +``` + +## Notes + +- JQL is case-sensitive for keywords (e.g. `AND`, `OR`). Field names and values depend on your JIRA schema. +- Large result sets: use `jira_search_by_page` and loop with `nextPageToken`. +- Response may be an array of issues, an object with `issues`, or an object with `result` (array of issues); handle all when parsing. + +## Avoiding malformed API URLs + +Prefer **positional** form when you only have JQL and optional fields: `dmtools jira_search_by_jql "project = PROJ" "key,summary,status"`. Use `--data` only for complex JQL or when needed. Ensure `--data` JSON is valid and passed as a single argument (correct shell quoting); bad quoting can produce mangled URLs and 404s. If you get a 404 or "dead link", retry with the positional form. diff --git a/.cursor/skills/jira-dmtools/commands/ticket-create/SKILL.md b/.cursor/skills/jira-dmtools/commands/ticket-create/SKILL.md new file mode 100644 index 00000000..dcfd5c0d --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/ticket-create/SKILL.md @@ -0,0 +1,50 @@ +--- +name: jira-dmtools-ticket-create +description: Use when the user needs to create a JIRA ticket (basic, with JSON, or with parent). Covers jira_create_ticket_basic, jira_create_ticket_with_json, jira_create_ticket_with_parent. Run dmtools in the terminal. +--- + +# JIRA create tickets + +## When to use this skill + +Use when the user wants to: +- Create a new ticket with basic fields (issue type, summary, project, description) +- Create a ticket with custom fields (JSON) +- Create a ticket as child of a parent (e.g. subtask) + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_create_ticket_basic` | Create with type, summary, project, description | `issueType`, `summary`, `project`, `description` (all required) | +| `jira_create_ticket_with_json` | Create with custom fields | `project`, `fieldsJson` (required) | +| `jira_create_ticket_with_parent` | Create with parent key | `issueType`, `summary`, `project`, `description`, `parentKey` (all required) | + +## Steps + +1. Choose basic, JSON, or with-parent. +2. For basic: `dmtools jira_create_ticket_basic "Task" "Title" "PROJ" "Description"`. +3. For JSON or parent use `--data` with the required keys. +4. Parse stdout for created `key`; on error check stderr. + +## Examples + +```bash +# Create basic +dmtools jira_create_ticket_basic "Task" "Fix login bug" "PROJ" "Users cannot log in." + +# Create with parent (subtask) +dmtools jira_create_ticket_with_parent --data '{"issueType":"Subtask","summary":"Implement validation","project":"PROJ","description":"...","parentKey":"PROJ-123"}' + +# Create with custom fields +dmtools jira_create_ticket_with_json --data '{"project":"PROJ","fieldsJson":{"summary":"New feature","description":"...","issuetype":{"name":"Story"},"priority":{"name":"High"}}}' +``` + +## Notes + +- Descriptions and summaries support Jira markup: `h2.`, `*bold*`, `{code}...{code}`, `*` lists. +- For custom field IDs use `jira_get_field_custom_code` or `jira_get_fields` (see fieldsdata subskill). + +## Avoiding malformed API URLs + +Prefer **positional** for basic create: `dmtools jira_create_ticket_basic "Task" "Title" "PROJ" "Description"`. For `jira_create_ticket_with_json` and `jira_create_ticket_with_parent` use `--data`; ensure the JSON is valid and passed as a single argument. Bad quoting can cause mangled URLs and 404s. diff --git a/.cursor/skills/jira-dmtools/commands/ticket-delete/SKILL.md b/.cursor/skills/jira-dmtools/commands/ticket-delete/SKILL.md new file mode 100644 index 00000000..b734c6f9 --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/ticket-delete/SKILL.md @@ -0,0 +1,34 @@ +--- +name: jira-dmtools-ticket-delete +description: Use when the user needs to delete a JIRA ticket. Covers jira_delete_ticket. Run dmtools in the terminal. +--- + +# JIRA delete tickets + +## When to use this skill + +Use when the user wants to: +- Delete a JIRA ticket by key (irreversible) + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_delete_ticket` | Delete ticket (irreversible) | `key` (required) | + +## Steps + +1. Confirm the ticket key and that delete is intended (no undo). +2. Run `dmtools jira_delete_ticket PROJ-123`. +3. Check stdout/stderr and exit code. + +## Examples + +```bash +dmtools jira_delete_ticket PROJ-123 +``` + +## Notes + +- `jira_delete_ticket` cannot be undone; confirm before running. +- This command uses **positional** args only (`dmtools jira_delete_ticket PROJ-123`); no `--data`, so URL-mangling from JSON is not a concern. diff --git a/.cursor/skills/jira-dmtools/commands/ticket-update/SKILL.md b/.cursor/skills/jira-dmtools/commands/ticket-update/SKILL.md new file mode 100644 index 00000000..443d9676 --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/ticket-update/SKILL.md @@ -0,0 +1,55 @@ +--- +name: jira-dmtools-ticket-update +description: Use when the user needs to update a JIRA ticket or clear a field. Covers jira_update_ticket, jira_update_field, jira_update_description, jira_update_all_fields_with_name, jira_update_ticket_parent, jira_clear_field. Run dmtools in the terminal. +--- + +# JIRA update tickets and clear fields + +## When to use this skill + +Use when the user wants to: +- Update ticket fields, description, or parent +- Clear a field value + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_update_ticket` | Update with Jira REST API JSON | `key`, `params` (required) | +| `jira_update_field` | Update one field (by name or customfield_ id) | `key`, `field`, `value` (required) | +| `jira_update_description` | Set description | `key`, `description` (required) | +| `jira_update_all_fields_with_name` | Update all fields with same display name | `key`, `fieldName`, `value` (required) | +| `jira_update_ticket_parent` | Change parent | `key`, `parentKey` (required) | +| `jira_clear_field` | Clear a field | `key`, `field` (required) | + +## Steps + +1. Use `jira_update_ticket` for bulk REST-style updates, or `jira_update_field` / `jira_update_description` / `jira_update_ticket_parent` for single changes. +2. Pass `key` and the relevant payload via `--data`. +3. For clear: `jira_clear_field` with field name or customfield_ id. +4. Parse stdout; on error check stderr. + +## Examples + +```bash +# Update field +dmtools jira_update_field --data '{"key":"PROJ-123","field":"priority","value":{"name":"High"}}' + +# Update description +dmtools jira_update_description --data '{"key":"PROJ-123","description":"Updated text"}' + +# Change parent +dmtools jira_update_ticket_parent --data '{"key":"PROJ-124","parentKey":"PROJ-100"}' + +# Clear field +dmtools jira_clear_field --data '{"key":"PROJ-123","field":"customfield_10001"}' +``` + +## Notes + +- Jira markup in descriptions: `h2.`, `*bold*`, `{code}...{code}`, `*` lists. +- For custom field IDs use `jira_get_field_custom_code` or `jira_get_fields` (see fieldsdata subskill). `jira_update_all_fields_with_name` updates all fields with that display name. + +## Avoiding malformed API URLs + +These commands use `--data` with JSON. Ensure the JSON is valid and passed as a **single argument** (correct shell quoting); bad quoting can cause mangled URLs and 404s. diff --git a/.cursor/skills/jira-dmtools/commands/tickets-read/SKILL.md b/.cursor/skills/jira-dmtools/commands/tickets-read/SKILL.md new file mode 100644 index 00000000..36555c55 --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/tickets-read/SKILL.md @@ -0,0 +1,67 @@ +--- +name: jira-dmtools-tickets-read +description: Use when the user needs to read a JIRA ticket, its subtasks, comments, or available transitions. Covers jira_get_ticket, jira_get_subtasks, jira_get_comments, jira_get_transitions. Run dmtools in the terminal. +--- + +# JIRA read ticket, subtasks, comments, transitions + +## When to use this skill + +Use when the user wants to: +- Get full or partial details of a ticket by key +- List subtasks of a parent ticket +- Read comments on a ticket +- List available workflow transitions (next statuses) for a ticket + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_get_ticket` | Get ticket by key, optional fields | `key` (required), `fields` (array, optional) | +| `jira_get_subtasks` | Get subtasks of a parent | `key` (required) | +| `jira_get_comments` | Get all comments | `key` (required), `ticket` (optional) | +| `jira_get_transitions` | Get available transitions | `key` (required) | + +## Steps + +1. Identify the ticket key (e.g. `PROJ-123`). +2. Run the appropriate command in the terminal: + - **Ticket:** `dmtools jira_get_ticket PROJ-123` or `dmtools jira_get_ticket PROJ-123 summary,description,status` or `--data '{"key":"PROJ-123","fields":["summary","description","status","assignee"]}'` + - **Subtasks:** `dmtools jira_get_subtasks PROJ-123` + - **Comments:** `dmtools jira_get_comments PROJ-123` + - **Transitions:** `dmtools jira_get_transitions PROJ-123` +3. Parse JSON stdout; on error check stderr and exit code. Some dmtools responses wrap the payload in a `result` property; handle both top-level fields and `result` when parsing (e.g. for attachments). + +## Examples + +```bash +# Full ticket +dmtools jira_get_ticket PROJ-123 + +# Selected fields (positional) +dmtools jira_get_ticket PROJ-123 summary,description,status,assignee + +# Selected fields (JSON) +dmtools jira_get_ticket --data '{"key": "PROJ-123", "fields": ["summary", "description", "status", "assignee", "priority"]}' + +# Subtasks and comments +dmtools jira_get_subtasks PROJ-123 +dmtools jira_get_comments PROJ-123 +dmtools jira_get_transitions PROJ-123 +``` + +## Notes + +- Use `jira_get_transitions` before moving a ticket so you know valid `statusName` values for `jira_move_to_status`. +- Subtask issue types vary by project (e.g. subtask, sub-task); the tool handles common names. + +## Avoiding malformed API URLs + +Some shells or dmtools versions can build wrong URLs when `--data` JSON is used (e.g. fields or key end up in the path). To reduce this: + +1. **Prefer positional arguments for `jira_get_ticket`** when you only need the key and optional fields: + - `dmtools jira_get_ticket PROJ-123` (full ticket) + - `dmtools jira_get_ticket PROJ-123 summary,description,status` (comma-separated fields, no spaces) +2. **Keep field list simple:** use a single comma-separated string with no spaces (e.g. `summary,description,status`). +3. **Use `--data` only when necessary** (e.g. many or optional parameters). If you do, ensure JSON is valid and correctly quoted for your shell so the tool receives one argument; bad quoting can cause the CLI to split or mangle the request and produce 404s or malformed URLs. +4. If you get a 404 or "dead link" with a mangled path, retry with the positional form above. diff --git a/.cursor/skills/jira-dmtools/commands/workflow/SKILL.md b/.cursor/skills/jira-dmtools/commands/workflow/SKILL.md new file mode 100644 index 00000000..218ccff5 --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/workflow/SKILL.md @@ -0,0 +1,63 @@ +--- +name: jira-dmtools-workflow +description: Use when the user needs to move a JIRA ticket to a status or assign it. Covers jira_move_to_status, jira_move_to_status_with_resolution, jira_assign_ticket_to. For user/profile (account by email, my profile, user profile) see profile subskill. Run dmtools in the terminal. +--- + +# JIRA workflow and assignment + +## When to use this skill + +Use when the user wants to: +- Move a ticket to another status (workflow transition) +- Move to Done (or similar) and set a resolution +- Assign a ticket to a user (by account ID; resolve email to accountId via profile subskill) + +## Commands + +| Tool | Purpose | Parameters | +|------|---------|------------| +| `jira_move_to_status` | Move to status (transition) | `key`, `statusName` (required) | +| `jira_move_to_status_with_resolution` | Move and set resolution | `key`, `statusName`, `resolution` (required) | +| `jira_assign_ticket_to` | Assign ticket to user | `key`, `accountId` (required) | + +## Steps + +1. **Transitions:** Get valid status names with `dmtools jira_get_transitions PROJ-123`, then call `jira_move_to_status` or `jira_move_to_status_with_resolution` with that `statusName` (and `resolution` for Done-like statuses). +2. **Assign:** If you have email, use the profile subskill to run `jira_get_account_by_email` to get `accountId`, then `jira_assign_ticket_to` with `key` and `accountId`. +3. All mutation commands use `--data` with JSON; run in terminal and parse stdout/stderr. + +## Examples + +```bash +# List transitions then move +dmtools jira_get_transitions PROJ-123 +dmtools jira_move_to_status --data '{"key":"PROJ-123","statusName":"In Review"}' + +# Move to Done with resolution +dmtools jira_move_to_status_with_resolution --data '{"key":"PROJ-123","statusName":"Done","resolution":"Fixed"}' + +# Assign (accountId from jira_get_account_by_email in profile subskill) +dmtools jira_assign_ticket_to --data '{"key":"PROJ-123","accountId":"123456:uuid-from-above"}' +``` + +## Notes + +- Status and resolution names must match your project's workflow (e.g. "In Progress", "Done", "Fixed"). Use `jira_get_transitions` to see allowed values. +- Assignee must have permission to be assigned; unassign by assigning to `null` if your JIRA allows it (check API). + +## Avoiding malformed API URLs + +These commands use `--data` with JSON. Ensure the JSON is valid and passed as a **single argument** (correct shell quoting); bad quoting can cause the CLI to split or mangle the request and produce 404s or malformed URLs. Use single quotes around the JSON and escape inner quotes if needed. + +- **Windows / PowerShell:** Prefer single quotes around the entire JSON so the string is one argument: `dmtools jira_move_to_status --data '{"key":"PROJ-123","statusName":"Ready for work"}'`. Double-quoted JSON often gets split on spaces (e.g. status name truncated to "Ready"), producing 404s. +- **Status names with spaces:** Must be inside quotes in the JSON; use the exact value from `jira_get_transitions` (e.g. `"to": { "name": "Ready for work" }` → use `"statusName": "Ready for work"`). + +## Identifying issues faster + +| Symptom | Likely cause | Action | +|--------|----------------|--------| +| 404 in error message and URL path contains `/,/statusName/:/` with only part of the status (e.g. `.../Ready/transitions`) | Shell split the `--data` argument; status name was truncated. | Retry with **single-quoted** JSON: `--data '{"key":"KEY","statusName":"Full Status Name"}'`. On PowerShell avoid double quotes around JSON. | +| 404 with a clean-looking URL (correct key and path) | Invalid transition for current state, or wrong status name. | Run `dmtools jira_get_transitions KEY` and use exactly the `to.name` value from a transition. If no transition to that status exists, the workflow does not allow the move from the current state. | +| Tool returns `"result": ""` or no error but ticket status unchanged | Transition may have succeeded (e.g. 204) or failed silently. | Verify with `dmtools jira_get_ticket KEY summary,status`; if status did not change, try single-quoted JSON or check workflow/permissions in JIRA. | + +**Recommended flow:** Before moving, run `jira_get_transitions KEY`, pick the target status from the response, then call `jira_move_to_status` with that exact `statusName` in single-quoted JSON. diff --git a/.cursor/skills/jira-dmtools/commands/xray/SKILL.md b/.cursor/skills/jira-dmtools/commands/xray/SKILL.md new file mode 100644 index 00000000..12fd42ae --- /dev/null +++ b/.cursor/skills/jira-dmtools/commands/xray/SKILL.md @@ -0,0 +1,22 @@ +--- +name: jira-dmtools-xray +description: Xray test and precondition operations. All jira_xray_* tools. Run dmtools in terminal. +--- + +# JIRA Xray + +Use when working with Xray tests or preconditions (search, get details/steps, create precondition, add steps/preconditions). Requires JIRA with Xray. + +**Tools:** jira_xray_search_tickets, jira_xray_get_test_details, jira_xray_get_test_steps, jira_xray_get_preconditions, jira_xray_get_precondition_details, jira_xray_create_precondition, jira_xray_add_precondition_to_test, jira_xray_add_preconditions_to_test, jira_xray_add_test_step, jira_xray_add_test_steps. + +**Examples:** +```bash +dmtools jira_xray_search_tickets --data '{"searchQueryJQL":"project=TP AND issueType=Test"}' +dmtools jira_xray_get_test_details --data '{"testKey":"TP-909"}' +dmtools jira_xray_create_precondition --data '{"summary":"...","project":"TP"}' +dmtools jira_xray_add_test_step --data '{"issueId":"12345","action":"...","result":"..."}' +``` + +Use issue IDs (not keys) for add-precondition and add-step tools when required by the API. + +**Avoiding malformed API URLs:** All Xray commands use `--data`. Ensure the JSON is valid and passed as a **single argument** (correct shell quoting); bad quoting can cause mangled URLs and 404s. diff --git a/.cursor/skills/jira-dmtools/readme-jira-dmtools.md b/.cursor/skills/jira-dmtools/readme-jira-dmtools.md new file mode 100644 index 00000000..26998039 --- /dev/null +++ b/.cursor/skills/jira-dmtools/readme-jira-dmtools.md @@ -0,0 +1,139 @@ +# jira-dmtools skill + +**Location:** `.cursor/skills/jira-dmtools/` +**Purpose:** Perform JIRA operations via the **dmtools** CLI. The skill describes when to use each command, the agent flow (verify → config → run), and how to invoke tools (positional vs `--data` JSON). Subskills under `commands/` hold the exact command and parameters per group. + +--- + +## Skill and script invocation flow + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 1. SCRIPT: Verify JIRA connection (from repo root) │ +└─────────────────────────────────────────────────────────────────────────────┘ + │ + │ python .cursor/skills/jira-dmtools/scripts/verify_jira_connection.py --verify + │ → Checks .dmtools/jira.cfg exists and is valid. + ▼ + ├── OK ──────────────────────────────────────────────────────────────────► + │ │ + └── Fail (no/invalid jira.cfg) │ + │ │ + │ python .cursor/skills/jira-dmtools/scripts/verify_jira_connection.py + │ [--project PROJ] ← optional: cache issue types, statuses, etc. + │ → Reads dmtools.env (JIRA_BASE_PATH, JIRA_EMAIL, JIRA_API_TOKEN). + │ → Calls JIRA, writes .dmtools/jira.cfg. + ▼ │ + Retry step 1 ─────────────────────────────────────────────────────────┘ + │ +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 2. SKILL: Select command and run dmtools │ +└─────────────────────────────────────────────────────────────────────────────┘ + │ + │ • Match user task to "When to use" in Command index (below). + │ • Open commands//SKILL.md for that group. + │ • Run: dmtools [positional args] + │ or dmtools --data '' + ▼ + Output: JSON on stdout (issues, key, result, or error). + On 401/403/timeout: re-run script without --verify (step 1 fail path), then retry dmtools. +``` + +**Script (verify only):** +```bash +# From repo root +python .cursor/skills/jira-dmtools/scripts/verify_jira_connection.py --verify +``` + +**Script (create/refresh config):** +```bash +# From repo root; run when verify fails or after auth/config change +python .cursor/skills/jira-dmtools/scripts/verify_jira_connection.py +python .cursor/skills/jira-dmtools/scripts/verify_jira_connection.py --project PROJ # optional: cache metadata +``` + +**Skill (dmtools):** +```bash +# 1) Pick tool from Command index by task +# 2) Read commands//SKILL.md for exact args +# 3) Run in terminal, e.g.: +dmtools jira_get_ticket PROJ-123 summary,status +dmtools jira_search_by_jql "assignee = currentUser()" "key,summary,status" +dmtools jira_move_to_status --data '{"key":"PROJ-123","statusName":"In Progress"}' +``` + +--- + +## Skill description (summary) + +Use when the user or task involves: searching or listing JIRA tickets; reading ticket details, comments, subtasks, or transitions; creating or updating tickets; adding/reading comments; moving tickets through workflow or assigning; linking issues; managing fix versions, labels, or attachments; or Xray test/precondition operations. + +**Config:** JIRA is driven by **dmtools.env** (or env vars): `JIRA_BASE_PATH`, `JIRA_EMAIL`, `JIRA_API_TOKEN`. Optional: `JIRA_AUTH_TYPE` (default `basic`). + +--- + +## How to invoke + +Full sequence: see **Skill and script invocation flow** above. For running dmtools only: + +- Run `dmtools ` in the terminal. Use **positional** args for simple calls or **`--data ''`** for complex or multi-parameter calls. +- Output is JSON on stdout; parse for `issues`, `key`, or error fields. Some responses wrap payload in `result`—handle both. +- List JIRA tools: `dmtools list | jq '.tools[] | select(.name | startswith("jira_"))'` + +--- + +## Full command list (by group) + +| Group | When to use | Tools | +|-------|-------------|-------| +| **Search** | Search or list tickets by JQL, project, status, or with paging. | `jira_search_by_jql`, `jira_search_by_page`, `jira_search_with_pagination` | +| **Read** | Get one ticket, its subtasks, comments, or available transitions. | `jira_get_ticket`, `jira_get_subtasks`, `jira_get_comments`, `jira_get_transitions` | +| **Create** | Create a ticket (basic, with custom fields, or as subtask under parent). | `jira_create_ticket_basic`, `jira_create_ticket_with_json`, `jira_create_ticket_with_parent` | +| **Update** | Change fields, description, parent, or clear a field on a ticket. | `jira_update_ticket`, `jira_update_field`, `jira_update_description`, `jira_update_all_fields_with_name`, `jira_update_ticket_parent`, `jira_clear_field` | +| **Delete** | Delete a ticket (irreversible). | `jira_delete_ticket` | +| **Workflow** | Move ticket to a status, set resolution, or assign to a user. | `jira_move_to_status`, `jira_move_to_status_with_resolution`, `jira_assign_ticket_to` | +| **Profile** | Resolve account ID by email, get current user or another user's profile. | `jira_get_account_by_email`, `jira_get_my_profile`, `jira_get_user_profile` | +| **Comments** | Add a comment or add only if not already present. | `jira_post_comment`, `jira_post_comment_if_not_exists` | +| **Fix versions** | List, set, add, or remove fix version(s) on a ticket or project. | `jira_get_fix_versions`, `jira_set_fix_version`, `jira_add_fix_version`, `jira_remove_fix_version` | +| **Links & labels** | List link types, link two issues, add label, or set priority. | `jira_get_issue_link_types`, `jira_link_issues`, `jira_add_label`, `jira_set_priority` | +| **Fields data** | Get field definitions or custom field ID(s) by display name for a project. | `jira_get_fields`, `jira_get_field_custom_code`, `jira_get_all_fields_with_name` | +| **Metadata** | Get issue types, statuses, or components for a project. | `jira_get_issue_types`, `jira_get_project_statuses`, `jira_get_components` | +| **Attachments** | Attach a file to a ticket or download an attachment by URL. | `jira_attach_file_to_ticket`, `jira_download_attachment` | +| **Xray** | Xray test/precondition operations. | `jira_xray_search_tickets`, `jira_xray_get_test_details`, `jira_xray_get_test_steps`, `jira_xray_get_preconditions`, `jira_xray_get_precondition_details`, `jira_xray_create_precondition`, `jira_xray_add_precondition_to_test`, `jira_xray_add_preconditions_to_test`, `jira_xray_add_test_step`, `jira_xray_add_test_steps` | +| **Advanced** | Run a custom GET request to the JIRA API with auth. | `jira_execute_request` | + +**Subskills:** For each group, read `commands//SKILL.md` (e.g. `commands/search/SKILL.md`) for the exact command and parameters. + +--- + +## Important notes + +- Prefer **positional** args where possible; use `--data` only when needed and ensure JSON is valid. +- Status and resolution names must match the project workflow; use `jira_get_transitions` for allowed values. +- For assignment: resolve email to accountId with `jira_get_account_by_email`, then pass `accountId` to `jira_assign_ticket_to`. +- **jira_delete_ticket** is irreversible; confirm before running. +- **References:** [dmtools jira-tools.md](https://github.com/IstiN/dmtools/blob/main/dmtools-ai-docs/references/mcp-tools/jira-tools.md); project: `.dmtools/dmtools_commands_jira.md`. + +--- + +## Suggestions to identify issues faster (dmtools tool) + +If you maintain or extend the dmtools CLI, these changes would help agents and users diagnose failures sooner: + +1. **Positional args for workflow moves** + Support e.g. `dmtools jira_move_to_status PROJ-123 "Ready for work"` so status names with spaces do not depend on `--data` JSON quoting (which often breaks on PowerShell/Windows). + +2. **Clearer error differentiation** + When the API returns 404, distinguish in the message: (a) URL malformed (e.g. truncated status in path) → suggest single-quoted JSON or positional args; (b) valid URL but transition not allowed → suggest running `jira_get_transitions` and using an exact `to.name`. + +3. **Explicit success response for transitions** + On 204 No Content from JIRA, return e.g. `{"success": true, "key": "PROJ-123"}` so agents can confirm the move without a follow-up `jira_get_ticket`. + +4. **Optional pre-check** + If `statusName` is provided, optionally validate it against `jira_get_transitions` (or cached transitions) and fail early with "Status X not in available transitions" instead of a generic 404. + +--- + +## Verification + +Command verification is run from repo root via `tmp/jira-dmtools-verify/run-verify.ps1`. Set `JIRA_VERIFY_PROJECT`; optionally `JIRA_VERIFY_TICKET` and `JIRA_VERIFY_EMAIL`. See `tmp/jira-dmtools-verify/README.md` for flow and max-coverage instructions. Results are written to `tmp/jira-dmtools-verify/results.md`. diff --git a/.cursor/skills/jira-dmtools/scripts/verify_jira_connection.py b/.cursor/skills/jira-dmtools/scripts/verify_jira_connection.py new file mode 100644 index 00000000..eb8d51ba --- /dev/null +++ b/.cursor/skills/jira-dmtools/scripts/verify_jira_connection.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python3 +""" +Validate JIRA connection via dmtools and write confirmation to .dmtools/jira.cfg. +Run from repository root (where dmtools.env exists). + +Modes: + Default: Run dmtools --version, list (jira tools), jira_get_my_profile; + optionally --project PROJ to fetch and cache metadata; write jira.cfg on success. + --verify: Only validate that .dmtools/jira.cfg exists and has mandatory keys in correct format (no dmtools calls). +""" + +import argparse +import json +import os +import re +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path + +DMTOOLS_README = "https://github.com/IstiN/dmtools/blob/main/README.md" +JIRA_API_TOKENS = "https://id.atlassian.com/manage-profile/security/api-tokens" +REQUIRED_ENV_VARS = "JIRA_BASE_PATH, JIRA_EMAIL, JIRA_API_TOKEN" +JIRA_CFG_MANDATORY_KEYS = ("validated_at", "jira_base_path", "account_id") +SCRIPT_INVOKE = "python .cursor/skills/jira-dmtools/scripts/verify_jira_connection.py" + + +def find_repo_root() -> Path: + """Return directory containing dmtools.env (current dir or parent walk).""" + path = Path.cwd().resolve() + for _ in range(20): + if (path / "dmtools.env").is_file(): + return path + parent = path.parent + if parent == path: + break + path = parent + return Path.cwd().resolve() + + +def run_dmtools(repo_root: Path, *args: str) -> tuple[int, str, str]: + """Run dmtools with given args; cwd=repo_root. Returns (returncode, stdout, stderr).""" + cmd = ["dmtools"] + list(args) + try: + r = subprocess.run( + cmd, + cwd=repo_root, + capture_output=True, + text=True, + timeout=60, + ) + return r.returncode, r.stdout or "", r.stderr or "" + except FileNotFoundError: + return -1, "", "dmtools not found" + except subprocess.TimeoutExpired: + return -1, "", "dmtools timed out" + + +def err(msg: str, fix: str = "", links: str = "") -> None: + print(msg, file=sys.stderr) + if fix: + print(f"Fix: {fix}", file=sys.stderr) + if links: + print(f"See: {links}", file=sys.stderr) + + +def fail_dmtools_not_found() -> None: + err( + "dmtools is not on PATH or not installed.", + "Install: macOS/Linux/Git Bash: curl -fsSL https://raw.githubusercontent.com/IstiN/dmtools/main/install | bash | Windows: curl -fsSL https://raw.githubusercontent.com/IstiN/dmtools/main/install.bat -o \"%TEMP%\\dmtools-install.bat\" && \"%TEMP%\\dmtools-install.bat\" | Then ensure dmtools is on PATH.", + DMTOOLS_README, + ) + sys.exit(2) + + +def fail_no_jira_config() -> None: + err( + "JIRA tools are missing from dmtools. dmtools.env is not loaded or does not contain JIRA variables.", + f"Create or fix dmtools.env in the project root. Required: {REQUIRED_ENV_VARS}. Optional: JIRA_AUTH_TYPE (Basic or Bearer). Example: JIRA_BASE_PATH=https://your-company.atlassian.net JIRA_EMAIL=your@email.com JIRA_API_TOKEN=.", + f"{DMTOOLS_README} | API token: {JIRA_API_TOKENS}", + ) + sys.exit(3) + + +def fail_auth() -> None: + err( + "JIRA authentication failed (jira_get_my_profile returned an error).", + f"Check dmtools.env in project root: {REQUIRED_ENV_VARS}. Ensure JIRA_BASE_PATH has no trailing slash. Token may be invalid or expired; create a new API token at the link below.", + JIRA_API_TOKENS, + ) + sys.exit(4) + + +def verify_mode(repo_root: Path) -> None: + """Validate .dmtools/jira.cfg exists and has mandatory keys in correct format.""" + cfg_path = repo_root / ".dmtools" / "jira.cfg" + if not cfg_path.is_file(): + err( + f"Missing required file: {cfg_path}", + f"Run from repo root: {SCRIPT_INVOKE} (without --verify) to validate JIRA connection and create jira.cfg.", + DMTOOLS_README, + ) + sys.exit(1) + + try: + data = json.loads(cfg_path.read_text(encoding="utf-8")) + except json.JSONDecodeError as e: + err( + f"Invalid JSON in {cfg_path}: {e}", + f"Re-run: {SCRIPT_INVOKE} to regenerate a valid jira.cfg.", + ) + sys.exit(1) + + for key in JIRA_CFG_MANDATORY_KEYS: + if key not in data: + err( + f"Missing required key in {cfg_path}: {key}", + f"Re-run: {SCRIPT_INVOKE} to refresh jira.cfg.", + ) + sys.exit(1) + + if not isinstance(data.get("jira_base_path"), str) or not data["jira_base_path"].strip(): + err( + f"Invalid format in {cfg_path}: jira_base_path must be a non-empty string.", + f"Re-run: {SCRIPT_INVOKE} to refresh jira.cfg.", + ) + sys.exit(1) + if not isinstance(data.get("account_id"), str) or not data["account_id"].strip(): + err( + f"Invalid format in {cfg_path}: account_id must be a non-empty string.", + f"Re-run: {SCRIPT_INVOKE} to refresh jira.cfg.", + ) + sys.exit(1) + + validated_at = data.get("validated_at") + if not isinstance(validated_at, str) or not validated_at.strip(): + err( + f"Invalid format in {cfg_path}: validated_at must be a non-empty ISO 8601 string.", + f"Re-run: {SCRIPT_INVOKE} to refresh jira.cfg.", + ) + sys.exit(1) + try: + datetime.fromisoformat(validated_at.replace("Z", "+00:00")) + except ValueError: + err( + f"Invalid format in {cfg_path}: validated_at must be parseable as ISO 8601 date.", + f"Re-run: {SCRIPT_INVOKE} to refresh jira.cfg.", + ) + sys.exit(1) + + print("OK: .dmtools/jira.cfg is present and valid.") + sys.exit(0) + + +def extract_account_id(stdout: str) -> str | None: + """Parse accountId from jira_get_my_profile JSON (top-level or result wrapper).""" + try: + data = json.loads(stdout) + except json.JSONDecodeError: + return None + if isinstance(data.get("accountId"), str): + return data["accountId"] + if isinstance(data.get("result"), dict) and isinstance(data["result"].get("accountId"), str): + return data["result"]["accountId"] + return None + + +def has_jira_tools(stdout: str) -> bool: + """Check dmtools list output for any jira_ tool.""" + try: + data = json.loads(stdout) + except json.JSONDecodeError: + return False + tools = data.get("tools") if isinstance(data.get("tools"), list) else data if isinstance(data, list) else [] + return any( + isinstance(t, dict) and str(t.get("name", "")).startswith("jira_") + or isinstance(t, str) and t.startswith("jira_") + for t in tools + ) + + +def full_validation(repo_root: Path, project_key: str | None) -> None: + """Run full connection checks and write .dmtools/jira.cfg on success.""" + code, out, err_out = run_dmtools(repo_root, "--version") + if code != 0: + fail_dmtools_not_found() + + code, out, err_out = run_dmtools(repo_root, "list") + if code != 0 or not has_jira_tools(out): + fail_no_jira_config() + + code, out, err_out = run_dmtools(repo_root, "jira_get_my_profile") + if code != 0: + fail_auth() + account_id = extract_account_id(out) + if not account_id: + err( + "jira_get_my_profile succeeded but response did not contain accountId.", + f"Check dmtools output. Ensure {REQUIRED_ENV_VARS} are set in dmtools.env.", + DMTOOLS_README, + ) + sys.exit(4) + + # Optional: get JIRA_BASE_PATH from dmtools.env for jira.cfg + jira_base_path = "" + env_file = repo_root / "dmtools.env" + if env_file.is_file(): + for line in env_file.read_text(encoding="utf-8").splitlines(): + m = re.match(r"^\s*JIRA_BASE_PATH\s*=\s*(.+)$", line) + if m: + jira_base_path = m.group(1).strip().strip('"') + break + if not jira_base_path: + jira_base_path = os.environ.get("JIRA_BASE_PATH", "").strip() + if not jira_base_path: + jira_base_path = "unknown" + + cfg = { + "validated_at": datetime.now(tz=timezone.utc).isoformat(), + "jira_base_path": jira_base_path, + "account_id": account_id, + } + + # Optional: profile cache + try: + cfg["profile"] = json.loads(out) + except json.JSONDecodeError: + pass + + # Optional: project metadata + if project_key: + projects = cfg.setdefault("projects", {}) + proj = {} + for cmd, key in [ + ("jira_get_issue_types", "issue_types"), + ("jira_get_project_statuses", "statuses"), + ("jira_get_components", "components"), + ("jira_get_fields", "fields"), + ]: + code, out, _ = run_dmtools(repo_root, cmd, project_key) + if code == 0 and out.strip(): + try: + proj[key] = json.loads(out) + except json.JSONDecodeError: + proj[key] = out.strip() + projects[project_key] = proj + + out_dir = repo_root / ".dmtools" + out_dir.mkdir(parents=True, exist_ok=True) + cfg_path = out_dir / "jira.cfg" + cfg_path.write_text(json.dumps(cfg, indent=2), encoding="utf-8") + print(f"OK: JIRA connection validated. Wrote {cfg_path}") + sys.exit(0) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Validate JIRA connection and write .dmtools/jira.cfg, or only verify jira.cfg (--verify)." + ) + parser.add_argument( + "--verify", + action="store_true", + help="Only validate that .dmtools/jira.cfg exists and has mandatory keys; do not call dmtools.", + ) + parser.add_argument( + "--project", + metavar="PROJECT_KEY", + help="Optional project key to fetch and cache issue types, statuses, components, fields into jira.cfg.", + ) + args = parser.parse_args() + + repo_root = find_repo_root() + if args.verify: + verify_mode(repo_root) + full_validation(repo_root, args.project) + + +if __name__ == "__main__": + main()