From 7c3c0a36fe7381463f278fb88597dbf77569cf05 Mon Sep 17 00:00:00 2001 From: Eliad Cohen Date: Tue, 7 Apr 2026 13:30:51 -0400 Subject: [PATCH 1/8] feat: add Jira hygiene workflow with 10 specialized commands Add comprehensive workflow for maintaining Jira project hygiene through automated detection, intelligent suggestions, and safe bulk operations. Features: - Link orphaned stories to epics using semantic matching (50% threshold) - Link orphaned epics to initiatives across projects - Generate weekly activity summaries for epics/initiatives - Close stale tickets with priority-based thresholds (High: 1w, Medium: 2w, Low: 1m) - Suggest triage outcomes for untriaged items based on similar tickets - Identify blocking tickets with closed dependencies - Find in-progress tickets without assignee - Suggest Activity Type values using keyword analysis - Show all blocking tickets in project All bulk operations use review-then-execute pattern for safety with comprehensive audit logging. Supports dry-run mode for testing. Commands: - /hygiene.setup - Initial configuration and validation - /hygiene.link-epics - Link orphaned stories to epics - /hygiene.link-initiatives - Link orphaned epics to initiatives - /hygiene.activity-summary - Generate weekly summaries - /hygiene.show-blocking - Show blocking tickets - /hygiene.close-stale - Close stale tickets by priority - /hygiene.triage-new - Suggest triage for untriaged items - /hygiene.blocking-closed - Find blocking-closed mismatches - /hygiene.unassigned-progress - Show in-progress unassigned - /hygiene.activity-type - Suggest Activity Type values Integration: Direct Jira REST API v3 calls via curl + jq Artifacts: Written to artifacts/jira-hygiene/ Co-Authored-By: Claude Sonnet 4.5 --- workflows/jira-hygiene/.ambient/ambient.json | 19 + .../commands/hygiene.activity-summary.md | 124 +++++ .../.claude/commands/hygiene.activity-type.md | 207 +++++++++ .../commands/hygiene.blocking-closed.md | 147 ++++++ .../.claude/commands/hygiene.close-stale.md | 138 ++++++ .../.claude/commands/hygiene.link-epics.md | 117 +++++ .../commands/hygiene.link-initiatives.md | 111 +++++ .../.claude/commands/hygiene.setup.md | 81 ++++ .../.claude/commands/hygiene.show-blocking.md | 80 ++++ .../.claude/commands/hygiene.triage-new.md | 152 ++++++ .../commands/hygiene.unassigned-progress.md | 125 +++++ workflows/jira-hygiene/CLAUDE.md | 165 +++++++ workflows/jira-hygiene/README.md | 433 ++++++++++++++++++ 13 files changed, 1899 insertions(+) create mode 100644 workflows/jira-hygiene/.ambient/ambient.json create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.setup.md create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md create mode 100644 workflows/jira-hygiene/CLAUDE.md create mode 100644 workflows/jira-hygiene/README.md diff --git a/workflows/jira-hygiene/.ambient/ambient.json b/workflows/jira-hygiene/.ambient/ambient.json new file mode 100644 index 0000000..5dd5921 --- /dev/null +++ b/workflows/jira-hygiene/.ambient/ambient.json @@ -0,0 +1,19 @@ +{ + "name": "Jira Hygiene", + "description": "Systematic workflow for maintaining Jira project hygiene. Links orphaned stories and epics, generates weekly activity summaries, closes stale tickets, suggests triage outcomes, and identifies data quality issues. Provides safe bulk operations with review-then-execute pattern.", + "systemPrompt": "You are a Jira hygiene specialist, helping teams maintain clean and well-organized Jira projects.\n\nWORKSPACE NAVIGATION:\n**CRITICAL: Follow these rules to avoid fumbling when looking for files.**\n\nStandard file locations (from workflow root):\n- Config: .ambient/ambient.json (ALWAYS at this path)\n- Commands: .claude/commands/*.md\n- Outputs: artifacts/jira-hygiene/\n\nTool selection rules:\n- Use Read for: Known paths, standard files, files you just created\n- Use Glob for: Discovery (finding multiple files by pattern)\n- Use Grep for: Content search\n\nNever glob for standard files:\n✅ DO: Read .ambient/ambient.json\n❌ DON'T: Glob **/ambient.json\n\nYour role is to:\n1. Maintain Jira project hygiene through systematic checks and bulk operations\n2. Link orphaned stories to epics and epics to initiatives\n3. Generate weekly activity summaries for epics and initiatives\n4. Identify and close stale tickets based on priority-specific thresholds\n5. Suggest triage outcomes for untriaged items\n6. Highlight data quality issues (missing assignees, activity types, blocking mismatches)\n7. Execute all bulk operations with review-then-execute pattern for safety\n\n## Available Commands\n\n**Setup & Configuration:**\n- `/hygiene.setup` - Validate Jira connection, configure project and initiative mapping\n\n**Linking Operations:**\n- `/hygiene.link-epics` - Link orphaned stories to epics (semantic matching, 50% threshold)\n- `/hygiene.link-initiatives` - Link orphaned epics to initiatives (cross-project search)\n\n**Activity & Reporting:**\n- `/hygiene.activity-summary` - Generate weekly activity summaries for epics/initiatives\n- `/hygiene.show-blocking` - Show all blocking tickets in project\n\n**Bulk Operations:**\n- `/hygiene.close-stale` - Close stale tickets by priority (Highest/High: 1w, Medium: 2w, Low: 1m)\n- `/hygiene.triage-new` - Suggest triage for items in New status >1 week\n\n**Data Quality:**\n- `/hygiene.blocking-closed` - Find blocking tickets where blocked items are closed\n- `/hygiene.unassigned-progress` - Show in-progress tickets without assignee\n- `/hygiene.activity-type` - Suggest Activity Type for tickets missing this field\n\n## Jira API Integration\n\nAll commands use Jira REST API v3 with these environment variables:\n- `JIRA_URL` - Your Jira instance URL (e.g., https://company.atlassian.net)\n- `JIRA_EMAIL` - Your Jira email address\n- `JIRA_API_TOKEN` - Your Jira API token\n\nAuthentication: Basic Auth using base64(email:token)\nRate limiting: 0.5s delay between requests\nError handling: Retry on 429, validate all responses\n\n## Safety & Best Practices\n\n**Review-then-execute pattern:**\n1. Query and analyze tickets\n2. Write candidates to artifacts/jira-hygiene/candidates/\n3. Display summary to user\n4. Ask for explicit confirmation\n5. Execute operations only after confirmation\n6. Log all operations with timestamps to artifacts/jira-hygiene/operations/\n\n**Key safety rules:**\n- No destructive operations without confirmation\n- No modification of closed tickets (only unresolved)\n- Validate JQL queries before execution\n- Log all operations for audit trail\n- Respect rate limits (0.5s minimum between requests)\n- No sensitive data in logs (redact API tokens)\n- All operations are idempotent (safe to run multiple times)\n- No cross-project operations without explicit mapping\n\n**Dry-run support:**\nAll bulk commands support `--dry-run` flag to show what would happen without making changes.\n\n## Output Locations\n\nAll artifacts are written to `artifacts/jira-hygiene/`:\n- `config.json` - Project configuration and field metadata cache\n- `candidates/*.json` - Review candidates before bulk operations\n- `summaries/{epic-key}-{date}.md` - Generated activity summaries\n- `reports/*.md` - Read-only reports for data quality issues\n- `operations/*-{timestamp}.log` - Audit logs for all executed operations\n\n## Semantic Matching Algorithm\n\nFor linking and triage suggestions:\n1. Extract keywords from ticket summary/description (remove stopwords)\n2. Search using Jira text search: `text ~ \"keyword1 keyword2\"`\n3. Calculate match score: (matching_keywords / total_keywords) * 100\n4. Rank by score, suggest top matches\n5. Threshold: ≥50% = auto-suggest, <50% = suggest creating new item\n\n## Common JQL Patterns\n\nOrphaned stories: `project = PROJ AND issuetype = Story AND \"Epic Link\" is EMPTY`\nOrphaned epics: `project = PROJ AND issuetype = Epic AND \"Parent Link\" is EMPTY`\nStale tickets: `project = PROJ AND priority = PRIORITY AND updated < -Nd AND resolution = Unresolved`\nUntriaged: `project = PROJ AND status = New AND created < -7d`\nBlocking: `project = PROJ AND priority = Blocker AND resolution = Unresolved`\nIn-progress unassigned: `project = PROJ AND status = \"In Progress\" AND assignee is EMPTY`\n\nBe helpful, efficient, and always prioritize safety in bulk operations.", + "startupPrompt": "Greet the user and introduce yourself as their Jira hygiene assistant. Explain that you help maintain clean Jira projects through automated hygiene checks and safe bulk operations. Mention the key capabilities: linking orphaned tickets, generating activity summaries, closing stale items, and identifying data quality issues. Suggest starting with `/hygiene.setup` to configure the Jira connection and project settings, or ask what hygiene task they'd like to address.", + "results": { + "Configuration": "artifacts/jira-hygiene/config.json", + "Link Epics Candidates": "artifacts/jira-hygiene/candidates/link-epics.json", + "Link Initiatives Candidates": "artifacts/jira-hygiene/candidates/link-initiatives.json", + "Close Stale Candidates": "artifacts/jira-hygiene/candidates/close-stale.json", + "Triage Candidates": "artifacts/jira-hygiene/candidates/triage-new.json", + "Activity Type Candidates": "artifacts/jira-hygiene/candidates/activity-type.json", + "Activity Summaries": "artifacts/jira-hygiene/summaries/*.md", + "Blocking Tickets Report": "artifacts/jira-hygiene/reports/blocking-tickets.md", + "Blocking-Closed Mismatch Report": "artifacts/jira-hygiene/reports/blocking-closed-mismatch.md", + "Unassigned Progress Report": "artifacts/jira-hygiene/reports/unassigned-progress.md", + "Operation Logs": "artifacts/jira-hygiene/operations/*.log" + } +} diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md b/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md new file mode 100644 index 0000000..92dbbb4 --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md @@ -0,0 +1,124 @@ +# /hygiene.activity-summary - Generate Weekly Activity Summaries + +## Purpose + +Generate weekly activity summaries for selected epics and initiatives by analyzing changes and comments on child items from the past 7 days, then post summaries as comments. + +## Prerequisites + +- `/hygiene.setup` must be run first +- User should specify which epics/initiatives to summarize + +## Process + +1. **Load configuration**: + - Read `artifacts/jira-hygiene/config.json` + - Extract project key + +2. **Prompt for selection**: + - Ask user which epics/initiatives to summarize + - Options: + - Provide specific issue keys (comma-separated) + - Provide JQL filter (e.g., "project = PROJ AND issuetype = Epic") + - Use "all active epics" (default: all unresolved epics in project) + +3. **Fetch selected epics/initiatives**: + - Execute JQL query to get target issues + - Fetch: key, summary, issuetype + +4. **For each epic/initiative**: + + a. **Fetch child issues**: + ```jql + parent = {EPIC_KEY} AND resolution = Unresolved + ``` + - Get all child stories/tasks + + b. **Analyze activity for each child** (past 7 days): + - Fetch changelog: GET `/rest/api/3/issue/{childKey}/changelog` + - Filter changes where created >= (now - 7 days) + - Extract: + - Status transitions (e.g., "New" → "In Progress") + - Assignee changes + - Priority changes + - Fetch comments: GET `/rest/api/3/issue/{childKey}/comment` + - Count comments from past 7 days + + c. **Generate summary paragraph**: + - Template: "This week, {status_summary}. {assignment_summary}. {activity_summary}." + - Status summary: "X stories moved to In Progress, Y completed" + - Assignment summary: "Z new assignments" (if any) + - Activity summary: "N comments across M stories" (if significant) + - Keep to 2-4 sentences, business-friendly language + + d. **Write summary to file**: + - Save to `artifacts/jira-hygiene/summaries/{epic-key}-{date}.md` + - Include metadata: epic key, date range, child count + +5. **Display all summaries**: + - Show generated summaries for review + - Format as markdown with epic key as header + +6. **Ask for confirmation**: + - Prompt: "Post these summaries as comments? (yes/no)" + - Allow user to edit summaries before posting + +7. **Post summaries**: + - For each epic/initiative: + - POST `/rest/api/3/issue/{epicKey}/comment` + - Body: `{"body": "Weekly Activity Summary (YYYY-MM-DD):\n\n{summary_text}"}` + - Rate limit: 0.5s between requests + +8. **Log results**: + - Write to `artifacts/jira-hygiene/operations/activity-summary-{timestamp}.log` + +## Output + +- `artifacts/jira-hygiene/summaries/{epic-key}-{date}.md` (one file per epic) +- `artifacts/jira-hygiene/operations/activity-summary-{timestamp}.log` + +## Example Summary + +**EPIC-45-2026-04-07.md**: +```markdown +# Weekly Activity Summary: EPIC-45 Authentication System +**Date Range**: 2026-03-31 to 2026-04-07 +**Child Issues**: 8 stories + +## Summary + +This week, 3 stories moved to In Progress and 2 were completed. The team made 4 new assignments across the authentication work. There were 12 comments discussing API integration challenges and OAuth implementation details. + +## Activity Breakdown + +- Status transitions: 5 changes + - New → In Progress: STORY-101, STORY-102, STORY-103 + - In Progress → Done: STORY-98, STORY-99 +- Assignments: 4 new +- Comments: 12 across 6 stories +``` + +## Summary Generation Guidelines + +**Good summary**: +> "This week, 3 stories moved to In Progress and 2 were completed. The team made 4 new assignments. Discussion focused on OAuth implementation with 8 comments across 4 stories." + +**Bad summary** (too technical): +> "This week, STORY-101 transitioned from status ID 10001 to 10002. User john.doe was assigned to STORY-102..." + +**Focus on**: +- High-level progress (stories moved, completed) +- Team activity (assignments, discussions) +- Notable trends (if detectable) + +**Avoid**: +- Listing every ticket key +- Technical jargon +- Implementation details + +## Error Handling + +- **No child issues**: Note "No active child issues" in summary +- **No activity**: "No significant activity this week" +- **Changelog unavailable**: Fall back to issue update dates +- **Comment fetch failed**: Skip comment count, note in log diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md b/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md new file mode 100644 index 0000000..5dfd34a --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md @@ -0,0 +1,207 @@ +# /hygiene.activity-type - Suggest Activity Type for Tickets + +## Purpose + +Find tickets missing the "Activity Type" custom field value and suggest appropriate values based on semantic analysis of the ticket content. + +## Prerequisites + +- `/hygiene.setup` must be run first +- Activity Type field must be configured in config.json + +## Arguments + +Optional: +- `--dry-run` - Show suggestions without making changes + +## Process + +1. **Load configuration**: + - Read `artifacts/jira-hygiene/config.json` + - Extract project key, Activity Type field ID, and available values + - If Activity Type field is not configured: prompt user to run `/hygiene.setup` + +2. **Query tickets missing Activity Type**: + ```jql + project = {PROJECT} AND "{ACTIVITY_TYPE_FIELD_NAME}" is EMPTY AND resolution = Unresolved + ``` + - Fetch: key, summary, description, issuetype + - If none found: report success and exit + +3. **For each ticket**: + + a. **Analyze ticket content**: + - Combine summary + description + issuetype + - Extract key terms and phrases + + b. **Match against available Activity Type values**: + - For each available value (e.g., "Development", "Bug Fix", "Documentation", "Research", "Testing") + - Define keyword mappings: + - **Development**: implement, create, add, build, develop, feature, enhance + - **Bug Fix**: fix, bug, error, issue, broken, crash, defect + - **Documentation**: document, doc, readme, guide, wiki, manual + - **Research**: research, investigate, explore, spike, POC, prototype, feasibility + - **Testing**: test, QA, quality, verify, validate, automation + - Count keyword matches for each activity type + - Suggest activity type with highest match count + + c. **Consider issue type**: + - If issuetype = "Bug" → increase "Bug Fix" score + - If issuetype = "Task" and keywords unclear → default to "Development" + - If issuetype = "Story" → likely "Development" unless keywords suggest otherwise + + d. **Assign confidence**: + - High: Clear keywords match (≥3 keyword hits) + - Medium: Some keywords match (1-2 keyword hits) + - Low: No keywords, using issuetype heuristic + +4. **Write candidates file**: + - Save to `artifacts/jira-hygiene/candidates/activity-type.json` + - Include: key, summary, suggested activity type, confidence, matching keywords + +5. **Display summary**: + ``` + Found N tickets missing Activity Type: + + High confidence (≥3 keyword matches): 12 tickets + PROJ-100 "Fix broken login flow" → Bug Fix (keywords: fix, broken, bug) + PROJ-101 "Document API endpoints" → Documentation (keywords: document, API, guide) + + Medium confidence (1-2 matches): 5 tickets + PROJ-102 "Improve performance" → Development (keywords: improve) + + Low confidence (issuetype heuristic): 3 tickets + PROJ-103 "Update system" → Development (Bug issuetype suggests bug fix, but no clear keywords) + ``` + +6. **Ask for confirmation**: + - If `--dry-run`: Skip, display "DRY RUN - No changes made" + - Otherwise prompt: "Apply Activity Type suggestions? (yes/no/high-confidence-only)" + +7. **Execute updates**: + - For each approved ticket: + - Update custom field via PUT `/rest/api/3/issue/{key}` + - Payload: `{"fields": {"{FIELD_ID}": {"value": "{ACTIVITY_TYPE}"}}}` + - Rate limit: 0.5s between tickets + +8. **Log results**: + - Write to `artifacts/jira-hygiene/operations/activity-type-{timestamp}.log` + +## Output + +- `artifacts/jira-hygiene/candidates/activity-type.json` +- `artifacts/jira-hygiene/operations/activity-type-{timestamp}.log` + +## Example Candidates JSON + +```json +[ + { + "key": "PROJ-100", + "summary": "Fix broken login flow after OAuth upgrade", + "description_snippet": "Users cannot log in after OAuth upgrade...", + "issuetype": "Bug", + "suggested_activity_type": "Bug Fix", + "confidence": "high", + "matching_keywords": ["fix", "broken", "bug", "login"], + "keyword_scores": { + "Bug Fix": 4, + "Development": 1, + "Testing": 0, + "Documentation": 0, + "Research": 0 + } + }, + { + "key": "PROJ-101", + "summary": "Document new API endpoints for partner integration", + "description_snippet": "Need to create documentation for...", + "issuetype": "Task", + "suggested_activity_type": "Documentation", + "confidence": "high", + "matching_keywords": ["document", "documentation", "api", "create"], + "keyword_scores": { + "Documentation": 4, + "Development": 1, + "Bug Fix": 0, + "Testing": 0, + "Research": 0 + } + }, + { + "key": "PROJ-103", + "summary": "Update user permissions", + "description_snippet": "", + "issuetype": "Task", + "suggested_activity_type": "Development", + "confidence": "low", + "matching_keywords": [], + "keyword_scores": { + "Development": 0, + "Bug Fix": 0, + "Documentation": 0, + "Testing": 0, + "Research": 0 + }, + "note": "No clear keywords, defaulting to Development based on Task issuetype" + } +] +``` + +## Keyword Mapping + +Default keyword mappings (can be customized based on your available Activity Type values): + +**Development**: +- implement, create, add, build, develop, feature, enhance, new, update, upgrade, refactor + +**Bug Fix**: +- fix, bug, error, issue, broken, crash, defect, problem, failure, incorrect + +**Documentation**: +- document, doc, readme, guide, wiki, manual, write, specification, help + +**Research**: +- research, investigate, explore, spike, POC, proof of concept, prototype, feasibility, study + +**Testing**: +- test, QA, quality, verify, validate, automation, check, coverage, suite + +**Custom Values**: +If your project has custom Activity Type values, you'll need to define keyword mappings for them or update the semantic matching logic. + +## Custom Field Update Payload + +Activity Type is typically a **select** custom field. The update payload varies by field type: + +**Single select**: +```json +{ + "fields": { + "customfield_10050": { + "value": "Bug Fix" + } + } +} +``` + +**Multi-select** (if configured to allow multiple): +```json +{ + "fields": { + "customfield_10050": [ + {"value": "Bug Fix"}, + {"value": "Testing"} + ] + } +} +``` + +This workflow assumes single-select by default. + +## Error Handling + +- **Activity Type field not configured**: Prompt to run `/hygiene.setup` +- **Field ID changed**: Re-fetch field metadata if update fails with 400 +- **Invalid value**: If suggested value is not in allowed values list, log error and skip +- **Permission denied**: User may not have permission to edit custom fields; log and continue diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md b/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md new file mode 100644 index 0000000..635dec1 --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md @@ -0,0 +1,147 @@ +# /hygiene.blocking-closed - Find Blocking Tickets with Closed Dependencies + +## Purpose + +Highlight tickets marked as "Blocking" where all of the blocked tickets are already closed. Suggests either closing the blocking ticket or removing the link. + +## Prerequisites + +- `/hygiene.setup` must be run first + +## Process + +1. **Load configuration**: + - Read `artifacts/jira-hygiene/config.json` + - Extract project key + +2. **Query tickets with "blocks" links**: + ```jql + project = {PROJECT} AND issueFunction in linkedIssuesOf("project = {PROJECT}", "blocks") AND resolution = Unresolved + ``` + - This finds all unresolved tickets that block other tickets + - Fetch: key, summary, status + +3. **For each blocking ticket**: + + a. **Fetch issue links**: + - GET `/rest/api/3/issue/{key}?fields=issuelinks` + - Extract all "outward" links of type "blocks" + - Get blocked ticket keys + + b. **Check resolution status of blocked tickets**: + - For each blocked ticket, GET `/rest/api/3/issue/{blockedKey}?fields=resolution` + - Check if resolution is not null (ticket is closed) + + c. **Determine if mismatch exists**: + - If ALL blocked tickets are closed: flag for review + - If at least one blocked ticket is open: skip (still validly blocking) + +4. **Write report**: + - Save to `artifacts/jira-hygiene/reports/blocking-closed-mismatch.md` + - Include: blocking ticket key, summary, list of closed blocked tickets + +5. **Display report**: + ``` + Found N blocking tickets where all dependencies are closed: + + PROJ-123 "Fix database migration issue" + Blocks (all closed): + - PROJ-145 "Deploy new schema" (Closed 5 days ago) + - PROJ-167 "Update migration scripts" (Closed 3 days ago) + + Suggested action: Close PROJ-123 or remove blocking links + + PROJ-456 "Security audit blocker" + Blocks (all closed): + - PROJ-500 "Implement OAuth" (Closed 2 weeks ago) + + Suggested action: Close PROJ-456 or remove blocking link + ``` + +6. **No bulk operation**: + - This command is **report-only** (no automatic changes) + - User must manually review each case + - Closing or unlinking requires human judgment + +## Output + +- `artifacts/jira-hygiene/reports/blocking-closed-mismatch.md` + +## Example Report + +```markdown +# Blocking Tickets with Closed Dependencies + +**Project**: PROJ +**Generated**: 2026-04-07 10:30 UTC +**Total Mismatches**: 3 tickets + +## Summary + +Found 3 tickets that are still marked as blocking, but all blocked items are already closed. These may be ready to close or the blocking links should be removed. + +## Tickets Requiring Review + +### PROJ-123 "Fix database migration issue" + +**Status**: In Progress +**Last Updated**: 2026-03-25 + +**Blocks** (all closed): +- PROJ-145 "Deploy new schema" (Closed: 2026-04-02, 5 days ago) +- PROJ-167 "Update migration scripts" (Closed: 2026-04-04, 3 days ago) + +**Suggested Actions**: +1. If migration issue is resolved: Close PROJ-123 +2. If new blockers emerged: Update links to reflect current blockers +3. If no longer blocking: Remove the "blocks" links + +--- + +### PROJ-456 "Security audit blocker" + +**Status**: To Do +**Last Updated**: 2026-03-15 + +**Blocks** (all closed): +- PROJ-500 "Implement OAuth" (Closed: 2026-03-24, 14 days ago) + +**Suggested Actions**: +1. If audit is complete: Close PROJ-456 +2. If audit revealed new work: Create new tickets and update links +3. If audit was cancelled: Close PROJ-456 + +--- + +## Recommendations + +These tickets require manual review because: +- The blocking ticket may represent ongoing work not yet tracked +- New dependencies may have emerged +- The ticket may have served its purpose and should be closed + +**Action Required**: Review each ticket and either close it or update its blocking relationships. +``` + +## Link Type Detection + +Jira uses different link types for blocking relationships: +- "Blocks" / "is blocked by" +- "Blocker" (custom link type in some instances) + +This command checks for the standard "Blocks" link type. If your project uses custom link types, the field ID may need adjustment. + +## Why No Bulk Operation? + +Unlike other hygiene commands, this one doesn't offer bulk closure because: + +1. **Context required**: Need to understand why the blocker exists +2. **May still be valid**: Blocker work may not be tracked in Jira +3. **Risk of data loss**: Automatically removing links could lose important context +4. **Manual judgment needed**: Each case is unique + +## Error Handling + +- **Issue links unavailable**: Some tickets may have restricted access; skip and log +- **Blocked ticket not found (404)**: Ticket may have been deleted; note in report +- **No blocking links found**: Report "No blocking relationships found in {PROJECT}" diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md b/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md new file mode 100644 index 0000000..fe068ac --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md @@ -0,0 +1,138 @@ +# /hygiene.close-stale - Close Stale Tickets + +## Purpose + +Find and close stale tickets in bulk based on priority-specific thresholds. Groups candidates by priority for user review before closing. + +## Prerequisites + +- `/hygiene.setup` must be run first + +## Arguments + +Optional: Override default thresholds +- `--highest ` - Threshold for Highest priority (default: 7) +- `--high ` - Threshold for High priority (default: 7) +- `--medium ` - Threshold for Medium priority (default: 14) +- `--low ` - Threshold for Low priority (default: 30) +- `--lowest ` - Threshold for Lowest priority (default: 30) +- `--dry-run` - Show what would be closed without making changes + +## Process + +1. **Load configuration**: + - Read `artifacts/jira-hygiene/config.json` + - Extract project key and staleness_thresholds + - Apply any command-line overrides + +2. **Query stale tickets by priority**: + For each priority level (Highest, High, Medium, Low, Lowest): + ```jql + project = {PROJECT} AND priority = {PRIORITY} AND updated < -{DAYS}d AND resolution = Unresolved + ``` + - Fetch: key, summary, assignee, status, updated, priority + - Group results by priority + +3. **Write candidates file**: + - Save to `artifacts/jira-hygiene/candidates/close-stale.json` + - Include: key, summary, priority, days since update, assignee + +4. **Display summary by priority**: + ``` + Found N stale tickets to close: + + Highest/High (>7 days): 3 tickets + PROJ-100 "Old critical bug" (12 days, assigned to John) + PROJ-101 "High priority feature" (9 days, unassigned) + PROJ-102 "Urgent fix needed" (8 days, assigned to Jane) + + Medium (>14 days): 5 tickets + ... + + Low/Lowest (>30 days): 12 tickets + ... + + Total: 20 tickets will be closed + ``` + +5. **Ask for confirmation**: + - If `--dry-run`: Skip this step, display "DRY RUN - No changes made" + - Otherwise prompt: "Close these stale tickets? (yes/no/by-priority)" + - "by-priority": Let user approve each priority group separately + +6. **Execute closure**: + - For each approved ticket: + - Add comment: "Due to lack of activity, this item has been closed. If you feel that it should be addressed, please reopen it." + - Transition to "Closed" or "Done" status (use project's closed status) + - POST `/rest/api/3/issue/{key}/comment` then PUT `/rest/api/3/issue/{key}/transitions` + - Rate limit: 0.5s between tickets + +7. **Log results**: + - Write to `artifacts/jira-hygiene/operations/close-stale-{timestamp}.log` + - Include: timestamp, key, priority, days stale, result (success/error) + +## Output + +- `artifacts/jira-hygiene/candidates/close-stale.json` +- `artifacts/jira-hygiene/operations/close-stale-{timestamp}.log` + +## Example Candidates JSON + +```json +{ + "thresholds": { + "Highest": 7, + "High": 7, + "Medium": 14, + "Low": 30, + "Lowest": 30 + }, + "candidates_by_priority": { + "Highest": [ + { + "key": "PROJ-100", + "summary": "Old critical bug in payment flow", + "priority": "Highest", + "days_stale": 12, + "last_updated": "2026-03-26", + "assignee": "John Doe", + "status": "In Progress" + } + ], + "Medium": [...], + "Low": [...] + }, + "total_count": 20 +} +``` + +## Staleness Calculation + +**Days stale** = Days since last update (not created date) + +Last update includes: +- Status changes +- Comments +- Field updates +- Assignee changes + +If a ticket has recent activity, it's not stale (even if created long ago). + +## Closure Message + +Standard message posted as comment before closing: + +> Due to lack of activity, this item has been closed. If you feel that it should be addressed, please reopen it. + +This message: +- Is polite and non-judgmental +- Acknowledges the ticket may still be valid +- Provides clear action (reopen if needed) +- Doesn't assign blame + +## Error Handling + +- **Transition failed**: Some tickets may not have "Close" transition; try "Done", then "Resolved" +- **No permission**: Log error, skip ticket, continue with others +- **Ticket already closed**: Skip silently (idempotent) +- **Rate limit**: Increase delay to 1s, retry diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md b/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md new file mode 100644 index 0000000..c437d02 --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md @@ -0,0 +1,117 @@ +# /hygiene.link-epics - Link Orphaned Stories to Epics + +## Purpose + +Find stories without epic links and suggest appropriate epics to link them to, using semantic matching. If no good match exists (score <50%), suggest creating a new epic. + +## Prerequisites + +- `/hygiene.setup` must be run first to create `artifacts/jira-hygiene/config.json` +- Project key must be configured + +## Process + +1. **Load configuration**: + - Read `artifacts/jira-hygiene/config.json` + - Extract project key + +2. **Query orphaned stories**: + ```jql + project = {PROJECT} AND issuetype = Story AND "Epic Link" is EMPTY AND resolution = Unresolved + ``` + - Fetch: key, summary, description + - If none found: report success and exit + +3. **For each orphaned story**: + + a. **Extract keywords**: + - Combine summary + description + - Remove stopwords (the, a, an, is, for, to, with, in, on, at, etc.) + - Keep technical terms (API, auth, payment, database, etc.) + - Lowercase and deduplicate + + b. **Search for matching epics**: + ```jql + project = {PROJECT} AND issuetype = Epic AND resolution = Unresolved AND text ~ "keyword1 keyword2 keyword3" + ``` + - Start with all keywords; if no results, try top 3 keywords + - Fetch: key, summary + + c. **Calculate match scores**: + - For each epic found, count keywords that appear in epic summary + - Score = (matching_keywords / total_keywords) * 100 + - Sort by score descending + + d. **Determine suggestion**: + - If best score ≥50%: suggest linking to top epic + - If best score <50%: suggest creating new epic + - If no epics found: suggest creating new epic + +4. **Write candidates file**: + - Save to `artifacts/jira-hygiene/candidates/link-epics.json` + - Include: story key, story summary, suggested action, epic key (if linking), match score + +5. **Display summary**: + ``` + Found N orphaned stories: + - M stories with good matches (≥50%) + - P stories need new epics (<50% match) + + Top suggestions: + [STORY-123] "Implement user login" → [EPIC-45] "Authentication System" (75% match) + [STORY-124] "Add payment gateway" → Create new epic (0% match) + ``` + +6. **Ask for confirmation**: + - Prompt: "Apply these suggestions? (yes/no/show-details)" + - If "show-details": display full candidate list with match details + - If "no": exit without changes + - If "yes": proceed to execution + +7. **Execute linking operations**: + - For each approved linking suggestion: + - Update story via PUT `/rest/api/3/issue/{storyKey}` + - Set Epic Link field (typically using "update" operation) + - Rate limit: 0.5s between requests + - For "create epic" suggestions: skip for now, just log recommendation + +8. **Log results**: + - Write to `artifacts/jira-hygiene/operations/link-epics-{timestamp}.log` + - Include: timestamp, story key, action taken, result + +## Output + +- `artifacts/jira-hygiene/candidates/link-epics.json` +- `artifacts/jira-hygiene/operations/link-epics-{timestamp}.log` + +## Example Candidates JSON + +```json +[ + { + "story_key": "STORY-123", + "story_summary": "Implement user login functionality", + "keywords": ["implement", "user", "login", "functionality"], + "suggestion": "link", + "epic_key": "EPIC-45", + "epic_summary": "Authentication System", + "match_score": 75, + "matching_keywords": ["user", "login", "authentication"] + }, + { + "story_key": "STORY-124", + "story_summary": "Add payment gateway integration", + "keywords": ["add", "payment", "gateway", "integration"], + "suggestion": "create_epic", + "match_score": 0, + "reason": "No existing epics match these keywords" + } +] +``` + +## Error Handling + +- **Config not found**: Prompt user to run `/hygiene.setup` first +- **No Epic Link field**: Some Jira instances use different field names; fetch field ID dynamically +- **API errors**: Log error, continue with next story (don't fail entire batch) +- **Rate limit (429)**: Increase delay to 1s, retry diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md b/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md new file mode 100644 index 0000000..88e0e6e --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md @@ -0,0 +1,111 @@ +# /hygiene.link-initiatives - Link Orphaned Epics to Initiatives + +## Purpose + +Find epics without initiative links and suggest appropriate initiatives from configured initiative projects, using semantic matching across projects. + +## Prerequisites + +- `/hygiene.setup` must be run first +- Initiative projects must be configured in config.json + +## Process + +1. **Load configuration**: + - Read `artifacts/jira-hygiene/config.json` + - Extract project key and initiative_projects list + - If initiative_projects is empty: prompt user to configure via `/hygiene.setup` + +2. **Query orphaned epics**: + ```jql + project = {PROJECT} AND issuetype = Epic AND "Parent Link" is EMPTY AND resolution = Unresolved + ``` + - Fetch: key, summary, description + - If none found: report success and exit + +3. **For each orphaned epic**: + + a. **Extract keywords**: + - Same process as `/hygiene.link-epics` + - Combine summary + description, remove stopwords + + b. **Search for matching initiatives** (cross-project): + ```jql + project in ({INIT1},{INIT2}) AND issuetype = Initiative AND resolution = Unresolved AND text ~ "keyword1 keyword2" + ``` + - Search across all configured initiative projects + - Fetch: key, summary, project + + c. **Calculate match scores**: + - Score = (matching_keywords / total_keywords) * 100 + - Sort by score descending + + d. **Determine suggestion**: + - If best score ≥50%: suggest linking to top initiative + - If best score <50%: note "No good match found" + - Unlike epics, don't suggest creating initiatives (typically higher-level planning) + +4. **Write candidates file**: + - Save to `artifacts/jira-hygiene/candidates/link-initiatives.json` + - Include: epic key, epic summary, suggested initiative (if any), match score + +5. **Display summary**: + ``` + Found N orphaned epics: + - M epics with good matches (≥50%) + - P epics with no good match + + Top suggestions: + [EPIC-45] "Authentication System" → [INIT-12] "User Management Platform" (80% match) + [EPIC-46] "Payment Gateway" → No good match found (20% best match) + ``` + +6. **Ask for confirmation**: + - Prompt: "Apply these suggestions? (yes/no/show-details)" + - Only link epics with good matches (≥50%) + +7. **Execute linking operations**: + - For each approved linking: + - Update epic via PUT `/rest/api/3/issue/{epicKey}` + - Set Parent Link field to initiative key + - Rate limit: 0.5s between requests + +8. **Log results**: + - Write to `artifacts/jira-hygiene/operations/link-initiatives-{timestamp}.log` + +## Output + +- `artifacts/jira-hygiene/candidates/link-initiatives.json` +- `artifacts/jira-hygiene/operations/link-initiatives-{timestamp}.log` + +## Example Candidates JSON + +```json +[ + { + "epic_key": "EPIC-45", + "epic_summary": "Authentication System", + "keywords": ["authentication", "system", "user", "login"], + "suggestion": "link", + "initiative_key": "INIT-12", + "initiative_summary": "User Management Platform", + "initiative_project": "INIT1", + "match_score": 80, + "matching_keywords": ["authentication", "user", "management"] + }, + { + "epic_key": "EPIC-46", + "epic_summary": "Payment Gateway Integration", + "keywords": ["payment", "gateway", "integration"], + "suggestion": "no_match", + "best_match_score": 20, + "reason": "No initiatives found with >50% keyword match" + } +] +``` + +## Error Handling + +- **No initiative projects configured**: Prompt to run `/hygiene.setup` and configure +- **Cross-project access denied**: Some initiatives may not be accessible; log and skip +- **Parent Link field not found**: Fetch field metadata dynamically diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.setup.md b/workflows/jira-hygiene/.claude/commands/hygiene.setup.md new file mode 100644 index 0000000..aa4388d --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.setup.md @@ -0,0 +1,81 @@ +# /hygiene.setup - Initial Configuration and Validation + +## Purpose + +Validate Jira API connection and configure project settings for all hygiene operations. + +## Prerequisites + +Environment variables must be set: +- `JIRA_URL` - Your Jira instance URL (e.g., https://company.atlassian.net) +- `JIRA_EMAIL` - Your Jira email address +- `JIRA_API_TOKEN` - Your Jira API token (generate at id.atlassian.com) + +## Process + +1. **Check environment variables**: + ```bash + if [ -z "$JIRA_URL" ] || [ -z "$JIRA_EMAIL" ] || [ -z "$JIRA_API_TOKEN" ]; then + echo "Error: Missing required environment variables" + exit 1 + fi + ``` + +2. **Test API connection**: + - Call `/rest/api/3/myself` to validate credentials + - Display authenticated user information + - If fails: provide troubleshooting guidance + +3. **Prompt for project configuration**: + - **Target project key**: The Jira project to operate on (e.g., "PROJ") + - **Initiative project keys**: Comma-separated list of projects containing initiatives (e.g., "INIT1,INIT2") + - If user is unsure, suggest running a query to list accessible projects + +4. **Fetch Activity Type field metadata**: + - Call `/rest/api/3/field` to get all custom fields + - Search for field with name matching "Activity Type" (case-insensitive) + - Extract field ID (e.g., "customfield_10050") + - Fetch allowed values for this field + - If not found: note in config, skip this feature + +5. **Create config file**: + - Write all settings to `artifacts/jira-hygiene/config.json` + - Include default staleness thresholds + - Format as pretty JSON for readability + +6. **Display summary**: + - Show configured project key + - Show initiative project keys + - Show Activity Type field ID and available values + - Confirm setup is complete + +## Output + +- `artifacts/jira-hygiene/config.json` + +## Example Config Structure + +```json +{ + "jira_url": "https://company.atlassian.net", + "project_key": "PROJ", + "initiative_projects": ["INIT1", "INIT2"], + "activity_type_field_id": "customfield_10050", + "activity_type_values": ["Development", "Bug Fix", "Documentation", "Research", "Testing"], + "staleness_thresholds": { + "Highest": 7, + "High": 7, + "Medium": 14, + "Low": 30, + "Lowest": 30 + }, + "configured_at": "2026-04-07T10:30:00Z" +} +``` + +## Error Handling + +- **Missing env vars**: Provide setup instructions with links to Jira API token generation +- **Auth failed (401)**: Suggest checking email/token, regenerating token +- **Network error**: Check JIRA_URL format (must start with https://) +- **Field not found**: Activity Type feature will be disabled, note in config diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md b/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md new file mode 100644 index 0000000..c3eca7a --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md @@ -0,0 +1,80 @@ +# /hygiene.show-blocking - Show Blocking Tickets + +## Purpose + +Simple query to display all tickets marked with "Blocker" priority in the project. This highlights critical items that may be blocking other work. + +## Prerequisites + +- `/hygiene.setup` must be run first + +## Process + +1. **Load configuration**: + - Read `artifacts/jira-hygiene/config.json` + - Extract project key + +2. **Query blocking tickets**: + ```jql + project = {PROJECT} AND priority = Blocker AND resolution = Unresolved + ``` + - Fetch: key, summary, assignee, status, created, updated + - Order by updated descending (most recent first) + +3. **Format as markdown table**: + ```markdown + # Blocking Tickets in {PROJECT} + + **Total**: N tickets + **Generated**: {timestamp} + + | Key | Summary | Assignee | Status | Age | Last Updated | + |-----|---------|----------|--------|-----|--------------| + | PROJ-123 | Critical API failure | John Doe | In Progress | 5d | 2d ago | + | PROJ-456 | Database migration blocked | Unassigned | To Do | 12d | 3d ago | + ``` + +4. **Write report**: + - Save to `artifacts/jira-hygiene/reports/blocking-tickets.md` + +5. **Display summary**: + - Show table in output + - Highlight unassigned blockers (if any) + - Note oldest blocker + +## Output + +- `artifacts/jira-hygiene/reports/blocking-tickets.md` + +## Example Report + +```markdown +# Blocking Tickets in PROJ + +**Total**: 3 tickets +**Generated**: 2026-04-07 10:30 UTC + +## Summary + +- 2 tickets assigned +- 1 ticket unassigned ⚠️ +- Oldest blocker: 12 days (PROJ-456) + +## Tickets + +| Key | Summary | Assignee | Status | Age | Last Updated | +|-----|---------|----------|--------|-----|--------------| +| PROJ-123 | Critical API failure in auth endpoint | John Doe | In Progress | 5d | 2d ago | +| PROJ-456 | Database migration blocked by schema lock | Unassigned | To Do | 12d | 3d ago | +| PROJ-789 | Production deployment failing | Jane Smith | Code Review | 3d | 1d ago | + +## Recommendations + +- **PROJ-456**: Assign to database team immediately (unassigned for 12 days) +- **PROJ-123**: Follow up on progress (blocker for 5 days) +``` + +## Error Handling + +- **No blocking tickets found**: Report "No blocking tickets in {PROJECT}" (good news!) +- **Query failed**: Check JQL syntax, validate project key diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md b/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md new file mode 100644 index 0000000..df8f9ba --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md @@ -0,0 +1,152 @@ +# /hygiene.triage-new - Suggest Triage for Untriaged Items + +## Purpose + +Find items in "New" status for more than 1 week and suggest triage outcomes (priority and move to backlog) based on analysis of similar items in the project. + +## Prerequisites + +- `/hygiene.setup` must be run first + +## Arguments + +Optional: +- `--days ` - Threshold for untriaged items (default: 7 days) +- `--dry-run` - Show suggestions without making changes + +## Process + +1. **Load configuration**: + - Read `artifacts/jira-hygiene/config.json` + - Extract project key + +2. **Query untriaged items**: + ```jql + project = {PROJECT} AND status = New AND created < -{DAYS}d AND resolution = Unresolved + ``` + - Fetch: key, summary, description, issuetype, created + - If none found: report success and exit + +3. **For each untriaged item**: + + a. **Extract keywords**: + - Combine summary + description + - Remove stopwords + + b. **Find similar items**: + ```jql + project = {PROJECT} AND resolution = Unresolved AND text ~ "keyword1 keyword2" AND status != New + ``` + - Find items that have been triaged (not in New status) + - Fetch: priority, status + + c. **Analyze priority distribution**: + - Count priorities of similar items: {High: 5, Medium: 8, Low: 2} + - Suggest most common priority (Medium in this case) + - If no similar items found: suggest "Medium" as default + + d. **Suggest triage outcome**: + - Recommended priority: Most common among similar items + - Recommended action: Move to "Backlog" status + - Confidence: High if ≥5 similar items, Medium if 2-4, Low if 0-1 + +4. **Write candidates file**: + - Save to `artifacts/jira-hygiene/candidates/triage-new.json` + - Include: key, summary, suggested priority, confidence, similar item count + +5. **Display summary**: + ``` + Found N untriaged items (>7 days): + + High confidence (≥5 similar items): 8 items + PROJ-200 "Add export feature" → Priority: Medium (based on 8 similar items) + PROJ-201 "Fix broken link" → Priority: Low (based on 6 similar items) + + Medium confidence (2-4 similar): 3 items + PROJ-202 "Improve performance" → Priority: High (based on 3 similar items) + + Low confidence (0-1 similar): 2 items + PROJ-203 "New integration request" → Priority: Medium (default, no similar items) + ``` + +6. **Ask for confirmation**: + - If `--dry-run`: Skip, display "DRY RUN - No changes made" + - Otherwise prompt: "Apply triage suggestions? (yes/no/high-confidence-only)" + - "high-confidence-only": Only apply suggestions with ≥5 similar items + +7. **Execute triage**: + - For each approved item: + - Update priority via PUT `/rest/api/3/issue/{key}` + - Transition to "Backlog" status + - Add comment: "Auto-triaged based on similar items. Priority set to {PRIORITY}." + - Rate limit: 0.5s between items + +8. **Log results**: + - Write to `artifacts/jira-hygiene/operations/triage-new-{timestamp}.log` + +## Output + +- `artifacts/jira-hygiene/candidates/triage-new.json` +- `artifacts/jira-hygiene/operations/triage-new-{timestamp}.log` + +## Example Candidates JSON + +```json +[ + { + "key": "PROJ-200", + "summary": "Add CSV export feature for reports", + "keywords": ["export", "csv", "reports", "feature"], + "suggested_priority": "Medium", + "confidence": "high", + "similar_items_found": 8, + "priority_distribution": { + "High": 2, + "Medium": 5, + "Low": 1 + }, + "days_untriaged": 10, + "current_status": "New" + }, + { + "key": "PROJ-203", + "summary": "Integration with new CRM system", + "keywords": ["integration", "crm", "system"], + "suggested_priority": "Medium", + "confidence": "low", + "similar_items_found": 0, + "priority_distribution": {}, + "days_untriaged": 15, + "current_status": "New", + "note": "No similar items found, using default priority" + } +] +``` + +## Priority Suggestion Logic + +1. **Find similar items**: Search by keywords, exclude items still in "New" +2. **Count priority distribution**: Tally priorities of similar items +3. **Suggest most common**: Pick priority with highest count +4. **Confidence levels**: + - High: ≥5 similar items found + - Medium: 2-4 similar items + - Low: 0-1 similar items (use default: Medium) + +## Default Backlog Status + +Most Jira projects use "Backlog" status, but some may use: +- "To Do" +- "Open" +- "Ready for Development" + +The workflow will: +1. Try "Backlog" first +2. If transition fails, try "To Do" +3. If still fails, log warning and skip status change (only update priority) + +## Error Handling + +- **No "Backlog" status**: Try alternative statuses, log which was used +- **Priority update failed**: Some projects have restricted priority changes; log error +- **Similar items query too broad**: If >100 results, limit to top 50 by updated date diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md b/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md new file mode 100644 index 0000000..373301e --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md @@ -0,0 +1,125 @@ +# /hygiene.unassigned-progress - Show In-Progress Tickets Without Assignee + +## Purpose + +Simple query to find tickets that are marked as "In Progress" but have no assignee. This highlights potential ownership issues. + +## Prerequisites + +- `/hygiene.setup` must be run first + +## Process + +1. **Load configuration**: + - Read `artifacts/jira-hygiene/config.json` + - Extract project key + +2. **Query unassigned in-progress tickets**: + ```jql + project = {PROJECT} AND status = "In Progress" AND assignee is EMPTY AND resolution = Unresolved + ``` + - Fetch: key, summary, status, created, updated, reporter + - Order by updated descending + +3. **Format as markdown table**: + ```markdown + # In-Progress Tickets Without Assignee + + **Total**: N tickets + **Generated**: {timestamp} + + | Key | Summary | Status | Reporter | Age | Last Updated | + |-----|---------|--------|----------|-----|--------------| + | PROJ-123 | Implement new API | In Progress | John Doe | 8d | 2d ago | + | PROJ-456 | Fix login bug | In Progress | Jane Smith | 5d | 1d ago | + ``` + +4. **Write report**: + - Save to `artifacts/jira-hygiene/reports/unassigned-progress.md` + +5. **Display summary**: + - Show table in output + - Highlight oldest ticket + - Group by reporter if helpful + +## Output + +- `artifacts/jira-hygiene/reports/unassigned-progress.md` + +## Example Report + +```markdown +# In-Progress Tickets Without Assignee + +**Project**: PROJ +**Generated**: 2026-04-07 10:30 UTC +**Total**: 4 tickets + +## Summary + +Found 4 tickets marked as "In Progress" but with no assignee. These tickets may be orphaned or forgotten. + +- Oldest: 12 days (PROJ-789) +- Most recent update: 1 day ago (PROJ-456) + +## Tickets + +| Key | Summary | Status | Reporter | Age | Last Updated | +|-----|---------|--------|----------|-----|--------------| +| PROJ-789 | Refactor authentication module | In Progress | John Doe | 12d | 5d ago | +| PROJ-123 | Implement new API endpoint | In Progress | John Doe | 8d | 2d ago | +| PROJ-456 | Fix login bug on mobile | In Progress | Jane Smith | 5d | 1d ago | +| PROJ-234 | Update documentation | In Progress | Bob Johnson | 3d | 6h ago | + +## Recommendations + +**Immediate Action Needed**: +- **PROJ-789**: No updates in 5 days, assign or move back to backlog +- **PROJ-123**: Assign to team member actively working on API + +**By Reporter**: +- John Doe (2 tickets): Follow up on PROJ-789 and PROJ-123 +- Jane Smith (1 ticket): Assign PROJ-456 or update status +- Bob Johnson (1 ticket): Recent activity on PROJ-234, verify assignment needed + +## Common Causes + +Tickets end up "In Progress" without assignee when: +1. Assignee was removed but status not updated +2. Ticket was started but never formally assigned +3. Team member left and tickets weren't reassigned +4. Workflow allows status change without assignment + +## Suggested Actions + +For each ticket: +1. **Assign to owner**: If work is ongoing, assign to current owner +2. **Move to backlog**: If work was abandoned, revert to "To Do" or "Backlog" +3. **Close if duplicate**: Check for duplicate tickets that may have superseded this one +``` + +## Status Variations + +Different Jira projects may use different status names for "in progress": +- "In Progress" +- "In Development" +- "Work In Progress" +- "Doing" + +This command checks for "In Progress" by default. If your project uses a different name, the JQL will need adjustment or the workflow can be enhanced to detect all "in progress category" statuses. + +## Why This Matters + +Unassigned in-progress tickets indicate: +- **Lost ownership**: Work may be forgotten +- **Stale work**: Previous assignee moved on +- **Process gaps**: Status changed without assignment +- **Coordination issues**: Team doesn't know who's working on what + +Regular checks help maintain accountability and prevent work from falling through cracks. + +## Error Handling + +- **No tickets found**: Report "No in-progress tickets without assignee" (good news!) +- **Status name mismatch**: If query returns empty but you expect results, check project's status names +- **Query failed**: Verify project key is correct diff --git a/workflows/jira-hygiene/CLAUDE.md b/workflows/jira-hygiene/CLAUDE.md new file mode 100644 index 0000000..53f0bef --- /dev/null +++ b/workflows/jira-hygiene/CLAUDE.md @@ -0,0 +1,165 @@ +# Jira Hygiene Workflow + +Systematic Jira project hygiene through 10 specialized commands: + +**Setup**: `/hygiene.setup` +**Linking**: `/hygiene.link-epics`, `/hygiene.link-initiatives` +**Activity**: `/hygiene.activity-summary`, `/hygiene.show-blocking` +**Bulk Ops**: `/hygiene.close-stale`, `/hygiene.triage-new` +**Data Quality**: `/hygiene.blocking-closed`, `/hygiene.unassigned-progress`, `/hygiene.activity-type` + +All commands are defined in `.claude/commands/hygiene.*.md`. +Artifacts written to `artifacts/jira-hygiene/`. + +## Principles + +- **Safety first**: All bulk operations use review-then-execute pattern +- **Transparency**: Show what will change before making changes +- **Auditability**: Log all operations with timestamps +- **Idempotency**: Safe to run commands multiple times +- **Semantic matching**: Use intelligent keyword-based matching for linking (50% threshold) +- **Priority-aware**: Different staleness thresholds by priority (High: 1w, Medium: 2w, Low: 1m) + +## Hard Limits + +### API Safety + +- **No operations without environment variables** - Validate JIRA_URL, JIRA_EMAIL, JIRA_API_TOKEN before any API call +- **No API token logging** - Always redact tokens in logs, use `len(token)` if needed +- **Respect rate limits** - Minimum 0.5s delay between requests, retry on 429 +- **No modification of closed tickets** - Only operate on `resolution = Unresolved` +- **Validate HTTP responses** - Check status codes, parse JSON safely + +### Bulk Operations + +- **No destructive operations without confirmation** - All bulk operations require explicit user approval +- **No cross-project operations without mapping** - Initiative linking requires configured project mapping +- **Maximum 50 tickets per confirmation** - Batch large operations for user review +- **Dry-run support required** - All bulk commands must support `--dry-run` flag +- **Log every operation** - Write timestamp, action, ticket key, result to operation logs + +### Data Integrity + +- **Validate JQL before execution** - Test queries return expected types/fields +- **No silent failures** - Report errors clearly, don't skip without notification +- **Preserve existing data** - Don't overwrite assignees, priorities, or custom fields without explicit intent +- **No duplicate links** - Check if link already exists before creating +- **Verify field IDs** - Fetch custom field metadata, don't hardcode field IDs + +## Safety + +### Review-then-Execute Pattern + +Every bulk operation follows this flow: + +1. **Query** - Execute JQL, fetch ticket data +2. **Analyze** - Extract keywords, calculate match scores, determine candidates +3. **Save** - Write candidates to `artifacts/jira-hygiene/candidates/{operation}.json` +4. **Display** - Show summary with ticket counts, match scores, or suggestions +5. **Confirm** - Ask user for explicit approval ("yes" to proceed) +6. **Execute** - Only if confirmed, make API calls with rate limiting +7. **Log** - Write results to `artifacts/jira-hygiene/operations/{operation}-{timestamp}.log` + +### Dry-Run Mode + +When user passes `--dry-run` flag: + +- Execute steps 1-4 only +- Display "DRY RUN" header prominently +- Show what **would** happen without making changes +- Skip steps 5-7 entirely + +### Error Handling + +- **Connection errors**: Check network, validate JIRA_URL format +- **Auth errors (401)**: Validate email/token, suggest regenerating token +- **Rate limit (429)**: Wait and retry, increase delay to 1s +- **Not found (404)**: Ticket may have been deleted, log and continue +- **Bad request (400)**: JQL syntax error or invalid field, show error message +- **Server error (500)**: Jira issue, suggest trying again later + +## Quality + +### JQL Best Practices + +- Always include `resolution = Unresolved` for active tickets +- Use `text ~` for keyword search, not exact match +- Escape quotes in JQL: use single quotes for values with spaces +- Test JQL in Jira UI before using in commands +- Use project key, not project name (e.g., `PROJ` not `"My Project"`) + +### Semantic Matching + +For linking orphaned tickets: + +1. Extract keywords: Remove stopwords (the, a, an, is, for, to, etc.) +2. Keep technical terms: Preserve API, auth, payment, etc. +3. Search strategy: Start with all keywords, fallback to top 3 if no results +4. Score calculation: `(matching_keywords / total_keywords) * 100` +5. Thresholds: + - ≥50%: Suggest linking with confidence + - <50%: Suggest creating new epic/initiative + - 0%: Always suggest creating new + +### Activity Summary Quality + +When generating weekly summaries: + +- Focus on status changes (New → In Progress → Done) +- Highlight new assignments or reassignments +- Include comment count (not full text) +- Summarize in 2-4 sentences +- Use business language, not technical jargon +- Format: "This week, {X} stories were {action}. {Y} items are {status}. {Notable events}." + +## Escalation + +Stop and request human guidance when: + +- **Environment variables missing** - Cannot proceed without credentials +- **Project key unknown** - User must specify which project to operate on +- **Initiative project mapping unclear** - Cross-project linking requires explicit configuration +- **Custom field name ambiguous** - Multiple fields match "Activity Type", need field ID +- **Bulk operation >100 tickets** - Confirm user wants to proceed with large batch +- **API errors persist** - After 3 retries, suggest checking Jira status + +## Configuration + +The workflow uses `artifacts/jira-hygiene/config.json` to cache: + +```json +{ + "jira_url": "https://company.atlassian.net", + "project_key": "PROJ", + "initiative_projects": ["INIT1", "INIT2"], + "activity_type_field_id": "customfield_10050", + "activity_type_values": ["Development", "Bug Fix", "Documentation", "Research"], + "staleness_thresholds": { + "Highest": 7, + "High": 7, + "Medium": 14, + "Low": 30, + "Lowest": 30 + } +} +``` + +This file is created by `/hygiene.setup` and read by other commands. It avoids repeated API calls for field metadata. + +## Testing + +Before submitting PR, verify: + +1. **Validate JSON**: `jq . .ambient/ambient.json` (no syntax errors) +2. **Check commands**: All 10 command files exist in `.claude/commands/` +3. **Test dry-run**: Run `/hygiene.close-stale --dry-run` without making changes +4. **Verify logging**: Operation logs contain timestamp, action, results +5. **Check rate limiting**: Monitor API call timing (≥0.5s gaps) + +## Custom Workflow Testing + +Test in ACP using Custom Workflow: + +- **URL**: `https://github.com/YOUR-USERNAME/workflows.git` (your fork) +- **Branch**: `feature/jira_hygiene_workflows` +- **Path**: `workflows/jira-hygiene` diff --git a/workflows/jira-hygiene/README.md b/workflows/jira-hygiene/README.md new file mode 100644 index 0000000..bd1899f --- /dev/null +++ b/workflows/jira-hygiene/README.md @@ -0,0 +1,433 @@ +# Jira Hygiene Workflow + +Systematic Jira project hygiene through automated detection, intelligent suggestions, and safe bulk operations. + +## Overview + +This workflow helps maintain clean and well-organized Jira projects by: + +- **Linking orphaned tickets**: Connect stories to epics and epics to initiatives using semantic matching +- **Generating activity summaries**: Create weekly summaries for epics/initiatives by analyzing child item changes +- **Closing stale tickets**: Bulk-close inactive tickets based on priority-specific thresholds +- **Suggesting triage outcomes**: Recommend priority and status for untriaged items +- **Identifying data quality issues**: Find missing assignees, activity types, and broken blocking relationships + +All bulk operations use a **review-then-execute pattern** for safety: you see what will change before any changes are made. + +## Prerequisites + +### Jira API Credentials + +Set these environment variables before using the workflow: + +```bash +export JIRA_URL="https://your-instance.atlassian.net" +export JIRA_EMAIL="your-email@company.com" +export JIRA_API_TOKEN="your-api-token" +``` + +**To generate a Jira API token**: +1. Go to [id.atlassian.com/manage-profile/security/api-tokens](https://id.atlassian.com/manage-profile/security/api-tokens) +2. Click "Create API token" +3. Name it (e.g., "Jira Hygiene Workflow") +4. Copy the token (you won't be able to see it again) + +### Required Permissions + +Your Jira account must have: +- Read access to the target project(s) +- Edit access to update issues +- Permission to add comments +- Permission to close issues + +## Getting Started + +1. **Run setup** to configure the workflow: + ``` + /hygiene.setup + ``` + This validates your Jira connection and configures project settings. + +2. **Choose a hygiene task**: + - Start with simple reports: `/hygiene.show-blocking` or `/hygiene.unassigned-progress` + - Try bulk operations in dry-run mode: `/hygiene.close-stale --dry-run` + - Use linking operations to organize your backlog: `/hygiene.link-epics` + +3. **Review artifacts** in `artifacts/jira-hygiene/`: + - Check candidate files before bulk operations + - Review operation logs for audit trail + - Read generated summaries before posting + +## Commands + +### Setup & Configuration + +#### `/hygiene.setup` + +Validate Jira connection and configure project settings. + +**What it does**: +- Tests API credentials +- Prompts for project key and initiative project mapping +- Fetches Activity Type field metadata +- Creates `artifacts/jira-hygiene/config.json` + +**When to use**: First command to run, or when changing projects + +--- + +### Linking Operations + +#### `/hygiene.link-epics` + +Link orphaned stories to epics using semantic matching. + +**What it does**: +- Finds stories without epic links +- Extracts keywords from story summary/description +- Searches for matching epics (50% keyword overlap threshold) +- Suggests creating new epic if no good match exists + +**Review-then-execute**: Yes +**Dry-run support**: Via manual review step + +**Example output**: +``` +Found 15 orphaned stories: +- 10 stories with good matches (≥50%) +- 5 stories need new epics (<50% match) + +[STORY-123] "Implement user login" → [EPIC-45] "Authentication System" (75% match) +[STORY-124] "Add payment gateway" → Create new epic (0% match) +``` + +--- + +#### `/hygiene.link-initiatives` + +Link orphaned epics to initiatives across projects. + +**What it does**: +- Finds epics without parent initiative links +- Searches configured initiative projects for matches +- Suggests best matches based on keyword overlap + +**Review-then-execute**: Yes +**Dry-run support**: Via manual review step + +**Note**: Requires initiative project mapping in config + +--- + +### Activity & Reporting + +#### `/hygiene.activity-summary` + +Generate weekly activity summaries for epics/initiatives. + +**What it does**: +- Analyzes child items for the past 7 days +- Tracks status transitions, assignments, comments +- Generates business-friendly summary paragraph +- Posts summary as comment on epic/initiative + +**Review-then-execute**: Yes (shows summaries before posting) + +**Example summary**: +> This week, 3 stories moved to In Progress and 2 were completed. The team made 4 new assignments. Discussion focused on OAuth implementation with 8 comments across 4 stories. + +--- + +#### `/hygiene.show-blocking` + +Show all blocking tickets in the project. + +**What it does**: +- Queries tickets with "Blocker" priority +- Displays formatted table with status, assignee, age +- Highlights unassigned blockers + +**Review-then-execute**: No (read-only report) + +--- + +### Bulk Operations + +#### `/hygiene.close-stale` + +Close stale tickets based on priority-specific thresholds. + +**Default thresholds**: +- Highest/High: 7 days +- Medium: 14 days +- Low/Lowest: 30 days + +**Arguments**: +- `--highest ` - Override threshold for Highest priority +- `--high ` - Override for High priority +- `--medium ` - Override for Medium priority +- `--low ` - Override for Low priority +- `--lowest ` - Override for Lowest priority +- `--dry-run` - Show what would be closed without making changes + +**What it does**: +- Finds tickets not updated within threshold +- Groups by priority for review +- Adds closure comment and closes tickets + +**Closure message**: +> Due to lack of activity, this item has been closed. If you feel that it should be addressed, please reopen it. + +**Review-then-execute**: Yes +**Dry-run support**: Yes + +**Example**: +```bash +# Close stale tickets using defaults +/hygiene.close-stale + +# See what would be closed without making changes +/hygiene.close-stale --dry-run + +# Use custom thresholds +/hygiene.close-stale --high 14 --medium 30 --low 60 +``` + +--- + +#### `/hygiene.triage-new` + +Suggest triage outcomes for untriaged items. + +**What it does**: +- Finds items in "New" status for >1 week +- Analyzes similar triaged items to suggest priority +- Recommends moving to "Backlog" status +- Provides confidence level based on similar item count + +**Arguments**: +- `--days ` - Override threshold (default: 7) +- `--dry-run` - Show suggestions without making changes + +**Review-then-execute**: Yes +**Dry-run support**: Yes + +**Confidence levels**: +- High: ≥5 similar items found +- Medium: 2-4 similar items +- Low: 0-1 similar items (uses default: Medium) + +--- + +### Data Quality + +#### `/hygiene.blocking-closed` + +Find blocking tickets where all blocked items are closed. + +**What it does**: +- Finds tickets with "blocks" issue links +- Checks if all blocked tickets are resolved +- Suggests closing blocker or removing links + +**Review-then-execute**: No (manual review required) + +**Note**: This is a report-only command because each case requires human judgment. + +--- + +#### `/hygiene.unassigned-progress` + +Show tickets in progress without assignee. + +**What it does**: +- Finds "In Progress" tickets with no assignee +- Displays formatted table by age +- Groups by reporter for follow-up + +**Review-then-execute**: No (read-only report) + +--- + +#### `/hygiene.activity-type` + +Suggest Activity Type for tickets missing this field. + +**What it does**: +- Finds tickets with empty Activity Type field +- Analyzes summary/description for keywords +- Matches against available Activity Type values +- Suggests best match with confidence level + +**Arguments**: +- `--dry-run` - Show suggestions without making changes + +**Review-then-execute**: Yes +**Dry-run support**: Yes + +**Keyword mappings**: +- Development: implement, create, add, build, feature +- Bug Fix: fix, bug, error, broken, defect +- Documentation: document, guide, wiki, manual +- Research: investigate, explore, spike, POC +- Testing: test, QA, verify, validate + +--- + +## Output Structure + +All artifacts are written to `artifacts/jira-hygiene/`: + +``` +artifacts/jira-hygiene/ +├── config.json # Project configuration +├── candidates/ # Review before bulk ops +│ ├── link-epics.json +│ ├── link-initiatives.json +│ ├── close-stale.json +│ ├── triage-new.json +│ └── activity-type.json +├── summaries/ # Generated summaries +│ └── {epic-key}-{date}.md +├── reports/ # Read-only reports +│ ├── blocking-tickets.md +│ ├── blocking-closed-mismatch.md +│ └── unassigned-progress.md +└── operations/ # Audit logs + ├── link-epics-{timestamp}.log + ├── close-stale-{timestamp}.log + └── ... +``` + +## Safety Features + +### Review-then-Execute Pattern + +All bulk operations follow this flow: + +1. **Query**: Execute JQL to find candidates +2. **Analyze**: Apply semantic matching or rules +3. **Save**: Write candidates to JSON file +4. **Display**: Show summary of what will change +5. **Confirm**: Ask for explicit approval +6. **Execute**: Make changes only if confirmed +7. **Log**: Write audit log with results + +### Dry-Run Mode + +Commands that support `--dry-run`: +- `/hygiene.close-stale` +- `/hygiene.triage-new` +- `/hygiene.activity-type` + +Dry-run mode shows what **would** happen without making any changes. + +### Rate Limiting + +All API calls include: +- 0.5s delay between requests (minimum) +- Automatic retry on 429 (rate limit) errors +- Increased delay to 1s after rate limit hit + +### Operation Logging + +Every bulk operation writes a timestamped log: + +``` +2026-04-07 10:30:15 - START: Close stale tickets +2026-04-07 10:30:16 - CLOSED: PROJ-100 (Highest priority, 12 days stale) +2026-04-07 10:30:17 - CLOSED: PROJ-101 (High priority, 9 days stale) +2026-04-07 10:30:18 - ERROR: PROJ-102 - Transition failed (permission denied) +2026-04-07 10:30:19 - END: 2 closed, 1 error +``` + +## Best Practices + +### Regular Hygiene Routine + +**Weekly**: +- Generate activity summaries for key epics: `/hygiene.activity-summary` +- Check for untriaged items: `/hygiene.triage-new` +- Review blocking tickets: `/hygiene.show-blocking` + +**Monthly**: +- Close stale tickets: `/hygiene.close-stale` +- Link orphaned stories: `/hygiene.link-epics` +- Check in-progress items: `/hygiene.unassigned-progress` + +**Quarterly**: +- Link orphaned epics to initiatives: `/hygiene.link-initiatives` +- Review blocking-closed mismatches: `/hygiene.blocking-closed` +- Fill in missing activity types: `/hygiene.activity-type` + +### Using with Multiple Projects + +Run `/hygiene.setup` each time you switch projects. The config file stores the current project context. + +### Customizing Thresholds + +Adjust staleness thresholds based on your team's velocity: + +**Fast-moving team** (releases weekly): +```bash +/hygiene.close-stale --high 3 --medium 7 --low 14 +``` + +**Slower cadence** (releases monthly): +```bash +/hygiene.close-stale --high 14 --medium 30 --low 60 +``` + +## Troubleshooting + +### "Authentication failed (401)" + +**Cause**: Invalid credentials +**Solution**: +1. Verify `JIRA_EMAIL` matches your Atlassian account email +2. Regenerate API token at id.atlassian.com +3. Check `JIRA_URL` format (must start with https://) + +### "Field not found" errors + +**Cause**: Custom field names vary by project +**Solution**: +1. Run `/hygiene.setup` to fetch field metadata +2. Check field names in Jira (Admin → Issues → Custom Fields) +3. If "Epic Link" or "Parent Link" are named differently, update JQL + +### "Rate limit exceeded (429)" + +**Cause**: Too many requests +**Solution**: +- Workflow automatically retries with increased delay +- For large operations, work in smaller batches +- Jira Cloud typically allows 10 requests/second + +### "No transition available" + +**Cause**: Status workflow restrictions +**Solution**: +- Check Jira workflow for allowed transitions +- Some tickets may require intermediate states +- Logs will note which tickets couldn't be transitioned + +## Contributing + +To modify or extend this workflow: + +1. Read `CLAUDE.md` for safety rules and principles +2. Update command files in `.claude/commands/` +3. Test with `--dry-run` flags before live operations +4. Update this README with any new commands or features + +## Support + +For issues or feature requests: +- File an issue in the repository +- Include operation logs from `artifacts/jira-hygiene/operations/` +- Provide example JQL queries that aren't working + +## License + +Part of the Ambient Code Workflows repository. See main repository LICENSE. From 416a7e51650d9300b5766d0b31c18181c5ac61b8 Mon Sep 17 00:00:00 2001 From: Eliad Cohen Date: Tue, 7 Apr 2026 13:47:55 -0400 Subject: [PATCH 2/8] fix: prompt directly for project keys instead of fetching all projects Remove suggestion to query for accessible projects in /hygiene.setup. User should provide exact project keys they want to use. Co-Authored-By: Claude Sonnet 4.5 --- workflows/jira-hygiene/.claude/commands/hygiene.setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.setup.md b/workflows/jira-hygiene/.claude/commands/hygiene.setup.md index aa4388d..3a94a6a 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.setup.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.setup.md @@ -29,7 +29,7 @@ Environment variables must be set: 3. **Prompt for project configuration**: - **Target project key**: The Jira project to operate on (e.g., "PROJ") - **Initiative project keys**: Comma-separated list of projects containing initiatives (e.g., "INIT1,INIT2") - - If user is unsure, suggest running a query to list accessible projects + - User must provide the exact project keys they want to use 4. **Fetch Activity Type field metadata**: - Call `/rest/api/3/field` to get all custom fields From dccbe3840661fafdc3e82049d084ea205d62f607 Mon Sep 17 00:00:00 2001 From: Eliad Cohen Date: Tue, 7 Apr 2026 14:07:05 -0400 Subject: [PATCH 3/8] feat: add clickable Jira links to all reports and summaries Update all command files to generate clickable links in reports: - Ticket links: [KEY](JIRA_URL/browse/KEY) - JQL search links: [View in Jira](JIRA_URL/issues/?jql=...) - URL-encoded JQL queries for proper link formatting Updated commands: - /hygiene.show-blocking - links in table and report - /hygiene.unassigned-progress - links in table and recommendations - /hygiene.blocking-closed - links to tickets and blocked items - /hygiene.link-epics - links in suggestions display - /hygiene.link-initiatives - links in suggestions display - /hygiene.close-stale - links in priority groups - /hygiene.triage-new - links in confidence-based display - /hygiene.activity-type - links in suggestions Makes reports more actionable by allowing direct navigation to tickets and searches from generated markdown files. Co-Authored-By: Claude Sonnet 4.5 --- .../.claude/commands/hygiene.activity-type.md | 14 ++++--- .../commands/hygiene.blocking-closed.md | 37 +++++++++++-------- .../.claude/commands/hygiene.close-stale.md | 10 +++-- .../.claude/commands/hygiene.link-epics.md | 10 +++-- .../commands/hygiene.link-initiatives.md | 10 +++-- .../.claude/commands/hygiene.show-blocking.md | 25 ++++++++----- .../.claude/commands/hygiene.triage-new.md | 16 +++++--- .../commands/hygiene.unassigned-progress.md | 32 +++++++++------- 8 files changed, 96 insertions(+), 58 deletions(-) diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md b/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md index 5dfd34a..99f9bb0 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md @@ -59,19 +59,23 @@ Optional: - Save to `artifacts/jira-hygiene/candidates/activity-type.json` - Include: key, summary, suggested activity type, confidence, matching keywords -5. **Display summary**: +5. **Display summary with Jira links**: ``` Found N tickets missing Activity Type: High confidence (≥3 keyword matches): 12 tickets - PROJ-100 "Fix broken login flow" → Bug Fix (keywords: fix, broken, bug) - PROJ-101 "Document API endpoints" → Documentation (keywords: document, API, guide) + • [{PROJ-100}]({JIRA_URL}/browse/PROJ-100) "Fix broken login flow" + → Bug Fix (keywords: fix, broken, bug) + • [{PROJ-101}]({JIRA_URL}/browse/PROJ-101) "Document API endpoints" + → Documentation (keywords: document, API, guide) Medium confidence (1-2 matches): 5 tickets - PROJ-102 "Improve performance" → Development (keywords: improve) + • [{PROJ-102}]({JIRA_URL}/browse/PROJ-102) "Improve performance" + → Development (keywords: improve) Low confidence (issuetype heuristic): 3 tickets - PROJ-103 "Update system" → Development (Bug issuetype suggests bug fix, but no clear keywords) + • [{PROJ-103}]({JIRA_URL}/browse/PROJ-103) "Update system" + → Development (Bug issuetype suggests bug fix, but no clear keywords) ``` 6. **Ask for confirmation**: diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md b/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md index 635dec1..9528046 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md @@ -36,26 +36,30 @@ Highlight tickets marked as "Blocking" where all of the blocked tickets are alre - If ALL blocked tickets are closed: flag for review - If at least one blocked ticket is open: skip (still validly blocking) -4. **Write report**: +4. **Write report with Jira links**: - Save to `artifacts/jira-hygiene/reports/blocking-closed-mismatch.md` - Include: blocking ticket key, summary, list of closed blocked tickets + - Format ticket keys as clickable links: `[{KEY}]({JIRA_URL}/browse/{KEY})` + - Include search link at top to view all blocking tickets in Jira -5. **Display report**: +5. **Display report with Jira links**: ``` Found N blocking tickets where all dependencies are closed: - PROJ-123 "Fix database migration issue" + [{PROJ-123}]({JIRA_URL}/browse/PROJ-123) "Fix database migration issue" Blocks (all closed): - - PROJ-145 "Deploy new schema" (Closed 5 days ago) - - PROJ-167 "Update migration scripts" (Closed 3 days ago) + - [{PROJ-145}]({JIRA_URL}/browse/PROJ-145) "Deploy new schema" (Closed 5 days ago) + - [{PROJ-167}]({JIRA_URL}/browse/PROJ-167) "Update migration scripts" (Closed 3 days ago) Suggested action: Close PROJ-123 or remove blocking links - PROJ-456 "Security audit blocker" + [{PROJ-456}]({JIRA_URL}/browse/PROJ-456) "Security audit blocker" Blocks (all closed): - - PROJ-500 "Implement OAuth" (Closed 2 weeks ago) + - [{PROJ-500}]({JIRA_URL}/browse/PROJ-500) "Implement OAuth" (Closed 2 weeks ago) Suggested action: Close PROJ-456 or remove blocking link + + Full report: artifacts/jira-hygiene/reports/blocking-closed-mismatch.md ``` 6. **No bulk operation**: @@ -74,7 +78,8 @@ Highlight tickets marked as "Blocking" where all of the blocked tickets are alre **Project**: PROJ **Generated**: 2026-04-07 10:30 UTC -**Total Mismatches**: 3 tickets +**Total Mismatches**: 3 tickets +**[View all blocking tickets in Jira](https://company.atlassian.net/issues/?jql=project+%3D+PROJ+AND+issueFunction+in+linkedIssuesOf%28%22project+%3D+PROJ%22%2C+%22blocks%22%29+AND+resolution+%3D+Unresolved)** ## Summary @@ -82,34 +87,34 @@ Found 3 tickets that are still marked as blocking, but all blocked items are alr ## Tickets Requiring Review -### PROJ-123 "Fix database migration issue" +### [PROJ-123](https://company.atlassian.net/browse/PROJ-123) "Fix database migration issue" **Status**: In Progress **Last Updated**: 2026-03-25 **Blocks** (all closed): -- PROJ-145 "Deploy new schema" (Closed: 2026-04-02, 5 days ago) -- PROJ-167 "Update migration scripts" (Closed: 2026-04-04, 3 days ago) +- [PROJ-145](https://company.atlassian.net/browse/PROJ-145) "Deploy new schema" (Closed: 2026-04-02, 5 days ago) +- [PROJ-167](https://company.atlassian.net/browse/PROJ-167) "Update migration scripts" (Closed: 2026-04-04, 3 days ago) **Suggested Actions**: -1. If migration issue is resolved: Close PROJ-123 +1. If migration issue is resolved: Close [PROJ-123](https://company.atlassian.net/browse/PROJ-123) 2. If new blockers emerged: Update links to reflect current blockers 3. If no longer blocking: Remove the "blocks" links --- -### PROJ-456 "Security audit blocker" +### [PROJ-456](https://company.atlassian.net/browse/PROJ-456) "Security audit blocker" **Status**: To Do **Last Updated**: 2026-03-15 **Blocks** (all closed): -- PROJ-500 "Implement OAuth" (Closed: 2026-03-24, 14 days ago) +- [PROJ-500](https://company.atlassian.net/browse/PROJ-500) "Implement OAuth" (Closed: 2026-03-24, 14 days ago) **Suggested Actions**: -1. If audit is complete: Close PROJ-456 +1. If audit is complete: Close [PROJ-456](https://company.atlassian.net/browse/PROJ-456) 2. If audit revealed new work: Create new tickets and update links -3. If audit was cancelled: Close PROJ-456 +3. If audit was cancelled: Close [PROJ-456](https://company.atlassian.net/browse/PROJ-456) --- diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md b/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md index fe068ac..6f18588 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md @@ -37,14 +37,14 @@ Optional: Override default thresholds - Save to `artifacts/jira-hygiene/candidates/close-stale.json` - Include: key, summary, priority, days since update, assignee -4. **Display summary by priority**: +4. **Display summary by priority with Jira links**: ``` Found N stale tickets to close: Highest/High (>7 days): 3 tickets - PROJ-100 "Old critical bug" (12 days, assigned to John) - PROJ-101 "High priority feature" (9 days, unassigned) - PROJ-102 "Urgent fix needed" (8 days, assigned to Jane) + • [{PROJ-100}]({JIRA_URL}/browse/PROJ-100) "Old critical bug" (12 days, assigned to John) + • [{PROJ-101}]({JIRA_URL}/browse/PROJ-101) "High priority feature" (9 days, unassigned) + • [{PROJ-102}]({JIRA_URL}/browse/PROJ-102) "Urgent fix needed" (8 days, assigned to Jane) Medium (>14 days): 5 tickets ... @@ -53,6 +53,8 @@ Optional: Override default thresholds ... Total: 20 tickets will be closed + + View all candidates: See artifacts/jira-hygiene/candidates/close-stale.json ``` 5. **Ask for confirmation**: diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md b/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md index c437d02..c37a908 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md @@ -51,15 +51,19 @@ Find stories without epic links and suggest appropriate epics to link them to, u - Save to `artifacts/jira-hygiene/candidates/link-epics.json` - Include: story key, story summary, suggested action, epic key (if linking), match score -5. **Display summary**: +5. **Display summary with Jira links**: ``` Found N orphaned stories: - M stories with good matches (≥50%) - P stories need new epics (<50% match) + View orphaned stories: {JIRA_URL}/issues/?jql=project+%3D+{PROJECT}+AND+issuetype+%3D+Story+AND+%22Epic+Link%22+is+EMPTY + Top suggestions: - [STORY-123] "Implement user login" → [EPIC-45] "Authentication System" (75% match) - [STORY-124] "Add payment gateway" → Create new epic (0% match) + • [{STORY-123}]({JIRA_URL}/browse/STORY-123) "Implement user login" + → [{EPIC-45}]({JIRA_URL}/browse/EPIC-45) "Authentication System" (75% match) + • [{STORY-124}]({JIRA_URL}/browse/STORY-124) "Add payment gateway" + → Create new epic (0% match) ``` 6. **Ask for confirmation**: diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md b/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md index 88e0e6e..7a1941a 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md @@ -49,15 +49,19 @@ Find epics without initiative links and suggest appropriate initiatives from con - Save to `artifacts/jira-hygiene/candidates/link-initiatives.json` - Include: epic key, epic summary, suggested initiative (if any), match score -5. **Display summary**: +5. **Display summary with Jira links**: ``` Found N orphaned epics: - M epics with good matches (≥50%) - P epics with no good match + View orphaned epics: {JIRA_URL}/issues/?jql=project+%3D+{PROJECT}+AND+issuetype+%3D+Epic+AND+%22Parent+Link%22+is+EMPTY + Top suggestions: - [EPIC-45] "Authentication System" → [INIT-12] "User Management Platform" (80% match) - [EPIC-46] "Payment Gateway" → No good match found (20% best match) + • [{EPIC-45}]({JIRA_URL}/browse/EPIC-45) "Authentication System" + → [{INIT-12}]({JIRA_URL}/browse/INIT-12) "User Management Platform" (80% match) + • [{EPIC-46}]({JIRA_URL}/browse/EPIC-46) "Payment Gateway" + → No good match found (20% best match) ``` 6. **Ask for confirmation**: diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md b/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md index c3eca7a..8619fec 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md @@ -21,18 +21,24 @@ Simple query to display all tickets marked with "Blocker" priority in the projec - Fetch: key, summary, assignee, status, created, updated - Order by updated descending (most recent first) -3. **Format as markdown table**: +3. **Format as markdown table with Jira links**: ```markdown # Blocking Tickets in {PROJECT} **Total**: N tickets **Generated**: {timestamp} + **[View in Jira]({JIRA_URL}/issues/?jql=project+%3D+{PROJECT}+AND+priority+%3D+Blocker+AND+resolution+%3D+Unresolved)** | Key | Summary | Assignee | Status | Age | Last Updated | |-----|---------|----------|--------|-----|--------------| - | PROJ-123 | Critical API failure | John Doe | In Progress | 5d | 2d ago | - | PROJ-456 | Database migration blocked | Unassigned | To Do | 12d | 3d ago | + | [PROJ-123]({JIRA_URL}/browse/PROJ-123) | Critical API failure | John Doe | In Progress | 5d | 2d ago | + | [PROJ-456]({JIRA_URL}/browse/PROJ-456) | Database migration blocked | Unassigned | To Do | 12d | 3d ago | ``` + + **Link format**: + - Ticket links: `[{KEY}]({JIRA_URL}/browse/{KEY})` + - Search link: `[View in Jira]({JIRA_URL}/issues/?jql={URL_ENCODED_JQL})` + - URL-encode JQL: spaces → `+`, special chars → percent-encoded 4. **Write report**: - Save to `artifacts/jira-hygiene/reports/blocking-tickets.md` @@ -52,7 +58,8 @@ Simple query to display all tickets marked with "Blocker" priority in the projec # Blocking Tickets in PROJ **Total**: 3 tickets -**Generated**: 2026-04-07 10:30 UTC +**Generated**: 2026-04-07 10:30 UTC +**[View in Jira](https://company.atlassian.net/issues/?jql=project+%3D+PROJ+AND+priority+%3D+Blocker+AND+resolution+%3D+Unresolved)** ## Summary @@ -64,14 +71,14 @@ Simple query to display all tickets marked with "Blocker" priority in the projec | Key | Summary | Assignee | Status | Age | Last Updated | |-----|---------|----------|--------|-----|--------------| -| PROJ-123 | Critical API failure in auth endpoint | John Doe | In Progress | 5d | 2d ago | -| PROJ-456 | Database migration blocked by schema lock | Unassigned | To Do | 12d | 3d ago | -| PROJ-789 | Production deployment failing | Jane Smith | Code Review | 3d | 1d ago | +| [PROJ-123](https://company.atlassian.net/browse/PROJ-123) | Critical API failure in auth endpoint | John Doe | In Progress | 5d | 2d ago | +| [PROJ-456](https://company.atlassian.net/browse/PROJ-456) | Database migration blocked by schema lock | Unassigned | To Do | 12d | 3d ago | +| [PROJ-789](https://company.atlassian.net/browse/PROJ-789) | Production deployment failing | Jane Smith | Code Review | 3d | 1d ago | ## Recommendations -- **PROJ-456**: Assign to database team immediately (unassigned for 12 days) -- **PROJ-123**: Follow up on progress (blocker for 5 days) +- **[PROJ-456](https://company.atlassian.net/browse/PROJ-456)**: Assign to database team immediately (unassigned for 12 days) +- **[PROJ-123](https://company.atlassian.net/browse/PROJ-123)**: Follow up on progress (blocker for 5 days) ``` ## Error Handling diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md b/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md index df8f9ba..da53c82 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md @@ -54,19 +54,25 @@ Optional: - Save to `artifacts/jira-hygiene/candidates/triage-new.json` - Include: key, summary, suggested priority, confidence, similar item count -5. **Display summary**: +5. **Display summary with Jira links**: ``` Found N untriaged items (>7 days): + View untriaged: {JIRA_URL}/issues/?jql=project+%3D+{PROJECT}+AND+status+%3D+New+AND+created+%3C+-7d + High confidence (≥5 similar items): 8 items - PROJ-200 "Add export feature" → Priority: Medium (based on 8 similar items) - PROJ-201 "Fix broken link" → Priority: Low (based on 6 similar items) + • [{PROJ-200}]({JIRA_URL}/browse/PROJ-200) "Add export feature" + → Priority: Medium (based on 8 similar items) + • [{PROJ-201}]({JIRA_URL}/browse/PROJ-201) "Fix broken link" + → Priority: Low (based on 6 similar items) Medium confidence (2-4 similar): 3 items - PROJ-202 "Improve performance" → Priority: High (based on 3 similar items) + • [{PROJ-202}]({JIRA_URL}/browse/PROJ-202) "Improve performance" + → Priority: High (based on 3 similar items) Low confidence (0-1 similar): 2 items - PROJ-203 "New integration request" → Priority: Medium (default, no similar items) + • [{PROJ-203}]({JIRA_URL}/browse/PROJ-203) "New integration request" + → Priority: Medium (default, no similar items) ``` 6. **Ask for confirmation**: diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md b/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md index 373301e..f5f958c 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md @@ -21,18 +21,23 @@ Simple query to find tickets that are marked as "In Progress" but have no assign - Fetch: key, summary, status, created, updated, reporter - Order by updated descending -3. **Format as markdown table**: +3. **Format as markdown table with Jira links**: ```markdown # In-Progress Tickets Without Assignee **Total**: N tickets **Generated**: {timestamp} + **[View in Jira]({JIRA_URL}/issues/?jql=project+%3D+{PROJECT}+AND+status+%3D+%22In+Progress%22+AND+assignee+is+EMPTY+AND+resolution+%3D+Unresolved)** | Key | Summary | Status | Reporter | Age | Last Updated | |-----|---------|--------|----------|-----|--------------| - | PROJ-123 | Implement new API | In Progress | John Doe | 8d | 2d ago | - | PROJ-456 | Fix login bug | In Progress | Jane Smith | 5d | 1d ago | + | [PROJ-123]({JIRA_URL}/browse/PROJ-123) | Implement new API | In Progress | John Doe | 8d | 2d ago | + | [PROJ-456]({JIRA_URL}/browse/PROJ-456) | Fix login bug | In Progress | Jane Smith | 5d | 1d ago | ``` + + **Link format**: + - Ticket links: `[{KEY}]({JIRA_URL}/browse/{KEY})` + - Search link: URL-encode JQL (spaces → `+` or `%20`, quotes → `%22`) 4. **Write report**: - Save to `artifacts/jira-hygiene/reports/unassigned-progress.md` @@ -53,7 +58,8 @@ Simple query to find tickets that are marked as "In Progress" but have no assign **Project**: PROJ **Generated**: 2026-04-07 10:30 UTC -**Total**: 4 tickets +**Total**: 4 tickets +**[View in Jira](https://company.atlassian.net/issues/?jql=project+%3D+PROJ+AND+status+%3D+%22In+Progress%22+AND+assignee+is+EMPTY+AND+resolution+%3D+Unresolved)** ## Summary @@ -66,21 +72,21 @@ Found 4 tickets marked as "In Progress" but with no assignee. These tickets may | Key | Summary | Status | Reporter | Age | Last Updated | |-----|---------|--------|----------|-----|--------------| -| PROJ-789 | Refactor authentication module | In Progress | John Doe | 12d | 5d ago | -| PROJ-123 | Implement new API endpoint | In Progress | John Doe | 8d | 2d ago | -| PROJ-456 | Fix login bug on mobile | In Progress | Jane Smith | 5d | 1d ago | -| PROJ-234 | Update documentation | In Progress | Bob Johnson | 3d | 6h ago | +| [PROJ-789](https://company.atlassian.net/browse/PROJ-789) | Refactor authentication module | In Progress | John Doe | 12d | 5d ago | +| [PROJ-123](https://company.atlassian.net/browse/PROJ-123) | Implement new API endpoint | In Progress | John Doe | 8d | 2d ago | +| [PROJ-456](https://company.atlassian.net/browse/PROJ-456) | Fix login bug on mobile | In Progress | Jane Smith | 5d | 1d ago | +| [PROJ-234](https://company.atlassian.net/browse/PROJ-234) | Update documentation | In Progress | Bob Johnson | 3d | 6h ago | ## Recommendations **Immediate Action Needed**: -- **PROJ-789**: No updates in 5 days, assign or move back to backlog -- **PROJ-123**: Assign to team member actively working on API +- **[PROJ-789](https://company.atlassian.net/browse/PROJ-789)**: No updates in 5 days, assign or move back to backlog +- **[PROJ-123](https://company.atlassian.net/browse/PROJ-123)**: Assign to team member actively working on API **By Reporter**: -- John Doe (2 tickets): Follow up on PROJ-789 and PROJ-123 -- Jane Smith (1 ticket): Assign PROJ-456 or update status -- Bob Johnson (1 ticket): Recent activity on PROJ-234, verify assignment needed +- John Doe (2 tickets): Follow up on [PROJ-789](https://company.atlassian.net/browse/PROJ-789) and [PROJ-123](https://company.atlassian.net/browse/PROJ-123) +- Jane Smith (1 ticket): Assign [PROJ-456](https://company.atlassian.net/browse/PROJ-456) or update status +- Bob Johnson (1 ticket): Recent activity on [PROJ-234](https://company.atlassian.net/browse/PROJ-234), verify assignment needed ## Common Causes From 9eb4afc3b24b211567cb8b00899f87bc62e75be9 Mon Sep 17 00:00:00 2001 From: Eliad Cohen Date: Tue, 7 Apr 2026 14:11:40 -0400 Subject: [PATCH 4/8] fix: use issue links instead of priority for blocking tickets Change /hygiene.show-blocking to find tickets that block other work via 'Blocks' issue links, not tickets with 'Blocker' priority. Changes: - Query using issueFunction to find tickets with outward 'blocks' links - Display what tickets are being blocked in report table - Add fallback approach for Jira instances without issueFunction - Update JQL pattern in systemPrompt - Show blocked ticket links in summary and recommendations This correctly identifies tickets that are actually blocking other work, rather than just having high priority. Co-Authored-By: Claude Sonnet 4.5 --- workflows/jira-hygiene/.ambient/ambient.json | 2 +- .../.claude/commands/hygiene.show-blocking.md | 49 ++++++++++++------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/workflows/jira-hygiene/.ambient/ambient.json b/workflows/jira-hygiene/.ambient/ambient.json index 5dd5921..57e63af 100644 --- a/workflows/jira-hygiene/.ambient/ambient.json +++ b/workflows/jira-hygiene/.ambient/ambient.json @@ -1,7 +1,7 @@ { "name": "Jira Hygiene", "description": "Systematic workflow for maintaining Jira project hygiene. Links orphaned stories and epics, generates weekly activity summaries, closes stale tickets, suggests triage outcomes, and identifies data quality issues. Provides safe bulk operations with review-then-execute pattern.", - "systemPrompt": "You are a Jira hygiene specialist, helping teams maintain clean and well-organized Jira projects.\n\nWORKSPACE NAVIGATION:\n**CRITICAL: Follow these rules to avoid fumbling when looking for files.**\n\nStandard file locations (from workflow root):\n- Config: .ambient/ambient.json (ALWAYS at this path)\n- Commands: .claude/commands/*.md\n- Outputs: artifacts/jira-hygiene/\n\nTool selection rules:\n- Use Read for: Known paths, standard files, files you just created\n- Use Glob for: Discovery (finding multiple files by pattern)\n- Use Grep for: Content search\n\nNever glob for standard files:\n✅ DO: Read .ambient/ambient.json\n❌ DON'T: Glob **/ambient.json\n\nYour role is to:\n1. Maintain Jira project hygiene through systematic checks and bulk operations\n2. Link orphaned stories to epics and epics to initiatives\n3. Generate weekly activity summaries for epics and initiatives\n4. Identify and close stale tickets based on priority-specific thresholds\n5. Suggest triage outcomes for untriaged items\n6. Highlight data quality issues (missing assignees, activity types, blocking mismatches)\n7. Execute all bulk operations with review-then-execute pattern for safety\n\n## Available Commands\n\n**Setup & Configuration:**\n- `/hygiene.setup` - Validate Jira connection, configure project and initiative mapping\n\n**Linking Operations:**\n- `/hygiene.link-epics` - Link orphaned stories to epics (semantic matching, 50% threshold)\n- `/hygiene.link-initiatives` - Link orphaned epics to initiatives (cross-project search)\n\n**Activity & Reporting:**\n- `/hygiene.activity-summary` - Generate weekly activity summaries for epics/initiatives\n- `/hygiene.show-blocking` - Show all blocking tickets in project\n\n**Bulk Operations:**\n- `/hygiene.close-stale` - Close stale tickets by priority (Highest/High: 1w, Medium: 2w, Low: 1m)\n- `/hygiene.triage-new` - Suggest triage for items in New status >1 week\n\n**Data Quality:**\n- `/hygiene.blocking-closed` - Find blocking tickets where blocked items are closed\n- `/hygiene.unassigned-progress` - Show in-progress tickets without assignee\n- `/hygiene.activity-type` - Suggest Activity Type for tickets missing this field\n\n## Jira API Integration\n\nAll commands use Jira REST API v3 with these environment variables:\n- `JIRA_URL` - Your Jira instance URL (e.g., https://company.atlassian.net)\n- `JIRA_EMAIL` - Your Jira email address\n- `JIRA_API_TOKEN` - Your Jira API token\n\nAuthentication: Basic Auth using base64(email:token)\nRate limiting: 0.5s delay between requests\nError handling: Retry on 429, validate all responses\n\n## Safety & Best Practices\n\n**Review-then-execute pattern:**\n1. Query and analyze tickets\n2. Write candidates to artifacts/jira-hygiene/candidates/\n3. Display summary to user\n4. Ask for explicit confirmation\n5. Execute operations only after confirmation\n6. Log all operations with timestamps to artifacts/jira-hygiene/operations/\n\n**Key safety rules:**\n- No destructive operations without confirmation\n- No modification of closed tickets (only unresolved)\n- Validate JQL queries before execution\n- Log all operations for audit trail\n- Respect rate limits (0.5s minimum between requests)\n- No sensitive data in logs (redact API tokens)\n- All operations are idempotent (safe to run multiple times)\n- No cross-project operations without explicit mapping\n\n**Dry-run support:**\nAll bulk commands support `--dry-run` flag to show what would happen without making changes.\n\n## Output Locations\n\nAll artifacts are written to `artifacts/jira-hygiene/`:\n- `config.json` - Project configuration and field metadata cache\n- `candidates/*.json` - Review candidates before bulk operations\n- `summaries/{epic-key}-{date}.md` - Generated activity summaries\n- `reports/*.md` - Read-only reports for data quality issues\n- `operations/*-{timestamp}.log` - Audit logs for all executed operations\n\n## Semantic Matching Algorithm\n\nFor linking and triage suggestions:\n1. Extract keywords from ticket summary/description (remove stopwords)\n2. Search using Jira text search: `text ~ \"keyword1 keyword2\"`\n3. Calculate match score: (matching_keywords / total_keywords) * 100\n4. Rank by score, suggest top matches\n5. Threshold: ≥50% = auto-suggest, <50% = suggest creating new item\n\n## Common JQL Patterns\n\nOrphaned stories: `project = PROJ AND issuetype = Story AND \"Epic Link\" is EMPTY`\nOrphaned epics: `project = PROJ AND issuetype = Epic AND \"Parent Link\" is EMPTY`\nStale tickets: `project = PROJ AND priority = PRIORITY AND updated < -Nd AND resolution = Unresolved`\nUntriaged: `project = PROJ AND status = New AND created < -7d`\nBlocking: `project = PROJ AND priority = Blocker AND resolution = Unresolved`\nIn-progress unassigned: `project = PROJ AND status = \"In Progress\" AND assignee is EMPTY`\n\nBe helpful, efficient, and always prioritize safety in bulk operations.", + "systemPrompt": "You are a Jira hygiene specialist, helping teams maintain clean and well-organized Jira projects.\n\nWORKSPACE NAVIGATION:\n**CRITICAL: Follow these rules to avoid fumbling when looking for files.**\n\nStandard file locations (from workflow root):\n- Config: .ambient/ambient.json (ALWAYS at this path)\n- Commands: .claude/commands/*.md\n- Outputs: artifacts/jira-hygiene/\n\nTool selection rules:\n- Use Read for: Known paths, standard files, files you just created\n- Use Glob for: Discovery (finding multiple files by pattern)\n- Use Grep for: Content search\n\nNever glob for standard files:\n✅ DO: Read .ambient/ambient.json\n❌ DON'T: Glob **/ambient.json\n\nYour role is to:\n1. Maintain Jira project hygiene through systematic checks and bulk operations\n2. Link orphaned stories to epics and epics to initiatives\n3. Generate weekly activity summaries for epics and initiatives\n4. Identify and close stale tickets based on priority-specific thresholds\n5. Suggest triage outcomes for untriaged items\n6. Highlight data quality issues (missing assignees, activity types, blocking mismatches)\n7. Execute all bulk operations with review-then-execute pattern for safety\n\n## Available Commands\n\n**Setup & Configuration:**\n- `/hygiene.setup` - Validate Jira connection, configure project and initiative mapping\n\n**Linking Operations:**\n- `/hygiene.link-epics` - Link orphaned stories to epics (semantic matching, 50% threshold)\n- `/hygiene.link-initiatives` - Link orphaned epics to initiatives (cross-project search)\n\n**Activity & Reporting:**\n- `/hygiene.activity-summary` - Generate weekly activity summaries for epics/initiatives\n- `/hygiene.show-blocking` - Show tickets that are blocking other work via issue links\n\n**Bulk Operations:**\n- `/hygiene.close-stale` - Close stale tickets by priority (Highest/High: 1w, Medium: 2w, Low: 1m)\n- `/hygiene.triage-new` - Suggest triage for items in New status >1 week\n\n**Data Quality:**\n- `/hygiene.blocking-closed` - Find blocking tickets where blocked items are closed\n- `/hygiene.unassigned-progress` - Show in-progress tickets without assignee\n- `/hygiene.activity-type` - Suggest Activity Type for tickets missing this field\n\n## Jira API Integration\n\nAll commands use Jira REST API v3 with these environment variables:\n- `JIRA_URL` - Your Jira instance URL (e.g., https://company.atlassian.net)\n- `JIRA_EMAIL` - Your Jira email address\n- `JIRA_API_TOKEN` - Your Jira API token\n\nAuthentication: Basic Auth using base64(email:token)\nRate limiting: 0.5s delay between requests\nError handling: Retry on 429, validate all responses\n\n## Safety & Best Practices\n\n**Review-then-execute pattern:**\n1. Query and analyze tickets\n2. Write candidates to artifacts/jira-hygiene/candidates/\n3. Display summary to user\n4. Ask for explicit confirmation\n5. Execute operations only after confirmation\n6. Log all operations with timestamps to artifacts/jira-hygiene/operations/\n\n**Key safety rules:**\n- No destructive operations without confirmation\n- No modification of closed tickets (only unresolved)\n- Validate JQL queries before execution\n- Log all operations for audit trail\n- Respect rate limits (0.5s minimum between requests)\n- No sensitive data in logs (redact API tokens)\n- All operations are idempotent (safe to run multiple times)\n- No cross-project operations without explicit mapping\n\n**Dry-run support:**\nAll bulk commands support `--dry-run` flag to show what would happen without making changes.\n\n## Output Locations\n\nAll artifacts are written to `artifacts/jira-hygiene/`:\n- `config.json` - Project configuration and field metadata cache\n- `candidates/*.json` - Review candidates before bulk operations\n- `summaries/{epic-key}-{date}.md` - Generated activity summaries\n- `reports/*.md` - Read-only reports for data quality issues\n- `operations/*-{timestamp}.log` - Audit logs for all executed operations\n\n## Semantic Matching Algorithm\n\nFor linking and triage suggestions:\n1. Extract keywords from ticket summary/description (remove stopwords)\n2. Search using Jira text search: `text ~ \"keyword1 keyword2\"`\n3. Calculate match score: (matching_keywords / total_keywords) * 100\n4. Rank by score, suggest top matches\n5. Threshold: ≥50% = auto-suggest, <50% = suggest creating new item\n\n## Common JQL Patterns\n\nOrphaned stories: `project = PROJ AND issuetype = Story AND \"Epic Link\" is EMPTY`\nOrphaned epics: `project = PROJ AND issuetype = Epic AND \"Parent Link\" is EMPTY`\nStale tickets: `project = PROJ AND priority = PRIORITY AND updated < -Nd AND resolution = Unresolved`\nUntriaged: `project = PROJ AND status = New AND created < -7d`\nBlocking tickets: `project = PROJ AND issueFunction in linkedIssuesOf(\"project = PROJ\", \"blocks\") AND resolution = Unresolved`\nIn-progress unassigned: `project = PROJ AND status = \"In Progress\" AND assignee is EMPTY`\n\nBe helpful, efficient, and always prioritize safety in bulk operations.", "startupPrompt": "Greet the user and introduce yourself as their Jira hygiene assistant. Explain that you help maintain clean Jira projects through automated hygiene checks and safe bulk operations. Mention the key capabilities: linking orphaned tickets, generating activity summaries, closing stale items, and identifying data quality issues. Suggest starting with `/hygiene.setup` to configure the Jira connection and project settings, or ask what hygiene task they'd like to address.", "results": { "Configuration": "artifacts/jira-hygiene/config.json", diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md b/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md index 8619fec..76a8d4e 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md @@ -2,7 +2,7 @@ ## Purpose -Simple query to display all tickets marked with "Blocker" priority in the project. This highlights critical items that may be blocking other work. +Display all tickets that are blocking other tickets via "Blocks" issue links. This highlights items that are preventing other work from progressing. ## Prerequisites @@ -16,29 +16,37 @@ Simple query to display all tickets marked with "Blocker" priority in the projec 2. **Query blocking tickets**: ```jql - project = {PROJECT} AND priority = Blocker AND resolution = Unresolved + project = {PROJECT} AND issueFunction in linkedIssuesOf("project = {PROJECT}", "blocks") AND resolution = Unresolved ``` - - Fetch: key, summary, assignee, status, created, updated + - This finds tickets that have outward "blocks" links to other tickets + - Fetch: key, summary, assignee, status, created, updated, priority + - Also fetch issue links to see what tickets are being blocked - Order by updated descending (most recent first) + + **Alternative approach** (if issueFunction not available): + - Get all unresolved tickets + - For each, fetch issue links via `/rest/api/3/issue/{key}?fields=issuelinks` + - Filter tickets that have outward "blocks" type links 3. **Format as markdown table with Jira links**: ```markdown # Blocking Tickets in {PROJECT} - **Total**: N tickets + **Total**: N tickets blocking M other tickets **Generated**: {timestamp} - **[View in Jira]({JIRA_URL}/issues/?jql=project+%3D+{PROJECT}+AND+priority+%3D+Blocker+AND+resolution+%3D+Unresolved)** + **[View in Jira]({JIRA_URL}/issues/?jql=project+%3D+{PROJECT}+AND+issueFunction+in+linkedIssuesOf%28%22project+%3D+{PROJECT}%22%2C+%22blocks%22%29+AND+resolution+%3D+Unresolved)** - | Key | Summary | Assignee | Status | Age | Last Updated | - |-----|---------|----------|--------|-----|--------------| - | [PROJ-123]({JIRA_URL}/browse/PROJ-123) | Critical API failure | John Doe | In Progress | 5d | 2d ago | - | [PROJ-456]({JIRA_URL}/browse/PROJ-456) | Database migration blocked | Unassigned | To Do | 12d | 3d ago | + | Blocking Ticket | Summary | Blocks | Assignee | Status | Priority | Last Updated | + |-----------------|---------|--------|----------|--------|----------|--------------| + | [PROJ-123]({JIRA_URL}/browse/PROJ-123) | Database migration issue | [PROJ-145]({JIRA_URL}/browse/PROJ-145), [PROJ-167]({JIRA_URL}/browse/PROJ-167) | John Doe | In Progress | High | 2d ago | + | [PROJ-456]({JIRA_URL}/browse/PROJ-456) | Security audit | [PROJ-500]({JIRA_URL}/browse/PROJ-500) | Unassigned | To Do | Medium | 3d ago | ``` **Link format**: - Ticket links: `[{KEY}]({JIRA_URL}/browse/{KEY})` - Search link: `[View in Jira]({JIRA_URL}/issues/?jql={URL_ENCODED_JQL})` - URL-encode JQL: spaces → `+`, special chars → percent-encoded + - List blocked tickets in "Blocks" column as comma-separated links 4. **Write report**: - Save to `artifacts/jira-hygiene/reports/blocking-tickets.md` @@ -57,9 +65,9 @@ Simple query to display all tickets marked with "Blocker" priority in the projec ```markdown # Blocking Tickets in PROJ -**Total**: 3 tickets +**Total**: 3 tickets blocking 5 other tickets **Generated**: 2026-04-07 10:30 UTC -**[View in Jira](https://company.atlassian.net/issues/?jql=project+%3D+PROJ+AND+priority+%3D+Blocker+AND+resolution+%3D+Unresolved)** +**[View in Jira](https://company.atlassian.net/issues/?jql=project+%3D+PROJ+AND+issueFunction+in+linkedIssuesOf%28%22project+%3D+PROJ%22%2C+%22blocks%22%29+AND+resolution+%3D+Unresolved)** ## Summary @@ -69,19 +77,22 @@ Simple query to display all tickets marked with "Blocker" priority in the projec ## Tickets -| Key | Summary | Assignee | Status | Age | Last Updated | -|-----|---------|----------|--------|-----|--------------| -| [PROJ-123](https://company.atlassian.net/browse/PROJ-123) | Critical API failure in auth endpoint | John Doe | In Progress | 5d | 2d ago | -| [PROJ-456](https://company.atlassian.net/browse/PROJ-456) | Database migration blocked by schema lock | Unassigned | To Do | 12d | 3d ago | -| [PROJ-789](https://company.atlassian.net/browse/PROJ-789) | Production deployment failing | Jane Smith | Code Review | 3d | 1d ago | +| Blocking Ticket | Summary | Blocks | Assignee | Status | Priority | Last Updated | +|-----------------|---------|--------|----------|--------|----------|--------------| +| [PROJ-123](https://company.atlassian.net/browse/PROJ-123) | Critical API failure in auth endpoint | [PROJ-150](https://company.atlassian.net/browse/PROJ-150), [PROJ-151](https://company.atlassian.net/browse/PROJ-151) | John Doe | In Progress | High | 2d ago | +| [PROJ-456](https://company.atlassian.net/browse/PROJ-456) | Database migration blocked by schema lock | [PROJ-460](https://company.atlassian.net/browse/PROJ-460) | Unassigned | To Do | Medium | 3d ago | +| [PROJ-789](https://company.atlassian.net/browse/PROJ-789) | Production deployment failing | [PROJ-800](https://company.atlassian.net/browse/PROJ-800), [PROJ-801](https://company.atlassian.net/browse/PROJ-801) | Jane Smith | Code Review | High | 1d ago | ## Recommendations -- **[PROJ-456](https://company.atlassian.net/browse/PROJ-456)**: Assign to database team immediately (unassigned for 12 days) -- **[PROJ-123](https://company.atlassian.net/browse/PROJ-123)**: Follow up on progress (blocker for 5 days) +- **[PROJ-456](https://company.atlassian.net/browse/PROJ-456)**: Assign to database team immediately (unassigned, blocking [PROJ-460](https://company.atlassian.net/browse/PROJ-460)) +- **[PROJ-123](https://company.atlassian.net/browse/PROJ-123)**: Follow up on progress (blocking 2 tickets for 5 days) +- **[PROJ-789](https://company.atlassian.net/browse/PROJ-789)**: In code review, close to unblocking deployment work ``` ## Error Handling -- **No blocking tickets found**: Report "No blocking tickets in {PROJECT}" (good news!) +- **No blocking tickets found**: Report "No tickets are currently blocking other work in {PROJECT}" (good news!) +- **issueFunction not available**: Fall back to API approach (fetch all tickets, check issue links) - **Query failed**: Check JQL syntax, validate project key +- **Issue links unavailable**: Some Jira instances may restrict issue link access; note in report From e2fe6ceaeb9ad3042aca433d3b9f59344b6a36c7 Mon Sep 17 00:00:00 2001 From: Eliad Cohen Date: Tue, 7 Apr 2026 14:54:20 -0400 Subject: [PATCH 5/8] feat: include PR/MR activity in weekly summaries Enhance /hygiene.activity-summary to recursively gather details from linked Pull Requests and Merge Requests when generating summaries. Features: - Fetch PR/MR data from Jira development panel API - Support GitHub, GitLab, and Bitbucket integrations - Filter PR/MRs by update date (past 7 days only) - Include merged PRs, in-review PRs, and commit counts - Fallback to parsing PR/MR URLs from comments - Optional direct GitHub/GitLab API access via tokens Summary now includes: - Number of PRs merged vs in review - PR titles for context - Overall commit volume - Business-friendly language (avoids technical details) Optional environment variables: - GITHUB_TOKEN - For direct GitHub API access - GITLAB_TOKEN - For direct GitLab API access This provides stakeholders with concrete evidence of development progress beyond just Jira status changes. Co-Authored-By: Claude Sonnet 4.5 --- workflows/jira-hygiene/.ambient/ambient.json | 2 +- .../commands/hygiene.activity-summary.md | 114 ++++++++++++++++-- workflows/jira-hygiene/README.md | 9 +- 3 files changed, 115 insertions(+), 10 deletions(-) diff --git a/workflows/jira-hygiene/.ambient/ambient.json b/workflows/jira-hygiene/.ambient/ambient.json index 57e63af..587a087 100644 --- a/workflows/jira-hygiene/.ambient/ambient.json +++ b/workflows/jira-hygiene/.ambient/ambient.json @@ -1,7 +1,7 @@ { "name": "Jira Hygiene", "description": "Systematic workflow for maintaining Jira project hygiene. Links orphaned stories and epics, generates weekly activity summaries, closes stale tickets, suggests triage outcomes, and identifies data quality issues. Provides safe bulk operations with review-then-execute pattern.", - "systemPrompt": "You are a Jira hygiene specialist, helping teams maintain clean and well-organized Jira projects.\n\nWORKSPACE NAVIGATION:\n**CRITICAL: Follow these rules to avoid fumbling when looking for files.**\n\nStandard file locations (from workflow root):\n- Config: .ambient/ambient.json (ALWAYS at this path)\n- Commands: .claude/commands/*.md\n- Outputs: artifacts/jira-hygiene/\n\nTool selection rules:\n- Use Read for: Known paths, standard files, files you just created\n- Use Glob for: Discovery (finding multiple files by pattern)\n- Use Grep for: Content search\n\nNever glob for standard files:\n✅ DO: Read .ambient/ambient.json\n❌ DON'T: Glob **/ambient.json\n\nYour role is to:\n1. Maintain Jira project hygiene through systematic checks and bulk operations\n2. Link orphaned stories to epics and epics to initiatives\n3. Generate weekly activity summaries for epics and initiatives\n4. Identify and close stale tickets based on priority-specific thresholds\n5. Suggest triage outcomes for untriaged items\n6. Highlight data quality issues (missing assignees, activity types, blocking mismatches)\n7. Execute all bulk operations with review-then-execute pattern for safety\n\n## Available Commands\n\n**Setup & Configuration:**\n- `/hygiene.setup` - Validate Jira connection, configure project and initiative mapping\n\n**Linking Operations:**\n- `/hygiene.link-epics` - Link orphaned stories to epics (semantic matching, 50% threshold)\n- `/hygiene.link-initiatives` - Link orphaned epics to initiatives (cross-project search)\n\n**Activity & Reporting:**\n- `/hygiene.activity-summary` - Generate weekly activity summaries for epics/initiatives\n- `/hygiene.show-blocking` - Show tickets that are blocking other work via issue links\n\n**Bulk Operations:**\n- `/hygiene.close-stale` - Close stale tickets by priority (Highest/High: 1w, Medium: 2w, Low: 1m)\n- `/hygiene.triage-new` - Suggest triage for items in New status >1 week\n\n**Data Quality:**\n- `/hygiene.blocking-closed` - Find blocking tickets where blocked items are closed\n- `/hygiene.unassigned-progress` - Show in-progress tickets without assignee\n- `/hygiene.activity-type` - Suggest Activity Type for tickets missing this field\n\n## Jira API Integration\n\nAll commands use Jira REST API v3 with these environment variables:\n- `JIRA_URL` - Your Jira instance URL (e.g., https://company.atlassian.net)\n- `JIRA_EMAIL` - Your Jira email address\n- `JIRA_API_TOKEN` - Your Jira API token\n\nAuthentication: Basic Auth using base64(email:token)\nRate limiting: 0.5s delay between requests\nError handling: Retry on 429, validate all responses\n\n## Safety & Best Practices\n\n**Review-then-execute pattern:**\n1. Query and analyze tickets\n2. Write candidates to artifacts/jira-hygiene/candidates/\n3. Display summary to user\n4. Ask for explicit confirmation\n5. Execute operations only after confirmation\n6. Log all operations with timestamps to artifacts/jira-hygiene/operations/\n\n**Key safety rules:**\n- No destructive operations without confirmation\n- No modification of closed tickets (only unresolved)\n- Validate JQL queries before execution\n- Log all operations for audit trail\n- Respect rate limits (0.5s minimum between requests)\n- No sensitive data in logs (redact API tokens)\n- All operations are idempotent (safe to run multiple times)\n- No cross-project operations without explicit mapping\n\n**Dry-run support:**\nAll bulk commands support `--dry-run` flag to show what would happen without making changes.\n\n## Output Locations\n\nAll artifacts are written to `artifacts/jira-hygiene/`:\n- `config.json` - Project configuration and field metadata cache\n- `candidates/*.json` - Review candidates before bulk operations\n- `summaries/{epic-key}-{date}.md` - Generated activity summaries\n- `reports/*.md` - Read-only reports for data quality issues\n- `operations/*-{timestamp}.log` - Audit logs for all executed operations\n\n## Semantic Matching Algorithm\n\nFor linking and triage suggestions:\n1. Extract keywords from ticket summary/description (remove stopwords)\n2. Search using Jira text search: `text ~ \"keyword1 keyword2\"`\n3. Calculate match score: (matching_keywords / total_keywords) * 100\n4. Rank by score, suggest top matches\n5. Threshold: ≥50% = auto-suggest, <50% = suggest creating new item\n\n## Common JQL Patterns\n\nOrphaned stories: `project = PROJ AND issuetype = Story AND \"Epic Link\" is EMPTY`\nOrphaned epics: `project = PROJ AND issuetype = Epic AND \"Parent Link\" is EMPTY`\nStale tickets: `project = PROJ AND priority = PRIORITY AND updated < -Nd AND resolution = Unresolved`\nUntriaged: `project = PROJ AND status = New AND created < -7d`\nBlocking tickets: `project = PROJ AND issueFunction in linkedIssuesOf(\"project = PROJ\", \"blocks\") AND resolution = Unresolved`\nIn-progress unassigned: `project = PROJ AND status = \"In Progress\" AND assignee is EMPTY`\n\nBe helpful, efficient, and always prioritize safety in bulk operations.", + "systemPrompt": "You are a Jira hygiene specialist, helping teams maintain clean and well-organized Jira projects.\n\nWORKSPACE NAVIGATION:\n**CRITICAL: Follow these rules to avoid fumbling when looking for files.**\n\nStandard file locations (from workflow root):\n- Config: .ambient/ambient.json (ALWAYS at this path)\n- Commands: .claude/commands/*.md\n- Outputs: artifacts/jira-hygiene/\n\nTool selection rules:\n- Use Read for: Known paths, standard files, files you just created\n- Use Glob for: Discovery (finding multiple files by pattern)\n- Use Grep for: Content search\n\nNever glob for standard files:\n✅ DO: Read .ambient/ambient.json\n❌ DON'T: Glob **/ambient.json\n\nYour role is to:\n1. Maintain Jira project hygiene through systematic checks and bulk operations\n2. Link orphaned stories to epics and epics to initiatives\n3. Generate weekly activity summaries for epics and initiatives\n4. Identify and close stale tickets based on priority-specific thresholds\n5. Suggest triage outcomes for untriaged items\n6. Highlight data quality issues (missing assignees, activity types, blocking mismatches)\n7. Execute all bulk operations with review-then-execute pattern for safety\n\n## Available Commands\n\n**Setup & Configuration:**\n- `/hygiene.setup` - Validate Jira connection, configure project and initiative mapping\n\n**Linking Operations:**\n- `/hygiene.link-epics` - Link orphaned stories to epics (semantic matching, 50% threshold)\n- `/hygiene.link-initiatives` - Link orphaned epics to initiatives (cross-project search)\n\n**Activity & Reporting:**\n- `/hygiene.activity-summary` - Generate weekly activity summaries for epics/initiatives (includes PR/MR activity)\n- `/hygiene.show-blocking` - Show tickets that are blocking other work via issue links\n\n**Bulk Operations:**\n- `/hygiene.close-stale` - Close stale tickets by priority (Highest/High: 1w, Medium: 2w, Low: 1m)\n- `/hygiene.triage-new` - Suggest triage for items in New status >1 week\n\n**Data Quality:**\n- `/hygiene.blocking-closed` - Find blocking tickets where blocked items are closed\n- `/hygiene.unassigned-progress` - Show in-progress tickets without assignee\n- `/hygiene.activity-type` - Suggest Activity Type for tickets missing this field\n\n## Jira API Integration\n\nAll commands use Jira REST API v3 with these environment variables:\n- `JIRA_URL` - Your Jira instance URL (e.g., https://company.atlassian.net)\n- `JIRA_EMAIL` - Your Jira email address\n- `JIRA_API_TOKEN` - Your Jira API token\n\nAuthentication: Basic Auth using base64(email:token)\nRate limiting: 0.5s delay between requests\nError handling: Retry on 429, validate all responses\n\n## Safety & Best Practices\n\n**Review-then-execute pattern:**\n1. Query and analyze tickets\n2. Write candidates to artifacts/jira-hygiene/candidates/\n3. Display summary to user\n4. Ask for explicit confirmation\n5. Execute operations only after confirmation\n6. Log all operations with timestamps to artifacts/jira-hygiene/operations/\n\n**Key safety rules:**\n- No destructive operations without confirmation\n- No modification of closed tickets (only unresolved)\n- Validate JQL queries before execution\n- Log all operations for audit trail\n- Respect rate limits (0.5s minimum between requests)\n- No sensitive data in logs (redact API tokens)\n- All operations are idempotent (safe to run multiple times)\n- No cross-project operations without explicit mapping\n\n**Dry-run support:**\nAll bulk commands support `--dry-run` flag to show what would happen without making changes.\n\n## Output Locations\n\nAll artifacts are written to `artifacts/jira-hygiene/`:\n- `config.json` - Project configuration and field metadata cache\n- `candidates/*.json` - Review candidates before bulk operations\n- `summaries/{epic-key}-{date}.md` - Generated activity summaries\n- `reports/*.md` - Read-only reports for data quality issues\n- `operations/*-{timestamp}.log` - Audit logs for all executed operations\n\n## Semantic Matching Algorithm\n\nFor linking and triage suggestions:\n1. Extract keywords from ticket summary/description (remove stopwords)\n2. Search using Jira text search: `text ~ \"keyword1 keyword2\"`\n3. Calculate match score: (matching_keywords / total_keywords) * 100\n4. Rank by score, suggest top matches\n5. Threshold: ≥50% = auto-suggest, <50% = suggest creating new item\n\n## Common JQL Patterns\n\nOrphaned stories: `project = PROJ AND issuetype = Story AND \"Epic Link\" is EMPTY`\nOrphaned epics: `project = PROJ AND issuetype = Epic AND \"Parent Link\" is EMPTY`\nStale tickets: `project = PROJ AND priority = PRIORITY AND updated < -Nd AND resolution = Unresolved`\nUntriaged: `project = PROJ AND status = New AND created < -7d`\nBlocking tickets: `project = PROJ AND issueFunction in linkedIssuesOf(\"project = PROJ\", \"blocks\") AND resolution = Unresolved`\nIn-progress unassigned: `project = PROJ AND status = \"In Progress\" AND assignee is EMPTY`\n\nBe helpful, efficient, and always prioritize safety in bulk operations.", "startupPrompt": "Greet the user and introduce yourself as their Jira hygiene assistant. Explain that you help maintain clean Jira projects through automated hygiene checks and safe bulk operations. Mention the key capabilities: linking orphaned tickets, generating activity summaries, closing stale items, and identifying data quality issues. Suggest starting with `/hygiene.setup` to configure the Jira connection and project settings, or ask what hygiene task they'd like to address.", "results": { "Configuration": "artifacts/jira-hygiene/config.json", diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md b/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md index 92dbbb4..b8a14e5 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md @@ -9,6 +9,10 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi - `/hygiene.setup` must be run first - User should specify which epics/initiatives to summarize +**Optional** (for enhanced PR/MR summaries): +- `GITHUB_TOKEN` - For direct GitHub API access if Jira integration unavailable +- `GITLAB_TOKEN` - For direct GitLab API access if Jira integration unavailable + ## Process 1. **Load configuration**: @@ -43,13 +47,24 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi - Priority changes - Fetch comments: GET `/rest/api/3/issue/{childKey}/comment` - Count comments from past 7 days + + **Also check for linked MRs/PRs**: + - Fetch development info: GET `/rest/dev-status/1.0/issue/detail?issueId={issueId}&applicationType=github&dataType=pullrequest` + - Also check GitLab: `applicationType=gitlab&dataType=mergerequest` + - Parse PR/MR URLs from comments and description + - For each linked PR/MR with activity in past 7 days: + - Fetch PR details from GitHub/GitLab API + - Extract: status (open/merged/closed), commits added, reviews, last updated + - Note: PR/MR must have `updated_at` within past 7 days to include c. **Generate summary paragraph**: - - Template: "This week, {status_summary}. {assignment_summary}. {activity_summary}." + - Template: "This week, {status_summary}. {pr_summary}. {assignment_summary}. {activity_summary}." - Status summary: "X stories moved to In Progress, Y completed" - - Assignment summary: "Z new assignments" (if any) - - Activity summary: "N comments across M stories" (if significant) - - Keep to 2-4 sentences, business-friendly language + - PR/MR summary: "Z pull requests merged, N in review" (if any PR/MR activity) + - Assignment summary: "M new assignments" (if any) + - Activity summary: "P comments across Q stories" (if significant) + - Keep to 3-5 sentences, business-friendly language + - Prioritize PR/MR activity in summary (shows concrete progress) d. **Write summary to file**: - Save to `artifacts/jira-hygiene/summaries/{epic-key}-{date}.md` @@ -87,13 +102,17 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi ## Summary -This week, 3 stories moved to In Progress and 2 were completed. The team made 4 new assignments across the authentication work. There were 12 comments discussing API integration challenges and OAuth implementation details. +This week, 3 stories moved to In Progress and 2 were completed. The team merged 2 pull requests and has 3 PRs in active review. There were 4 new assignments and 12 comments discussing API integration challenges and OAuth implementation details. ## Activity Breakdown - Status transitions: 5 changes - New → In Progress: STORY-101, STORY-102, STORY-103 - In Progress → Done: STORY-98, STORY-99 +- Pull Requests: 5 active + - Merged: PR#145 (OAuth integration), PR#148 (Token refresh) + - In Review: PR#150 (SSO support), PR#151 (Session management), PR#152 (Password reset) + - Commits this week: 18 commits across 5 PRs - Assignments: 4 new - Comments: 12 across 6 stories ``` @@ -101,20 +120,34 @@ This week, 3 stories moved to In Progress and 2 were completed. The team made 4 ## Summary Generation Guidelines **Good summary**: -> "This week, 3 stories moved to In Progress and 2 were completed. The team made 4 new assignments. Discussion focused on OAuth implementation with 8 comments across 4 stories." +> "This week, 3 stories moved to In Progress and 2 were completed. The team merged 2 pull requests for OAuth integration and has 3 PRs in active review. There were 4 new assignments and 8 comments focused on implementation details." **Bad summary** (too technical): -> "This week, STORY-101 transitioned from status ID 10001 to 10002. User john.doe was assigned to STORY-102..." +> "This week, STORY-101 transitioned from status ID 10001 to 10002. User john.doe was assigned to STORY-102. Commit SHA abc123 was pushed to PR #145..." **Focus on**: - High-level progress (stories moved, completed) +- PR/MR activity (merged, in review, commit volume) - Team activity (assignments, discussions) - Notable trends (if detectable) **Avoid**: - Listing every ticket key -- Technical jargon +- Commit SHAs or technical identifiers - Implementation details +- Individual developer names (use "the team") + +**PR/MR Details to Include**: +- Number merged vs in review +- PR titles (if descriptive, e.g., "OAuth integration") +- Significant milestones (e.g., "first PR merged this epic") +- Overall commit volume (e.g., "18 commits this week") + +**PR/MR Details to Exclude**: +- Commit messages +- Code review comments +- Individual file changes +- Specific reviewers ## Error Handling @@ -122,3 +155,68 @@ This week, 3 stories moved to In Progress and 2 were completed. The team made 4 - **No activity**: "No significant activity this week" - **Changelog unavailable**: Fall back to issue update dates - **Comment fetch failed**: Skip comment count, note in log +- **Development info unavailable**: Not all Jira instances have GitHub/GitLab integration; skip PR/MR section +- **PR/MR API access denied**: May need GitHub/GitLab tokens; proceed without PR/MR data + +## GitHub/GitLab Integration + +### Jira Development Panel API + +**Endpoint**: `/rest/dev-status/1.0/issue/detail?issueId={issueId}&applicationType={type}&dataType={dataType}` + +**Supported integrations**: +- GitHub: `applicationType=github&dataType=pullrequest` +- GitLab: `applicationType=gitlab&dataType=mergerequest` +- Bitbucket: `applicationType=bitbucket&dataType=pullrequest` + +**Response includes**: +- PR/MR URLs +- Status (open, merged, closed) +- Last updated timestamp +- Review status + +### GitHub API (if direct access needed) + +**Environment variables** (optional): +- `GITHUB_TOKEN` - GitHub personal access token +- `GITHUB_API_URL` - Default: https://api.github.com + +**Endpoint**: `GET /repos/{owner}/{repo}/pulls/{number}` + +**Fetch**: +- `state` (open, closed) +- `merged_at` (if merged) +- `updated_at` (filter by this) +- `commits` count +- `additions`, `deletions` (code churn) +- `reviews` count + +### GitLab API (if direct access needed) + +**Environment variables** (optional): +- `GITLAB_TOKEN` - GitLab personal access token +- `GITLAB_API_URL` - Default: https://gitlab.com/api/v4 + +**Endpoint**: `GET /projects/{id}/merge_requests/{iid}` + +**Fetch**: +- `state` (opened, merged, closed) +- `merged_at` (if merged) +- `updated_at` (filter by this) +- `user_notes_count` (comments) + +### Date Filtering + +Only include PR/MR in summary if: +- `updated_at` >= (now - 7 days) +- OR `merged_at` >= (now - 7 days) + +This ensures only recent PR/MR activity is included in weekly summary. + +### Fallback: Parse URLs from Comments + +If Jira development panel is unavailable: +1. Search issue comments for GitHub/GitLab URLs +2. Extract PR/MR numbers from URLs (e.g., `/pull/123`, `/merge_requests/456`) +3. Fetch details directly from GitHub/GitLab API +4. Filter by update date diff --git a/workflows/jira-hygiene/README.md b/workflows/jira-hygiene/README.md index bd1899f..8f37512 100644 --- a/workflows/jira-hygiene/README.md +++ b/workflows/jira-hygiene/README.md @@ -128,13 +128,20 @@ Generate weekly activity summaries for epics/initiatives. **What it does**: - Analyzes child items for the past 7 days - Tracks status transitions, assignments, comments +- **Includes linked PR/MR activity** (merged, in review, commits) - Generates business-friendly summary paragraph - Posts summary as comment on epic/initiative **Review-then-execute**: Yes (shows summaries before posting) +**PR/MR Integration**: +- Automatically detects linked GitHub/GitLab PRs via Jira development panel +- Falls back to parsing PR URLs from comments +- Filters by last update date (past 7 days only) +- Optional: Set `GITHUB_TOKEN` or `GITLAB_TOKEN` for direct API access + **Example summary**: -> This week, 3 stories moved to In Progress and 2 were completed. The team made 4 new assignments. Discussion focused on OAuth implementation with 8 comments across 4 stories. +> This week, 3 stories moved to In Progress and 2 were completed. The team merged 2 pull requests for OAuth integration and has 3 PRs in active review. There were 4 new assignments and 8 comments focused on implementation details. --- From 4860ecc7d8231a14a017cdb24c2c2c7520424f22 Mon Sep 17 00:00:00 2001 From: Eliad Cohen Date: Tue, 7 Apr 2026 15:01:43 -0400 Subject: [PATCH 6/8] feat: add master hygiene report with health score MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add /hygiene.report command that generates comprehensive dashboard combining all hygiene checks into a single report. Features: - Runs all hygiene checks (read-only, no modifications) - Calculates health score (0-100) based on issue counts - Provides executive summary with issue breakdown - Lists top issues in each category with Jira links - Recommends which commands to run next - Supports custom output path and HTML format Health Score Ratings: - 90-100: Excellent 🟢 - 70-89: Good 🟡 - 50-69: Needs Attention 🟠 - 0-49: Critical 🔴 Categories Included: - Orphaned stories and epics - Blocking tickets - Stale tickets (by priority with thresholds) - Untriaged items - Blocking-closed mismatches - In-progress unassigned tickets - Missing activity types Scoring deducts points based on issue type severity: - Blocking tickets: -2 points each - Orphaned epics: -1 point each - Stale High priority: -1 point each - In-progress unassigned: -1 point each - And more... Use cases: - Weekly hygiene check - Stakeholder reporting - Project health dashboard - Identifying where to focus cleanup efforts Arguments: - --output : Custom report location - --format : Output format This command now brings the total to 11 specialized commands for comprehensive Jira hygiene management. Co-Authored-By: Claude Sonnet 4.5 --- workflows/jira-hygiene/.ambient/ambient.json | 5 +- .../.claude/commands/hygiene.report.md | 388 ++++++++++++++++++ workflows/jira-hygiene/CLAUDE.md | 3 +- workflows/jira-hygiene/README.md | 51 +++ 4 files changed, 444 insertions(+), 3 deletions(-) create mode 100644 workflows/jira-hygiene/.claude/commands/hygiene.report.md diff --git a/workflows/jira-hygiene/.ambient/ambient.json b/workflows/jira-hygiene/.ambient/ambient.json index 587a087..91fd7dd 100644 --- a/workflows/jira-hygiene/.ambient/ambient.json +++ b/workflows/jira-hygiene/.ambient/ambient.json @@ -1,7 +1,7 @@ { "name": "Jira Hygiene", "description": "Systematic workflow for maintaining Jira project hygiene. Links orphaned stories and epics, generates weekly activity summaries, closes stale tickets, suggests triage outcomes, and identifies data quality issues. Provides safe bulk operations with review-then-execute pattern.", - "systemPrompt": "You are a Jira hygiene specialist, helping teams maintain clean and well-organized Jira projects.\n\nWORKSPACE NAVIGATION:\n**CRITICAL: Follow these rules to avoid fumbling when looking for files.**\n\nStandard file locations (from workflow root):\n- Config: .ambient/ambient.json (ALWAYS at this path)\n- Commands: .claude/commands/*.md\n- Outputs: artifacts/jira-hygiene/\n\nTool selection rules:\n- Use Read for: Known paths, standard files, files you just created\n- Use Glob for: Discovery (finding multiple files by pattern)\n- Use Grep for: Content search\n\nNever glob for standard files:\n✅ DO: Read .ambient/ambient.json\n❌ DON'T: Glob **/ambient.json\n\nYour role is to:\n1. Maintain Jira project hygiene through systematic checks and bulk operations\n2. Link orphaned stories to epics and epics to initiatives\n3. Generate weekly activity summaries for epics and initiatives\n4. Identify and close stale tickets based on priority-specific thresholds\n5. Suggest triage outcomes for untriaged items\n6. Highlight data quality issues (missing assignees, activity types, blocking mismatches)\n7. Execute all bulk operations with review-then-execute pattern for safety\n\n## Available Commands\n\n**Setup & Configuration:**\n- `/hygiene.setup` - Validate Jira connection, configure project and initiative mapping\n\n**Linking Operations:**\n- `/hygiene.link-epics` - Link orphaned stories to epics (semantic matching, 50% threshold)\n- `/hygiene.link-initiatives` - Link orphaned epics to initiatives (cross-project search)\n\n**Activity & Reporting:**\n- `/hygiene.activity-summary` - Generate weekly activity summaries for epics/initiatives (includes PR/MR activity)\n- `/hygiene.show-blocking` - Show tickets that are blocking other work via issue links\n\n**Bulk Operations:**\n- `/hygiene.close-stale` - Close stale tickets by priority (Highest/High: 1w, Medium: 2w, Low: 1m)\n- `/hygiene.triage-new` - Suggest triage for items in New status >1 week\n\n**Data Quality:**\n- `/hygiene.blocking-closed` - Find blocking tickets where blocked items are closed\n- `/hygiene.unassigned-progress` - Show in-progress tickets without assignee\n- `/hygiene.activity-type` - Suggest Activity Type for tickets missing this field\n\n## Jira API Integration\n\nAll commands use Jira REST API v3 with these environment variables:\n- `JIRA_URL` - Your Jira instance URL (e.g., https://company.atlassian.net)\n- `JIRA_EMAIL` - Your Jira email address\n- `JIRA_API_TOKEN` - Your Jira API token\n\nAuthentication: Basic Auth using base64(email:token)\nRate limiting: 0.5s delay between requests\nError handling: Retry on 429, validate all responses\n\n## Safety & Best Practices\n\n**Review-then-execute pattern:**\n1. Query and analyze tickets\n2. Write candidates to artifacts/jira-hygiene/candidates/\n3. Display summary to user\n4. Ask for explicit confirmation\n5. Execute operations only after confirmation\n6. Log all operations with timestamps to artifacts/jira-hygiene/operations/\n\n**Key safety rules:**\n- No destructive operations without confirmation\n- No modification of closed tickets (only unresolved)\n- Validate JQL queries before execution\n- Log all operations for audit trail\n- Respect rate limits (0.5s minimum between requests)\n- No sensitive data in logs (redact API tokens)\n- All operations are idempotent (safe to run multiple times)\n- No cross-project operations without explicit mapping\n\n**Dry-run support:**\nAll bulk commands support `--dry-run` flag to show what would happen without making changes.\n\n## Output Locations\n\nAll artifacts are written to `artifacts/jira-hygiene/`:\n- `config.json` - Project configuration and field metadata cache\n- `candidates/*.json` - Review candidates before bulk operations\n- `summaries/{epic-key}-{date}.md` - Generated activity summaries\n- `reports/*.md` - Read-only reports for data quality issues\n- `operations/*-{timestamp}.log` - Audit logs for all executed operations\n\n## Semantic Matching Algorithm\n\nFor linking and triage suggestions:\n1. Extract keywords from ticket summary/description (remove stopwords)\n2. Search using Jira text search: `text ~ \"keyword1 keyword2\"`\n3. Calculate match score: (matching_keywords / total_keywords) * 100\n4. Rank by score, suggest top matches\n5. Threshold: ≥50% = auto-suggest, <50% = suggest creating new item\n\n## Common JQL Patterns\n\nOrphaned stories: `project = PROJ AND issuetype = Story AND \"Epic Link\" is EMPTY`\nOrphaned epics: `project = PROJ AND issuetype = Epic AND \"Parent Link\" is EMPTY`\nStale tickets: `project = PROJ AND priority = PRIORITY AND updated < -Nd AND resolution = Unresolved`\nUntriaged: `project = PROJ AND status = New AND created < -7d`\nBlocking tickets: `project = PROJ AND issueFunction in linkedIssuesOf(\"project = PROJ\", \"blocks\") AND resolution = Unresolved`\nIn-progress unassigned: `project = PROJ AND status = \"In Progress\" AND assignee is EMPTY`\n\nBe helpful, efficient, and always prioritize safety in bulk operations.", + "systemPrompt": "You are a Jira hygiene specialist, helping teams maintain clean and well-organized Jira projects.\n\nWORKSPACE NAVIGATION:\n**CRITICAL: Follow these rules to avoid fumbling when looking for files.**\n\nStandard file locations (from workflow root):\n- Config: .ambient/ambient.json (ALWAYS at this path)\n- Commands: .claude/commands/*.md\n- Outputs: artifacts/jira-hygiene/\n\nTool selection rules:\n- Use Read for: Known paths, standard files, files you just created\n- Use Glob for: Discovery (finding multiple files by pattern)\n- Use Grep for: Content search\n\nNever glob for standard files:\n✅ DO: Read .ambient/ambient.json\n❌ DON'T: Glob **/ambient.json\n\nYour role is to:\n1. Maintain Jira project hygiene through systematic checks and bulk operations\n2. Link orphaned stories to epics and epics to initiatives\n3. Generate weekly activity summaries for epics and initiatives\n4. Identify and close stale tickets based on priority-specific thresholds\n5. Suggest triage outcomes for untriaged items\n6. Highlight data quality issues (missing assignees, activity types, blocking mismatches)\n7. Execute all bulk operations with review-then-execute pattern for safety\n\n## Available Commands\n\n**Setup & Configuration:**\n- `/hygiene.setup` - Validate Jira connection, configure project and initiative mapping\n\n**Linking Operations:**\n- `/hygiene.link-epics` - Link orphaned stories to epics (semantic matching, 50% threshold)\n- `/hygiene.link-initiatives` - Link orphaned epics to initiatives (cross-project search)\n\n**Activity & Reporting:**\n- `/hygiene.report` - Generate master hygiene report with health score and all checks\n- `/hygiene.activity-summary` - Generate weekly activity summaries for epics/initiatives (includes PR/MR activity)\n- `/hygiene.show-blocking` - Show tickets that are blocking other work via issue links\n\n**Bulk Operations:**\n- `/hygiene.close-stale` - Close stale tickets by priority (Highest/High: 1w, Medium: 2w, Low: 1m)\n- `/hygiene.triage-new` - Suggest triage for items in New status >1 week\n\n**Data Quality:**\n- `/hygiene.blocking-closed` - Find blocking tickets where blocked items are closed\n- `/hygiene.unassigned-progress` - Show in-progress tickets without assignee\n- `/hygiene.activity-type` - Suggest Activity Type for tickets missing this field\n\n## Jira API Integration\n\nAll commands use Jira REST API v3 with these environment variables:\n- `JIRA_URL` - Your Jira instance URL (e.g., https://company.atlassian.net)\n- `JIRA_EMAIL` - Your Jira email address\n- `JIRA_API_TOKEN` - Your Jira API token\n\nAuthentication: Basic Auth using base64(email:token)\nRate limiting: 0.5s delay between requests\nError handling: Retry on 429, validate all responses\n\n## Safety & Best Practices\n\n**Review-then-execute pattern:**\n1. Query and analyze tickets\n2. Write candidates to artifacts/jira-hygiene/candidates/\n3. Display summary to user\n4. Ask for explicit confirmation\n5. Execute operations only after confirmation\n6. Log all operations with timestamps to artifacts/jira-hygiene/operations/\n\n**Key safety rules:**\n- No destructive operations without confirmation\n- No modification of closed tickets (only unresolved)\n- Validate JQL queries before execution\n- Log all operations for audit trail\n- Respect rate limits (0.5s minimum between requests)\n- No sensitive data in logs (redact API tokens)\n- All operations are idempotent (safe to run multiple times)\n- No cross-project operations without explicit mapping\n\n**Dry-run support:**\nAll bulk commands support `--dry-run` flag to show what would happen without making changes.\n\n## Output Locations\n\nAll artifacts are written to `artifacts/jira-hygiene/`:\n- `config.json` - Project configuration and field metadata cache\n- `candidates/*.json` - Review candidates before bulk operations\n- `summaries/{epic-key}-{date}.md` - Generated activity summaries\n- `reports/*.md` - Read-only reports for data quality issues\n- `operations/*-{timestamp}.log` - Audit logs for all executed operations\n\n## Semantic Matching Algorithm\n\nFor linking and triage suggestions:\n1. Extract keywords from ticket summary/description (remove stopwords)\n2. Search using Jira text search: `text ~ \"keyword1 keyword2\"`\n3. Calculate match score: (matching_keywords / total_keywords) * 100\n4. Rank by score, suggest top matches\n5. Threshold: ≥50% = auto-suggest, <50% = suggest creating new item\n\n## Common JQL Patterns\n\nOrphaned stories: `project = PROJ AND issuetype = Story AND \"Epic Link\" is EMPTY`\nOrphaned epics: `project = PROJ AND issuetype = Epic AND \"Parent Link\" is EMPTY`\nStale tickets: `project = PROJ AND priority = PRIORITY AND updated < -Nd AND resolution = Unresolved`\nUntriaged: `project = PROJ AND status = New AND created < -7d`\nBlocking tickets: `project = PROJ AND issueFunction in linkedIssuesOf(\"project = PROJ\", \"blocks\") AND resolution = Unresolved`\nIn-progress unassigned: `project = PROJ AND status = \"In Progress\" AND assignee is EMPTY`\n\nBe helpful, efficient, and always prioritize safety in bulk operations.", "startupPrompt": "Greet the user and introduce yourself as their Jira hygiene assistant. Explain that you help maintain clean Jira projects through automated hygiene checks and safe bulk operations. Mention the key capabilities: linking orphaned tickets, generating activity summaries, closing stale items, and identifying data quality issues. Suggest starting with `/hygiene.setup` to configure the Jira connection and project settings, or ask what hygiene task they'd like to address.", "results": { "Configuration": "artifacts/jira-hygiene/config.json", @@ -14,6 +14,7 @@ "Blocking Tickets Report": "artifacts/jira-hygiene/reports/blocking-tickets.md", "Blocking-Closed Mismatch Report": "artifacts/jira-hygiene/reports/blocking-closed-mismatch.md", "Unassigned Progress Report": "artifacts/jira-hygiene/reports/unassigned-progress.md", - "Operation Logs": "artifacts/jira-hygiene/operations/*.log" + "Operation Logs": "artifacts/jira-hygiene/operations/*.log", + "Master Hygiene Report": "artifacts/jira-hygiene/reports/master-report.md" } } diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.report.md b/workflows/jira-hygiene/.claude/commands/hygiene.report.md new file mode 100644 index 0000000..71379a1 --- /dev/null +++ b/workflows/jira-hygiene/.claude/commands/hygiene.report.md @@ -0,0 +1,388 @@ +# /hygiene.report - Generate Master Hygiene Report + +## Purpose + +Generate a comprehensive master report that combines all hygiene checks into a single dashboard view. This provides an at-a-glance overview of all project hygiene issues. + +## Prerequisites + +- `/hygiene.setup` must be run first + +## Arguments + +Optional: +- `--output ` - Custom output path (default: artifacts/jira-hygiene/reports/master-report.md) +- `--format ` - Output format (default: md) + +## Process + +1. **Load configuration**: + - Read `artifacts/jira-hygiene/config.json` + - Extract project key and settings + +2. **Run all hygiene checks** (read-only queries): + + a. **Orphaned Stories**: + ```jql + project = {PROJECT} AND issuetype = Story AND "Epic Link" is EMPTY AND resolution = Unresolved + ``` + - Count total orphaned stories + - List top 5 by age + + b. **Orphaned Epics**: + ```jql + project = {PROJECT} AND issuetype = Epic AND "Parent Link" is EMPTY AND resolution = Unresolved + ``` + - Count total orphaned epics + - List top 5 by age + + c. **Blocking Tickets**: + ```jql + project = {PROJECT} AND issueFunction in linkedIssuesOf("project = {PROJECT}", "blocks") AND resolution = Unresolved + ``` + - Count total blocking tickets + - Count tickets being blocked + - List all with blocked ticket counts + + d. **Stale Tickets** (by priority): + - Apply configured thresholds + - Count by priority level + - List top 5 oldest per priority + + e. **Untriaged Items**: + ```jql + project = {PROJECT} AND status = New AND created < -7d AND resolution = Unresolved + ``` + - Count total untriaged + - List top 5 by age + + f. **Blocking-Closed Mismatches**: + - Check blocking tickets where all blocked items are closed + - Count mismatches + - List all + + g. **In-Progress Unassigned**: + ```jql + project = {PROJECT} AND status = "In Progress" AND assignee is EMPTY AND resolution = Unresolved + ``` + - Count total + - List all + + h. **Missing Activity Type**: + ```jql + project = {PROJECT} AND "Activity Type" is EMPTY AND resolution = Unresolved + ``` + - Count total + - List top 10 by priority + +3. **Calculate health score**: + + **Scoring formula**: + - Start with 100 points + - Deduct points for each issue: + - Orphaned story: -0.5 points + - Orphaned epic: -1 point + - Blocking ticket: -2 points + - Stale ticket (High): -1 point + - Stale ticket (Medium): -0.5 points + - Stale ticket (Low): -0.25 points + - Untriaged item: -0.5 points + - Blocking-closed mismatch: -1 point + - In-progress unassigned: -1 point + - Missing activity type: -0.25 points + - Minimum score: 0 + + **Score interpretation**: + - 90-100: Excellent (🟢) + - 70-89: Good (🟡) + - 50-69: Needs Attention (🟠) + - 0-49: Critical (🔴) + +4. **Generate master report**: + - Write to `artifacts/jira-hygiene/reports/master-report.md` + - Include: + - Executive summary with health score + - Quick stats dashboard + - Detailed sections for each category + - Recommended actions (which commands to run) + - Links to detailed reports + - JQL search links for each category + +5. **Display summary**: + ``` + Project Hygiene Report: {PROJECT} + Health Score: {SCORE}/100 ({RATING}) + + Issues Found: + • {N} orphaned stories + • {N} orphaned epics + • {N} blocking tickets + • {N} stale tickets + • {N} untriaged items + • {N} blocking-closed mismatches + • {N} in-progress unassigned + • {N} missing activity types + + Full report: artifacts/jira-hygiene/reports/master-report.md + ``` + +## Output + +- `artifacts/jira-hygiene/reports/master-report.md` (or custom path) + +## Example Master Report + +```markdown +# Jira Hygiene Master Report: PROJ + +**Generated**: 2026-04-07 10:30 UTC +**Health Score**: 73/100 🟡 Good +**[View Project in Jira](https://company.atlassian.net/projects/PROJ)** + +--- + +## Executive Summary + +Your project has **good** overall hygiene with some areas needing attention. The main issues are: +- 15 orphaned stories need epic links +- 8 stale medium-priority tickets ready for closure +- 3 blocking tickets preventing other work + +**Recommended Actions**: +1. Run `/hygiene.link-epics` to link 15 orphaned stories +2. Run `/hygiene.close-stale` to close 12 stale tickets +3. Review 3 blocking tickets manually + +--- + +## Quick Stats Dashboard + +| Category | Count | Status | Action | +|----------|-------|--------|--------| +| Orphaned Stories | 15 | 🟡 | [Link to epics](#orphaned-stories) | +| Orphaned Epics | 2 | 🟢 | [Link to initiatives](#orphaned-epics) | +| Blocking Tickets | 3 | 🟡 | [Review](#blocking-tickets) | +| Stale Tickets | 12 | 🟠 | [Close stale](#stale-tickets) | +| Untriaged Items | 5 | 🟡 | [Triage](#untriaged-items) | +| Blocking-Closed | 1 | 🟢 | [Review](#blocking-closed-mismatches) | +| In-Progress Unassigned | 2 | 🟢 | [Assign](#in-progress-unassigned) | +| Missing Activity Type | 8 | 🟡 | [Set type](#missing-activity-type) | + +**Status Legend**: 🟢 Good (0-5) | 🟡 Monitor (6-15) | 🟠 Action Needed (16-30) | 🔴 Critical (30+) + +--- + +## Orphaned Stories + +**Count**: 15 stories +**Impact**: Stories without epic links are hard to organize and prioritize +**[View in Jira](https://company.atlassian.net/issues/?jql=project+%3D+PROJ+AND+issuetype+%3D+Story+AND+%22Epic+Link%22+is+EMPTY)** + +**Oldest 5**: + +| Story | Summary | Age | Priority | +|-------|---------|-----|----------| +| [PROJ-123](https://company.atlassian.net/browse/PROJ-123) | Implement user login | 45d | High | +| [PROJ-145](https://company.atlassian.net/browse/PROJ-145) | Add export feature | 38d | Medium | +| [PROJ-167](https://company.atlassian.net/browse/PROJ-167) | Fix broken link | 32d | Low | +| [PROJ-189](https://company.atlassian.net/browse/PROJ-189) | Update documentation | 28d | Low | +| [PROJ-201](https://company.atlassian.net/browse/PROJ-201) | Improve performance | 25d | High | + +**Recommended Action**: Run `/hygiene.link-epics` + +--- + +## Orphaned Epics + +**Count**: 2 epics +**Impact**: Epics without initiative links lack strategic alignment +**[View in Jira](https://company.atlassian.net/issues/?jql=project+%3D+PROJ+AND+issuetype+%3D+Epic+AND+%22Parent+Link%22+is+EMPTY)** + +**All Orphaned Epics**: + +| Epic | Summary | Age | Story Count | +|------|---------|-----|-------------| +| [EPIC-12](https://company.atlassian.net/browse/EPIC-12) | Payment Integration | 60d | 8 stories | +| [EPIC-15](https://company.atlassian.net/browse/EPIC-15) | Mobile App | 45d | 5 stories | + +**Recommended Action**: Run `/hygiene.link-initiatives` + +--- + +## Blocking Tickets + +**Count**: 3 tickets blocking 5 other tickets +**Impact**: Work is blocked, preventing progress on 5 tickets +**[View in Jira](https://company.atlassian.net/issues/?jql=project+%3D+PROJ+AND+issueFunction+in+linkedIssuesOf%28%22project+%3D+PROJ%22%2C+%22blocks%22%29)** + +**All Blocking Tickets**: + +| Blocking Ticket | Summary | Blocks | Assignee | Status | +|-----------------|---------|--------|----------|--------| +| [PROJ-50](https://company.atlassian.net/browse/PROJ-50) | Database migration | [PROJ-51](https://company.atlassian.net/browse/PROJ-51), [PROJ-52](https://company.atlassian.net/browse/PROJ-52) | John Doe | In Progress | +| [PROJ-75](https://company.atlassian.net/browse/PROJ-75) | Security audit | [PROJ-80](https://company.atlassian.net/browse/PROJ-80) | Unassigned | To Do | +| [PROJ-90](https://company.atlassian.net/browse/PROJ-90) | API changes | [PROJ-91](https://company.atlassian.net/browse/PROJ-91), [PROJ-92](https://company.atlassian.net/browse/PROJ-92) | Jane Smith | Code Review | + +**Recommended Action**: Review progress, assign unassigned blockers + +--- + +## Stale Tickets + +**Count**: 12 tickets (by priority) +**Impact**: Cluttering backlog, unclear if still relevant + +**Breakdown by Priority**: + +| Priority | Threshold | Count | Oldest | +|----------|-----------|-------|--------| +| High | 7 days | 2 | 15d | +| Medium | 14 days | 8 | 45d | +| Low | 30 days | 2 | 60d | + +**Top 5 Oldest**: + +| Ticket | Summary | Priority | Days Stale | +|--------|---------|----------|------------| +| [PROJ-100](https://company.atlassian.net/browse/PROJ-100) | Old feature request | Low | 60d | +| [PROJ-110](https://company.atlassian.net/browse/PROJ-110) | Performance issue | Medium | 45d | +| [PROJ-120](https://company.atlassian.net/browse/PROJ-120) | UI bug | Medium | 38d | +| [PROJ-130](https://company.atlassian.net/browse/PROJ-130) | Documentation update | Medium | 32d | +| [PROJ-140](https://company.atlassian.net/browse/PROJ-140) | Integration request | Medium | 28d | + +**Recommended Action**: Run `/hygiene.close-stale` + +--- + +## Untriaged Items + +**Count**: 5 items in "New" status for >7 days +**Impact**: Backlog not properly prioritized +**[View in Jira](https://company.atlassian.net/issues/?jql=project+%3D+PROJ+AND+status+%3D+New+AND+created+%3C+-7d)** + +**All Untriaged**: + +| Ticket | Summary | Age | Reporter | +|--------|---------|-----|----------| +| [PROJ-200](https://company.atlassian.net/browse/PROJ-200) | Add export feature | 12d | John Doe | +| [PROJ-201](https://company.atlassian.net/browse/PROJ-201) | Fix broken link | 10d | Jane Smith | +| [PROJ-202](https://company.atlassian.net/browse/PROJ-202) | Improve performance | 9d | Bob Johnson | +| [PROJ-203](https://company.atlassian.net/browse/PROJ-203) | New integration | 8d | Alice Lee | +| [PROJ-204](https://company.atlassian.net/browse/PROJ-204) | Update docs | 8d | John Doe | + +**Recommended Action**: Run `/hygiene.triage-new` + +--- + +## Blocking-Closed Mismatches + +**Count**: 1 ticket +**Impact**: Blocker may be ready to close + +**All Mismatches**: + +| Blocking Ticket | Summary | Blocks (All Closed) | +|-----------------|---------|---------------------| +| [PROJ-300](https://company.atlassian.net/browse/PROJ-300) | Security audit | [PROJ-305](https://company.atlassian.net/browse/PROJ-305) (closed 5d ago) | + +**Recommended Action**: Review and close or update links + +--- + +## In-Progress Unassigned + +**Count**: 2 tickets +**Impact**: Unclear ownership, work may be abandoned +**[View in Jira](https://company.atlassian.net/issues/?jql=project+%3D+PROJ+AND+status+%3D+%22In+Progress%22+AND+assignee+is+EMPTY)** + +**All Unassigned**: + +| Ticket | Summary | Status | Age | +|--------|---------|--------|-----| +| [PROJ-400](https://company.atlassian.net/browse/PROJ-400) | Refactor module | In Progress | 8d | +| [PROJ-401](https://company.atlassian.net/browse/PROJ-401) | API endpoint | In Progress | 5d | + +**Recommended Action**: Assign or move back to backlog + +--- + +## Missing Activity Type + +**Count**: 8 tickets +**Impact**: Reporting and categorization incomplete + +**Top 10 by Priority**: + +| Ticket | Summary | Priority | Issue Type | +|--------|---------|----------|------------| +| [PROJ-500](https://company.atlassian.net/browse/PROJ-500) | Fix login bug | High | Bug | +| [PROJ-501](https://company.atlassian.net/browse/PROJ-501) | Document API | Medium | Task | +| [PROJ-502](https://company.atlassian.net/browse/PROJ-502) | Add feature | Medium | Story | +| [PROJ-503](https://company.atlassian.net/browse/PROJ-503) | Update system | Low | Task | +| [PROJ-504](https://company.atlassian.net/browse/PROJ-504) | Research spike | Low | Task | +| [PROJ-505](https://company.atlassian.net/browse/PROJ-505) | Test automation | Low | Task | + +**Recommended Action**: Run `/hygiene.activity-type` + +--- + +## Health Score Breakdown + +**Total Score**: 73/100 🟡 Good + +**Deductions**: +- Orphaned stories (15 × 0.5): -7.5 points +- Orphaned epics (2 × 1): -2 points +- Blocking tickets (3 × 2): -6 points +- Stale High (2 × 1): -2 points +- Stale Medium (8 × 0.5): -4 points +- Stale Low (2 × 0.25): -0.5 points +- Untriaged (5 × 0.5): -2.5 points +- Blocking-closed (1 × 1): -1 point +- In-progress unassigned (2 × 1): -2 points +- Missing activity type (8 × 0.25): -2 points + +**Total Deductions**: -27 points + +--- + +## Next Steps + +**Priority 1 - High Impact** (address first): +1. `/hygiene.link-epics` - Link 15 orphaned stories +2. Review 3 blocking tickets - Unblock 5 downstream tickets + +**Priority 2 - Medium Impact** (address this week): +3. `/hygiene.close-stale` - Close 12 stale tickets +4. `/hygiene.triage-new` - Triage 5 items + +**Priority 3 - Low Impact** (address as time allows): +5. `/hygiene.link-initiatives` - Link 2 orphaned epics +6. `/hygiene.activity-type` - Set activity type for 8 tickets +7. Assign 2 in-progress tickets +8. Review 1 blocking-closed mismatch + +**Estimated Time**: 30-45 minutes to address all issues + +--- + +## Report Details + +**Project**: PROJ +**Generated**: 2026-04-07 10:30 UTC +**Total Unresolved Tickets**: 250 +**Issues Found**: 48 (19% of tickets need hygiene attention) + +**Related Reports**: +- [Blocking Tickets](./blocking-tickets.md) +- [Blocking-Closed Mismatches](./blocking-closed-mismatch.md) +- [In-Progress Unassigned](./unassigned-progress.md) +``` + +## Notes + +- All queries are read-only (no modifications made) +- Health score is a guideline, not absolute measure +- Customize thresholds via config.json +- Run this report weekly for ongoing hygiene monitoring +- Consider adding to cron for automated reporting diff --git a/workflows/jira-hygiene/CLAUDE.md b/workflows/jira-hygiene/CLAUDE.md index 53f0bef..3bab884 100644 --- a/workflows/jira-hygiene/CLAUDE.md +++ b/workflows/jira-hygiene/CLAUDE.md @@ -1,8 +1,9 @@ # Jira Hygiene Workflow -Systematic Jira project hygiene through 10 specialized commands: +Systematic Jira project hygiene through 11 specialized commands: **Setup**: `/hygiene.setup` +**Reporting**: `/hygiene.report` (master report with health score) **Linking**: `/hygiene.link-epics`, `/hygiene.link-initiatives` **Activity**: `/hygiene.activity-summary`, `/hygiene.show-blocking` **Bulk Ops**: `/hygiene.close-stale`, `/hygiene.triage-new` diff --git a/workflows/jira-hygiene/README.md b/workflows/jira-hygiene/README.md index 8f37512..8b0d3c4 100644 --- a/workflows/jira-hygiene/README.md +++ b/workflows/jira-hygiene/README.md @@ -60,6 +60,8 @@ Your Jira account must have: ## Commands +The workflow provides **11 specialized commands** for comprehensive Jira hygiene management. + ### Setup & Configuration #### `/hygiene.setup` @@ -121,6 +123,55 @@ Link orphaned epics to initiatives across projects. ### Activity & Reporting +#### `/hygiene.report` + +Generate comprehensive master hygiene report with health score. + +**What it does**: +- Runs all hygiene checks (read-only, no modifications) +- Calculates project health score (0-100) +- Provides executive summary with issue counts +- Lists top issues in each category +- Recommends which commands to run +- Generates detailed sections for all hygiene categories + +**Health Score**: +- 90-100: Excellent 🟢 +- 70-89: Good 🟡 +- 50-69: Needs Attention 🟠 +- 0-49: Critical 🔴 + +**Categories Checked**: +- Orphaned stories and epics +- Blocking tickets +- Stale tickets (by priority) +- Untriaged items +- Blocking-closed mismatches +- In-progress unassigned +- Missing activity types + +**Arguments**: +- `--output ` - Custom output path +- `--format ` - Output format (default: md) + +**Example output**: +``` +Project Hygiene Report: PROJ +Health Score: 73/100 🟡 Good + +Issues Found: +• 15 orphaned stories +• 3 blocking tickets +• 12 stale tickets +• 5 untriaged items + +Full report: artifacts/jira-hygiene/reports/master-report.md +``` + +**Use case**: Weekly hygiene check, stakeholder reporting, project health dashboard + +--- + #### `/hygiene.activity-summary` Generate weekly activity summaries for epics/initiatives. From e63d658353952df0413a5fb62366c7049be3f9cf Mon Sep 17 00:00:00 2001 From: Eliad Cohen Date: Wed, 8 Apr 2026 15:04:37 -0400 Subject: [PATCH 7/8] feat(jira-hygiene): add custom JQL support and pagination - Add base_jql field to config for customizable query filtering - Implement pagination across all 10 commands to fetch complete result sets - Update systemPrompt with pagination rules and base_jql guidelines - Add pagination examples to all command files (simple, nested, and complex) - Handle special cases: issue functions, child queries, cross-project searches - Update CLAUDE.md with pagination and base_jql documentation Fixes data loss issue where only first 50 results were returned. Users can now scope workflows to teams/labels and handle large datasets. Co-Authored-By: Claude Sonnet 4.5 --- workflows/jira-hygiene/.ambient/ambient.json | 2 +- .../commands/hygiene.activity-summary.md | 51 +++++++++++++- .../.claude/commands/hygiene.activity-type.md | 27 +++++++- .../commands/hygiene.blocking-closed.md | 29 +++++++- .../.claude/commands/hygiene.close-stale.md | 36 +++++++++- .../.claude/commands/hygiene.link-epics.md | 51 ++++++++++++-- .../commands/hygiene.link-initiatives.md | 51 ++++++++++++-- .../.claude/commands/hygiene.report.md | 69 ++++++++++++------- .../.claude/commands/hygiene.setup.md | 27 ++++++-- .../.claude/commands/hygiene.show-blocking.md | 31 +++++++-- .../.claude/commands/hygiene.triage-new.md | 51 ++++++++++++-- .../commands/hygiene.unassigned-progress.md | 28 +++++++- workflows/jira-hygiene/CLAUDE.md | 43 ++++++++++++ 13 files changed, 435 insertions(+), 61 deletions(-) diff --git a/workflows/jira-hygiene/.ambient/ambient.json b/workflows/jira-hygiene/.ambient/ambient.json index 91fd7dd..2bdb1b8 100644 --- a/workflows/jira-hygiene/.ambient/ambient.json +++ b/workflows/jira-hygiene/.ambient/ambient.json @@ -1,7 +1,7 @@ { "name": "Jira Hygiene", "description": "Systematic workflow for maintaining Jira project hygiene. Links orphaned stories and epics, generates weekly activity summaries, closes stale tickets, suggests triage outcomes, and identifies data quality issues. Provides safe bulk operations with review-then-execute pattern.", - "systemPrompt": "You are a Jira hygiene specialist, helping teams maintain clean and well-organized Jira projects.\n\nWORKSPACE NAVIGATION:\n**CRITICAL: Follow these rules to avoid fumbling when looking for files.**\n\nStandard file locations (from workflow root):\n- Config: .ambient/ambient.json (ALWAYS at this path)\n- Commands: .claude/commands/*.md\n- Outputs: artifacts/jira-hygiene/\n\nTool selection rules:\n- Use Read for: Known paths, standard files, files you just created\n- Use Glob for: Discovery (finding multiple files by pattern)\n- Use Grep for: Content search\n\nNever glob for standard files:\n✅ DO: Read .ambient/ambient.json\n❌ DON'T: Glob **/ambient.json\n\nYour role is to:\n1. Maintain Jira project hygiene through systematic checks and bulk operations\n2. Link orphaned stories to epics and epics to initiatives\n3. Generate weekly activity summaries for epics and initiatives\n4. Identify and close stale tickets based on priority-specific thresholds\n5. Suggest triage outcomes for untriaged items\n6. Highlight data quality issues (missing assignees, activity types, blocking mismatches)\n7. Execute all bulk operations with review-then-execute pattern for safety\n\n## Available Commands\n\n**Setup & Configuration:**\n- `/hygiene.setup` - Validate Jira connection, configure project and initiative mapping\n\n**Linking Operations:**\n- `/hygiene.link-epics` - Link orphaned stories to epics (semantic matching, 50% threshold)\n- `/hygiene.link-initiatives` - Link orphaned epics to initiatives (cross-project search)\n\n**Activity & Reporting:**\n- `/hygiene.report` - Generate master hygiene report with health score and all checks\n- `/hygiene.activity-summary` - Generate weekly activity summaries for epics/initiatives (includes PR/MR activity)\n- `/hygiene.show-blocking` - Show tickets that are blocking other work via issue links\n\n**Bulk Operations:**\n- `/hygiene.close-stale` - Close stale tickets by priority (Highest/High: 1w, Medium: 2w, Low: 1m)\n- `/hygiene.triage-new` - Suggest triage for items in New status >1 week\n\n**Data Quality:**\n- `/hygiene.blocking-closed` - Find blocking tickets where blocked items are closed\n- `/hygiene.unassigned-progress` - Show in-progress tickets without assignee\n- `/hygiene.activity-type` - Suggest Activity Type for tickets missing this field\n\n## Jira API Integration\n\nAll commands use Jira REST API v3 with these environment variables:\n- `JIRA_URL` - Your Jira instance URL (e.g., https://company.atlassian.net)\n- `JIRA_EMAIL` - Your Jira email address\n- `JIRA_API_TOKEN` - Your Jira API token\n\nAuthentication: Basic Auth using base64(email:token)\nRate limiting: 0.5s delay between requests\nError handling: Retry on 429, validate all responses\n\n## Safety & Best Practices\n\n**Review-then-execute pattern:**\n1. Query and analyze tickets\n2. Write candidates to artifacts/jira-hygiene/candidates/\n3. Display summary to user\n4. Ask for explicit confirmation\n5. Execute operations only after confirmation\n6. Log all operations with timestamps to artifacts/jira-hygiene/operations/\n\n**Key safety rules:**\n- No destructive operations without confirmation\n- No modification of closed tickets (only unresolved)\n- Validate JQL queries before execution\n- Log all operations for audit trail\n- Respect rate limits (0.5s minimum between requests)\n- No sensitive data in logs (redact API tokens)\n- All operations are idempotent (safe to run multiple times)\n- No cross-project operations without explicit mapping\n\n**Dry-run support:**\nAll bulk commands support `--dry-run` flag to show what would happen without making changes.\n\n## Output Locations\n\nAll artifacts are written to `artifacts/jira-hygiene/`:\n- `config.json` - Project configuration and field metadata cache\n- `candidates/*.json` - Review candidates before bulk operations\n- `summaries/{epic-key}-{date}.md` - Generated activity summaries\n- `reports/*.md` - Read-only reports for data quality issues\n- `operations/*-{timestamp}.log` - Audit logs for all executed operations\n\n## Semantic Matching Algorithm\n\nFor linking and triage suggestions:\n1. Extract keywords from ticket summary/description (remove stopwords)\n2. Search using Jira text search: `text ~ \"keyword1 keyword2\"`\n3. Calculate match score: (matching_keywords / total_keywords) * 100\n4. Rank by score, suggest top matches\n5. Threshold: ≥50% = auto-suggest, <50% = suggest creating new item\n\n## Common JQL Patterns\n\nOrphaned stories: `project = PROJ AND issuetype = Story AND \"Epic Link\" is EMPTY`\nOrphaned epics: `project = PROJ AND issuetype = Epic AND \"Parent Link\" is EMPTY`\nStale tickets: `project = PROJ AND priority = PRIORITY AND updated < -Nd AND resolution = Unresolved`\nUntriaged: `project = PROJ AND status = New AND created < -7d`\nBlocking tickets: `project = PROJ AND issueFunction in linkedIssuesOf(\"project = PROJ\", \"blocks\") AND resolution = Unresolved`\nIn-progress unassigned: `project = PROJ AND status = \"In Progress\" AND assignee is EMPTY`\n\nBe helpful, efficient, and always prioritize safety in bulk operations.", + "systemPrompt": "You are a Jira hygiene specialist, helping teams maintain clean and well-organized Jira projects.\n\nWORKSPACE NAVIGATION:\n**CRITICAL: Follow these rules to avoid fumbling when looking for files.**\n\nStandard file locations (from workflow root):\n- Config: .ambient/ambient.json (ALWAYS at this path)\n- Commands: .claude/commands/*.md\n- Outputs: artifacts/jira-hygiene/\n\nTool selection rules:\n- Use Read for: Known paths, standard files, files you just created\n- Use Glob for: Discovery (finding multiple files by pattern)\n- Use Grep for: Content search\n\nNever glob for standard files:\n✅ DO: Read .ambient/ambient.json\n❌ DON'T: Glob **/ambient.json\n\nYour role is to:\n1. Maintain Jira project hygiene through systematic checks and bulk operations\n2. Link orphaned stories to epics and epics to initiatives\n3. Generate weekly activity summaries for epics and initiatives\n4. Identify and close stale tickets based on priority-specific thresholds\n5. Suggest triage outcomes for untriaged items\n6. Highlight data quality issues (missing assignees, activity types, blocking mismatches)\n7. Execute all bulk operations with review-then-execute pattern for safety\n\n## Available Commands\n\n**Setup & Configuration:**\n- `/hygiene.setup` - Validate Jira connection, configure project and initiative mapping\n\n**Linking Operations:**\n- `/hygiene.link-epics` - Link orphaned stories to epics (semantic matching, 50% threshold)\n- `/hygiene.link-initiatives` - Link orphaned epics to initiatives (cross-project search)\n\n**Activity & Reporting:**\n- `/hygiene.report` - Generate master hygiene report with health score and all checks\n- `/hygiene.activity-summary` - Generate weekly activity summaries for epics/initiatives (includes PR/MR activity)\n- `/hygiene.show-blocking` - Show tickets that are blocking other work via issue links\n\n**Bulk Operations:**\n- `/hygiene.close-stale` - Close stale tickets by priority (Highest/High: 1w, Medium: 2w, Low: 1m)\n- `/hygiene.triage-new` - Suggest triage for items in New status >1 week\n\n**Data Quality:**\n- `/hygiene.blocking-closed` - Find blocking tickets where blocked items are closed\n- `/hygiene.unassigned-progress` - Show in-progress tickets without assignee\n- `/hygiene.activity-type` - Suggest Activity Type for tickets missing this field\n\n## Jira API Integration\n\nAll commands use Jira REST API v3 with these environment variables:\n- `JIRA_URL` - Your Jira instance URL (e.g., https://company.atlassian.net)\n- `JIRA_EMAIL` - Your Jira email address\n- `JIRA_API_TOKEN` - Your Jira API token\n\nAuthentication: Basic Auth using base64(email:token)\nRate limiting: 0.5s delay between requests\nError handling: Retry on 429, validate all responses\n\n## Base JQL Configuration\n\nThe config file includes a `base_jql` field that customizes the default filter for all commands:\n\n**Structure**: `({base_jql}) AND {command_specific_filters}`\n\n**Example**:\n- Config: `\"base_jql\": \"project = MYPROJ AND resolution = Unresolved AND labels = backend\"`\n- Command: link-epics adds `AND issuetype = Story AND \"Epic Link\" is EMPTY`\n- Final JQL: `(project = MYPROJ AND resolution = Unresolved AND labels = backend) AND issuetype = Story AND \"Epic Link\" is EMPTY`\n\n**Usage rules**:\n- Apply base_jql to ALL primary queries (orphaned stories, stale tickets, blocking, etc.)\n- Do NOT apply to child queries (e.g., `parent = {EPIC_KEY}` should not include base_jql)\n- Do NOT apply to user-provided JQL in activity-summary (user has full control there)\n- For issueFunction queries, apply to both outer AND inner queries\n\n**Default**: If base_jql not in config, use `\"project = {PROJECT} AND resolution = Unresolved\"`\n\n## Pagination Rules\n\n**CRITICAL**: All Jira API queries must fetch ALL results using pagination. Never rely on default limits.\n\n**Standard pagination pattern**:\n```\nall_results = []\nstart_at = 0\nmax_results = 50\n\nwhile True:\n # Fetch page\n response = GET /rest/api/3/search?jql={jql}&startAt={start_at}&maxResults={max_results}\n \n # Extract results\n issues = response['issues']\n all_results.extend(issues)\n \n # Check if done\n total = response['total']\n if start_at + len(issues) >= total:\n break # All results fetched\n \n # Next page\n start_at += max_results\n \n # Rate limit\n sleep(0.5)\n```\n\n**When to paginate**:\n- ✅ Primary queries: orphaned stories, stale tickets, blocking tickets, untriaged\n- ✅ Semantic searches: text ~ \"keywords\" for linking operations\n- ✅ Multiple queries: close-stale has 5 queries (one per priority), paginate each\n- ✅ Nested queries: activity-summary fetches epics (paginate), then children per epic (paginate)\n- ✅ Child queries: `parent = {KEY}` should paginate for safety\n- ❌ Field metadata: `/rest/api/3/field` returns all in one call, no pagination needed\n\n**Progress indicators**:\n- Show: \"Fetched 50/237 orphaned stories...\" during pagination\n- Log: Include total_fetched and pages_processed in operation logs\n\n**Rate limiting**:\n- Maintain 0.5s delay between pages\n- If 429 response: increase delay to 1s, retry\n- Apply same delay to nested queries\n\n**Special cases**:\n\n1. **Multiple queries (close-stale)**:\n - Paginate each priority query separately\n - Example: Highest (3 pages), Medium (1 page), Low (5 pages)\n\n2. **Nested pagination (activity-summary)**:\n - Paginate parent query (e.g., fetch all epics)\n - For each parent, paginate child query\n - Example: 150 epics × (avg 30 children each) = paginate both levels\n\n3. **Issue functions**:\n ```\n # Apply base_jql to outer query AND inner query\n ({base_jql}) AND issueFunction in linkedIssuesOf(\"({base_jql})\", \"blocks\")\n ```\n\n4. **Cross-project searches (link-initiatives)**:\n ```\n # Initiative search uses different project list\n project in ({INIT1},{INIT2}) AND issuetype = Initiative AND text ~ \"keywords\"\n # Still paginate, but base_jql not applicable (different projects)\n ```\n\n## Safety & Best Practices\n\n**Review-then-execute pattern:**\n1. Query and analyze tickets\n2. Write candidates to artifacts/jira-hygiene/candidates/\n3. Display summary to user\n4. Ask for explicit confirmation\n5. Execute operations only after confirmation\n6. Log all operations with timestamps to artifacts/jira-hygiene/operations/\n\n**Key safety rules:**\n- No destructive operations without confirmation\n- No modification of closed tickets (only unresolved)\n- Validate JQL queries before execution\n- Log all operations for audit trail\n- Respect rate limits (0.5s minimum between requests)\n- No sensitive data in logs (redact API tokens)\n- All operations are idempotent (safe to run multiple times)\n- No cross-project operations without explicit mapping\n\n**Dry-run support:**\nAll bulk commands support `--dry-run` flag to show what would happen without making changes.\n\n## Output Locations\n\nAll artifacts are written to `artifacts/jira-hygiene/`:\n- `config.json` - Project configuration and field metadata cache\n- `candidates/*.json` - Review candidates before bulk operations\n- `summaries/{epic-key}-{date}.md` - Generated activity summaries\n- `reports/*.md` - Read-only reports for data quality issues\n- `operations/*-{timestamp}.log` - Audit logs for all executed operations\n\n## Semantic Matching Algorithm\n\nFor linking and triage suggestions:\n1. Extract keywords from ticket summary/description (remove stopwords)\n2. Search using Jira text search: `text ~ \"keyword1 keyword2\"`\n3. Calculate match score: (matching_keywords / total_keywords) * 100\n4. Rank by score, suggest top matches\n5. Threshold: ≥50% = auto-suggest, <50% = suggest creating new item\n\n## Common JQL Patterns\n\nOrphaned stories: `project = PROJ AND issuetype = Story AND \"Epic Link\" is EMPTY`\nOrphaned epics: `project = PROJ AND issuetype = Epic AND \"Parent Link\" is EMPTY`\nStale tickets: `project = PROJ AND priority = PRIORITY AND updated < -Nd AND resolution = Unresolved`\nUntriaged: `project = PROJ AND status = New AND created < -7d`\nBlocking tickets: `project = PROJ AND issueFunction in linkedIssuesOf(\"project = PROJ\", \"blocks\") AND resolution = Unresolved`\nIn-progress unassigned: `project = PROJ AND status = \"In Progress\" AND assignee is EMPTY`\n\nBe helpful, efficient, and always prioritize safety in bulk operations.", "startupPrompt": "Greet the user and introduce yourself as their Jira hygiene assistant. Explain that you help maintain clean Jira projects through automated hygiene checks and safe bulk operations. Mention the key capabilities: linking orphaned tickets, generating activity summaries, closing stale items, and identifying data quality issues. Suggest starting with `/hygiene.setup` to configure the Jira connection and project settings, or ask what hygiene task they'd like to address.", "results": { "Configuration": "artifacts/jira-hygiene/config.json", diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md b/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md index b8a14e5..17fe8c6 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md @@ -26,17 +26,62 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi - Provide JQL filter (e.g., "project = PROJ AND issuetype = Epic") - Use "all active epics" (default: all unresolved epics in project) -3. **Fetch selected epics/initiatives**: +3. **Fetch selected epics/initiatives WITH PAGINATION**: - Execute JQL query to get target issues + + **Pagination logic** (if using JQL filter): + ``` + all_epics = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={user_jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary,issuetype + epics = response['issues'] + all_epics.extend(epics) + + Print: "Fetched {start_at + len(epics)}/{response['total']} epics/initiatives..." + + if start_at + len(epics) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit + ``` + - Fetch: key, summary, issuetype 4. **For each epic/initiative**: - a. **Fetch child issues**: + a. **Fetch child issues WITH PAGINATION**: ```jql parent = {EPIC_KEY} AND resolution = Unresolved ``` - - Get all child stories/tasks + + **Note**: Child queries do NOT use base_jql (children can cross project boundaries) + + **Pagination logic**: + ``` + all_children = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={child_jql}&startAt={start_at}&maxResults={max_results} + children = response['issues'] + all_children.extend(children) + + Print: "Fetched {len(all_children)}/{response['total']} children for {EPIC_KEY}..." + + if start_at + len(children) >= response['total']: + break + + start_at += max_results + sleep(0.5) # Rate limit + ``` + + - Get all child stories/tasks (not limited to 50) + - Then analyze activity for ALL children b. **Analyze activity for each child** (past 7 days): - Fetch changelog: GET `/rest/api/3/issue/{childKey}/changelog` diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md b/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md index 99f9bb0..694d9f7 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md @@ -18,13 +18,34 @@ Optional: 1. **Load configuration**: - Read `artifacts/jira-hygiene/config.json` - - Extract project key, Activity Type field ID, and available values + - Extract base_jql, Activity Type field ID, and available values - If Activity Type field is not configured: prompt user to run `/hygiene.setup` -2. **Query tickets missing Activity Type**: +2. **Query tickets missing Activity Type WITH PAGINATION**: ```jql - project = {PROJECT} AND "{ACTIVITY_TYPE_FIELD_NAME}" is EMPTY AND resolution = Unresolved + ({base_jql}) AND "{ACTIVITY_TYPE_FIELD_NAME}" is EMPTY ``` + + **Pagination logic**: + ``` + all_tickets = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={encoded_jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary,description,issuetype + tickets = response['issues'] + all_tickets.extend(tickets) + + Print: "Fetched {start_at + len(tickets)}/{response['total']} tickets missing Activity Type..." + + if start_at + len(tickets) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit + ``` + - Fetch: key, summary, description, issuetype - If none found: report success and exit diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md b/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md index 9528046..0ed4edc 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.blocking-closed.md @@ -12,12 +12,35 @@ Highlight tickets marked as "Blocking" where all of the blocked tickets are alre 1. **Load configuration**: - Read `artifacts/jira-hygiene/config.json` - - Extract project key + - Extract base_jql -2. **Query tickets with "blocks" links**: +2. **Query tickets with "blocks" links WITH PAGINATION**: ```jql - project = {PROJECT} AND issueFunction in linkedIssuesOf("project = {PROJECT}", "blocks") AND resolution = Unresolved + ({base_jql}) AND issueFunction in linkedIssuesOf("({base_jql})", "blocks") ``` + + **Note**: Both outer query AND inner linkedIssuesOf query use base_jql for consistency + + **Pagination logic**: + ``` + all_blocking_tickets = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={encoded_jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary,status + tickets = response['issues'] + all_blocking_tickets.extend(tickets) + + Print: "Fetched {start_at + len(tickets)}/{response['total']} blocking tickets..." + + if start_at + len(tickets) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit + ``` + - This finds all unresolved tickets that block other tickets - Fetch: key, summary, status diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md b/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md index 6f18588..19e2fcf 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md @@ -22,14 +22,44 @@ Optional: Override default thresholds 1. **Load configuration**: - Read `artifacts/jira-hygiene/config.json` - - Extract project key and staleness_thresholds + - Extract base_jql and staleness_thresholds - Apply any command-line overrides -2. **Query stale tickets by priority**: +2. **Query stale tickets by priority WITH PAGINATION**: + For each priority level (Highest, High, Medium, Low, Lowest): ```jql - project = {PROJECT} AND priority = {PRIORITY} AND updated < -{DAYS}d AND resolution = Unresolved + ({base_jql}) AND priority = {PRIORITY} AND updated < -{DAYS}d + ``` + + **Pagination per priority**: ``` + all_stale = {} + + for priority in ["Highest", "High", "Medium", "Low", "Lowest"]: + days = staleness_thresholds[priority] + jql = f"({base_jql}) AND priority = {priority} AND updated < -{days}d" + + priority_tickets = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary,assignee,status,updated,priority + tickets = response['issues'] + priority_tickets.extend(tickets) + + Print: "Fetched {len(priority_tickets)}/{response['total']} {priority} priority tickets..." + + if start_at + len(tickets) >= response['total']: + break + + start_at += max_results + sleep(0.5) + + all_stale[priority] = priority_tickets + ``` + - Fetch: key, summary, assignee, status, updated, priority - Group results by priority diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md b/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md index c37a908..901ff88 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md @@ -13,12 +13,33 @@ Find stories without epic links and suggest appropriate epics to link them to, u 1. **Load configuration**: - Read `artifacts/jira-hygiene/config.json` - - Extract project key + - Extract base_jql -2. **Query orphaned stories**: +2. **Query orphaned stories WITH PAGINATION**: ```jql - project = {PROJECT} AND issuetype = Story AND "Epic Link" is EMPTY AND resolution = Unresolved + ({base_jql}) AND issuetype = Story AND "Epic Link" is EMPTY ``` + + **Pagination logic**: + ``` + all_orphaned_stories = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={encoded_jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary,description + stories = response['issues'] + all_orphaned_stories.extend(stories) + + Print: "Fetched {start_at + len(stories)}/{response['total']} orphaned stories..." + + if start_at + len(stories) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit + ``` + - Fetch: key, summary, description - If none found: report success and exit @@ -30,12 +51,32 @@ Find stories without epic links and suggest appropriate epics to link them to, u - Keep technical terms (API, auth, payment, database, etc.) - Lowercase and deduplicate - b. **Search for matching epics**: + b. **Search for matching epics WITH PAGINATION**: ```jql - project = {PROJECT} AND issuetype = Epic AND resolution = Unresolved AND text ~ "keyword1 keyword2 keyword3" + ({base_jql}) AND issuetype = Epic AND text ~ "keyword1 keyword2 keyword3" + ``` + + **Pagination for semantic search**: + ``` + matching_epics = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={search_jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary + epics = response['issues'] + matching_epics.extend(epics) + + if start_at + len(epics) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit ``` + - Start with all keywords; if no results, try top 3 keywords - Fetch: key, summary + - Calculate match scores for ALL epics returned (not just first 50) c. **Calculate match scores**: - For each epic found, count keywords that appear in epic summary diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md b/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md index 7a1941a..393408d 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md @@ -13,13 +13,34 @@ Find epics without initiative links and suggest appropriate initiatives from con 1. **Load configuration**: - Read `artifacts/jira-hygiene/config.json` - - Extract project key and initiative_projects list + - Extract base_jql and initiative_projects list - If initiative_projects is empty: prompt user to configure via `/hygiene.setup` -2. **Query orphaned epics**: +2. **Query orphaned epics WITH PAGINATION**: ```jql - project = {PROJECT} AND issuetype = Epic AND "Parent Link" is EMPTY AND resolution = Unresolved + ({base_jql}) AND issuetype = Epic AND "Parent Link" is EMPTY ``` + + **Pagination logic**: + ``` + all_orphaned_epics = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={encoded_jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary,description + epics = response['issues'] + all_orphaned_epics.extend(epics) + + Print: "Fetched {start_at + len(epics)}/{response['total']} orphaned epics..." + + if start_at + len(epics) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit + ``` + - Fetch: key, summary, description - If none found: report success and exit @@ -29,12 +50,34 @@ Find epics without initiative links and suggest appropriate initiatives from con - Same process as `/hygiene.link-epics` - Combine summary + description, remove stopwords - b. **Search for matching initiatives** (cross-project): + b. **Search for matching initiatives WITH PAGINATION** (cross-project): ```jql project in ({INIT1},{INIT2}) AND issuetype = Initiative AND resolution = Unresolved AND text ~ "keyword1 keyword2" ``` + + **Note**: Initiative search uses different project list, so base_jql is NOT applied here + + **Pagination for cross-project search**: + ``` + matching_initiatives = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={search_jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary,project + initiatives = response['issues'] + matching_initiatives.extend(initiatives) + + if start_at + len(initiatives) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit + ``` + - Search across all configured initiative projects - Fetch: key, summary, project + - Calculate match scores for ALL initiatives returned (not just first 50) c. **Calculate match scores**: - Score = (matching_keywords / total_keywords) * 100 diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.report.md b/workflows/jira-hygiene/.claude/commands/hygiene.report.md index 71379a1..709209b 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.report.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.report.md @@ -18,61 +18,84 @@ Optional: 1. **Load configuration**: - Read `artifacts/jira-hygiene/config.json` - - Extract project key and settings + - Extract base_jql and settings -2. **Run all hygiene checks** (read-only queries): +2. **Run all hygiene checks WITH PAGINATION** (read-only queries): - a. **Orphaned Stories**: + **Note**: All queries use base_jql and paginate to fetch complete counts + + **Standard pagination pattern for each query**: + ``` + all_results = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={jql}&startAt={start_at}&maxResults={max_results} + results = response['issues'] + all_results.extend(results) + + if start_at + len(results) >= response['total']: + break + + start_at += max_results + sleep(0.5) + ``` + + a. **Orphaned Stories WITH PAGINATION**: ```jql - project = {PROJECT} AND issuetype = Story AND "Epic Link" is EMPTY AND resolution = Unresolved + ({base_jql}) AND issuetype = Story AND "Epic Link" is EMPTY ``` - - Count total orphaned stories + - Paginate to count ALL orphaned stories - List top 5 by age - b. **Orphaned Epics**: + b. **Orphaned Epics WITH PAGINATION**: ```jql - project = {PROJECT} AND issuetype = Epic AND "Parent Link" is EMPTY AND resolution = Unresolved + ({base_jql}) AND issuetype = Epic AND "Parent Link" is EMPTY ``` - - Count total orphaned epics + - Paginate to count ALL orphaned epics - List top 5 by age - c. **Blocking Tickets**: + c. **Blocking Tickets WITH PAGINATION**: ```jql - project = {PROJECT} AND issueFunction in linkedIssuesOf("project = {PROJECT}", "blocks") AND resolution = Unresolved + ({base_jql}) AND issueFunction in linkedIssuesOf("({base_jql})", "blocks") ``` - - Count total blocking tickets + - Paginate to count ALL blocking tickets - Count tickets being blocked - List all with blocked ticket counts - d. **Stale Tickets** (by priority): + d. **Stale Tickets BY PRIORITY WITH PAGINATION**: + - For each priority: `({base_jql}) AND priority = {PRIORITY} AND updated < -{DAYS}d` - Apply configured thresholds + - Paginate each priority query separately - Count by priority level - List top 5 oldest per priority - e. **Untriaged Items**: + e. **Untriaged Items WITH PAGINATION**: ```jql - project = {PROJECT} AND status = New AND created < -7d AND resolution = Unresolved + ({base_jql}) AND status = New AND created < -7d ``` - - Count total untriaged + - Paginate to count ALL untriaged - List top 5 by age - f. **Blocking-Closed Mismatches**: - - Check blocking tickets where all blocked items are closed + f. **Blocking-Closed Mismatches WITH PAGINATION**: + - Query blocking tickets (with pagination) + - For each, check if all blocked items are closed - Count mismatches - List all - g. **In-Progress Unassigned**: + g. **In-Progress Unassigned WITH PAGINATION**: ```jql - project = {PROJECT} AND status = "In Progress" AND assignee is EMPTY AND resolution = Unresolved + ({base_jql}) AND status = "In Progress" AND assignee is EMPTY ``` - - Count total + - Paginate to count ALL - List all - h. **Missing Activity Type**: + h. **Missing Activity Type WITH PAGINATION**: ```jql - project = {PROJECT} AND "Activity Type" is EMPTY AND resolution = Unresolved + ({base_jql}) AND "Activity Type" is EMPTY ``` - - Count total + - Paginate to count ALL - List top 10 by priority 3. **Calculate health score**: diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.setup.md b/workflows/jira-hygiene/.claude/commands/hygiene.setup.md index 3a94a6a..f648e2e 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.setup.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.setup.md @@ -31,20 +31,37 @@ Environment variables must be set: - **Initiative project keys**: Comma-separated list of projects containing initiatives (e.g., "INIT1,INIT2") - User must provide the exact project keys they want to use -4. **Fetch Activity Type field metadata**: +4. **Prompt for base JQL filter (optional)**: + - **Base JQL filter**: Custom JQL to scope all operations + - If empty/skipped: Use default `"project = {PROJECT} AND resolution = Unresolved"` + - Examples: + - `"project = MYPROJ AND resolution = Unresolved AND labels = backend"` + - `"project in (PROJ1, PROJ2) AND resolution = Unresolved AND team = Platform"` + - `"project = MYPROJ AND resolution = Unresolved AND component = API"` + - Explain: This filter will be combined with each command's specific conditions + +4a. **Validate base JQL (if provided)**: + - Test query via `GET /rest/api/3/search?jql={encoded_jql}&maxResults=1` + - If 400 error: Show JQL syntax error, ask user to correct and retry + - If 200: Proceed with valid JQL + - If empty/skipped: Use default `"project = {PROJECT} AND resolution = Unresolved"` + +5. **Fetch Activity Type field metadata**: - Call `/rest/api/3/field` to get all custom fields - Search for field with name matching "Activity Type" (case-insensitive) - Extract field ID (e.g., "customfield_10050") - Fetch allowed values for this field - If not found: note in config, skip this feature -5. **Create config file**: +6. **Create config file**: - Write all settings to `artifacts/jira-hygiene/config.json` + - Include base_jql (either user-provided or default) - Include default staleness thresholds - Format as pretty JSON for readability -6. **Display summary**: +7. **Display summary**: - Show configured project key + - Show base JQL filter - Show initiative project keys - Show Activity Type field ID and available values - Confirm setup is complete @@ -59,6 +76,7 @@ Environment variables must be set: { "jira_url": "https://company.atlassian.net", "project_key": "PROJ", + "base_jql": "project = PROJ AND resolution = Unresolved", "initiative_projects": ["INIT1", "INIT2"], "activity_type_field_id": "customfield_10050", "activity_type_values": ["Development", "Bug Fix", "Documentation", "Research", "Testing"], @@ -69,7 +87,7 @@ Environment variables must be set: "Low": 30, "Lowest": 30 }, - "configured_at": "2026-04-07T10:30:00Z" + "configured_at": "2026-04-08T10:30:00Z" } ``` @@ -79,3 +97,4 @@ Environment variables must be set: - **Auth failed (401)**: Suggest checking email/token, regenerating token - **Network error**: Check JIRA_URL format (must start with https://) - **Field not found**: Activity Type feature will be disabled, note in config + diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md b/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md index 76a8d4e..2770494 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.show-blocking.md @@ -12,14 +12,37 @@ Display all tickets that are blocking other tickets via "Blocks" issue links. Th 1. **Load configuration**: - Read `artifacts/jira-hygiene/config.json` - - Extract project key + - Extract base_jql -2. **Query blocking tickets**: +2. **Query blocking tickets WITH PAGINATION**: ```jql - project = {PROJECT} AND issueFunction in linkedIssuesOf("project = {PROJECT}", "blocks") AND resolution = Unresolved + ({base_jql}) AND issueFunction in linkedIssuesOf("({base_jql})", "blocks") ``` + + **Note**: Both outer query AND inner linkedIssuesOf query use base_jql for consistency + + **Pagination logic**: + ``` + all_blocking_tickets = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={encoded_jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary,assignee,status,created,updated,priority,issuelinks&orderBy=updated DESC + tickets = response['issues'] + all_blocking_tickets.extend(tickets) + + Print: "Fetched {start_at + len(tickets)}/{response['total']} blocking tickets..." + + if start_at + len(tickets) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit + ``` + - This finds tickets that have outward "blocks" links to other tickets - - Fetch: key, summary, assignee, status, created, updated, priority + - Fetch: key, summary, assignee, status, created, updated, priority, issuelinks - Also fetch issue links to see what tickets are being blocked - Order by updated descending (most recent first) diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md b/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md index da53c82..d8381d3 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md @@ -18,12 +18,33 @@ Optional: 1. **Load configuration**: - Read `artifacts/jira-hygiene/config.json` - - Extract project key + - Extract base_jql -2. **Query untriaged items**: +2. **Query untriaged items WITH PAGINATION**: ```jql - project = {PROJECT} AND status = New AND created < -{DAYS}d AND resolution = Unresolved + ({base_jql}) AND status = New AND created < -{DAYS}d ``` + + **Pagination logic**: + ``` + all_untriaged = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={encoded_jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary,description,issuetype,created + items = response['issues'] + all_untriaged.extend(items) + + Print: "Fetched {start_at + len(items)}/{response['total']} untriaged items..." + + if start_at + len(items) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit + ``` + - Fetch: key, summary, description, issuetype, created - If none found: report success and exit @@ -33,12 +54,32 @@ Optional: - Combine summary + description - Remove stopwords - b. **Find similar items**: + b. **Find similar items WITH PAGINATION**: ```jql - project = {PROJECT} AND resolution = Unresolved AND text ~ "keyword1 keyword2" AND status != New + ({base_jql}) AND text ~ "keyword1 keyword2" AND status != New + ``` + + **Pagination for semantic search**: + ``` + similar_items = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={search_jql}&startAt={start_at}&maxResults={max_results}&fields=priority,status + items = response['issues'] + similar_items.extend(items) + + if start_at + len(items) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit ``` + - Find items that have been triaged (not in New status) - Fetch: priority, status + - Analyze priority distribution across ALL similar items (not just first 50) c. **Analyze priority distribution**: - Count priorities of similar items: {High: 5, Medium: 8, Low: 2} diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md b/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md index f5f958c..bf7f90c 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md @@ -12,14 +12,36 @@ Simple query to find tickets that are marked as "In Progress" but have no assign 1. **Load configuration**: - Read `artifacts/jira-hygiene/config.json` - - Extract project key + - Extract base_jql (or use default if not present) -2. **Query unassigned in-progress tickets**: +2. **Query unassigned in-progress tickets WITH PAGINATION**: ```jql - project = {PROJECT} AND status = "In Progress" AND assignee is EMPTY AND resolution = Unresolved + ({base_jql}) AND status = "In Progress" AND assignee is EMPTY ``` + + **Pagination logic**: + ``` + all_tickets = [] + start_at = 0 + max_results = 50 + + Loop: + response = GET /rest/api/3/search?jql={encoded_jql}&startAt={start_at}&maxResults={max_results}&fields=key,summary,status,created,updated,reporter&orderBy=updated DESC + tickets = response['issues'] + all_tickets.extend(tickets) + + Print: "Fetched {start_at + len(tickets)}/{response['total']} tickets..." + + if start_at + len(tickets) >= response['total']: + break # All results fetched + + start_at += max_results + sleep(0.5) # Rate limit + ``` + - Fetch: key, summary, status, created, updated, reporter - Order by updated descending + - If none found: report "No in-progress tickets without assignee" and exit 3. **Format as markdown table with Jira links**: ```markdown diff --git a/workflows/jira-hygiene/CLAUDE.md b/workflows/jira-hygiene/CLAUDE.md index 3bab884..28439f6 100644 --- a/workflows/jira-hygiene/CLAUDE.md +++ b/workflows/jira-hygiene/CLAUDE.md @@ -132,6 +132,7 @@ The workflow uses `artifacts/jira-hygiene/config.json` to cache: { "jira_url": "https://company.atlassian.net", "project_key": "PROJ", + "base_jql": "project = PROJ AND resolution = Unresolved", "initiative_projects": ["INIT1", "INIT2"], "activity_type_field_id": "customfield_10050", "activity_type_values": ["Development", "Bug Fix", "Documentation", "Research"], @@ -147,6 +148,48 @@ The workflow uses `artifacts/jira-hygiene/config.json` to cache: This file is created by `/hygiene.setup` and read by other commands. It avoids repeated API calls for field metadata. +## Pagination + +All commands automatically fetch ALL matching results using pagination: + +**How it works**: +- Jira API returns max 50 results by default +- Commands use `startAt` parameter to fetch in pages (0, 50, 100, ...) +- Loop continues until all results fetched +- Progress shown: "Fetched 150/237 tickets..." + +**User impact**: +- No manual intervention needed +- Large projects (>50 orphaned stories, >100 stale tickets) now fully supported +- Slightly longer execution time for large datasets (0.5s per page) + +**Example**: Project with 237 orphaned stories +- Old behavior: Only first 50 analyzed (187 missed) +- New behavior: All 237 fetched (5 pages × 0.5s = 2.5s extra time) + +## Base JQL Filter + +Customize which tickets are included in all operations using base_jql: + +**Setup**: During `/hygiene.setup`, provide optional base JQL filter + +**Default**: `project = {PROJECT} AND resolution = Unresolved` + +**Examples**: +- Scope to team: `project = MYPROJ AND resolution = Unresolved AND labels = backend` +- Multiple projects: `project in (PROJ1, PROJ2) AND resolution = Unresolved` +- Custom field: `project = MYPROJ AND resolution = Unresolved AND "Team" = Platform` + +**How it's used**: +- Combined with command-specific filters +- Example: link-epics uses `({base_jql}) AND issuetype = Story AND "Epic Link" is EMPTY` +- Applied to all queries except child relationships + +**When NOT to use**: +- Don't include `issuetype` (commands add this) +- Don't filter by status for specific tickets (may break linking logic) +- Don't add `updated < -Xd` (close-stale handles this) + ## Testing Before submitting PR, verify: From 2264c0ce7f039ded60644aa91603ae443d8a906c Mon Sep 17 00:00:00 2001 From: Eliad Cohen Date: Thu, 16 Apr 2026 09:11:07 -0400 Subject: [PATCH 8/8] fix: address all PR review comments from CodeRabbit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement all 16 actionable review comments: 1. Add --dry-run flag support: - hygiene.activity-summary: skip confirmation and POST requests - hygiene.link-epics: skip confirmation and API mutations - hygiene.link-initiatives: skip confirmation and modifications 2. Fix initiative→epic→child traversal: - Check issue type in activity-summary - Fetch child Epics first for Initiatives, then their children - Apply pagination at both levels 3. Enforce unresolved-only scope: - Append 'AND resolution = Unresolved' to JQL in activity-summary - Filter fetched issues to exclude resolved tickets 4. Add 50-ticket batch confirmations: - hygiene.activity-type: batch updates, require exact 'yes' - hygiene.close-stale: batch by priority groups, max 50 - hygiene.triage-new: batch mode with explicit confirmation 5. Fix JQL field references: - hygiene.activity-type: use {ACTIVITY_TYPE_FIELD_ID} not FIELD_NAME - hygiene.report: use activity_type_field_id from config - hygiene.triage-new: use status-change predicate not created date - hygiene.unassigned-progress: use statusCategory not hardcoded status 6. Add TOCTOU checks before bulk updates: - hygiene.link-epics: GET to verify Epic Link still empty - hygiene.link-initiatives: GET to verify Parent Link still empty 7. Fix HTTP method: - hygiene.close-stale: use POST for transitions endpoint not PUT 8. Update documentation: - CLAUDE.md: change 10 to 11 command files All changes preserve existing behavior while adding safety and correctness improvements as requested by the reviewer. Co-Authored-By: Claude Sonnet 4.5 --- .../commands/hygiene.activity-summary.md | 47 +++++++++++++++---- .../.claude/commands/hygiene.activity-type.md | 13 +++-- .../.claude/commands/hygiene.close-stale.md | 10 ++-- .../.claude/commands/hygiene.link-epics.md | 14 ++++-- .../commands/hygiene.link-initiatives.md | 14 ++++-- .../.claude/commands/hygiene.report.md | 3 +- .../.claude/commands/hygiene.triage-new.md | 14 ++++-- .../commands/hygiene.unassigned-progress.md | 4 +- workflows/jira-hygiene/CLAUDE.md | 2 +- 9 files changed, 88 insertions(+), 33 deletions(-) diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md b/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md index 17fe8c6..e829c12 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.activity-summary.md @@ -13,6 +13,11 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi - `GITHUB_TOKEN` - For direct GitHub API access if Jira integration unavailable - `GITLAB_TOKEN` - For direct GitLab API access if Jira integration unavailable +## Arguments + +Optional: +- `--dry-run` - Show summaries without posting them as comments (runs steps 1-4 only) + ## Process 1. **Load configuration**: @@ -25,9 +30,12 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi - Provide specific issue keys (comma-separated) - Provide JQL filter (e.g., "project = PROJ AND issuetype = Epic") - Use "all active epics" (default: all unresolved epics in project) + - **Always enforce unresolved scope**: Append "AND resolution = Unresolved" to any user-provided JQL 3. **Fetch selected epics/initiatives WITH PAGINATION**: - Execute JQL query to get target issues + - **If user provided specific keys**: Fetch each key and filter to only include issues where `fields.resolution == null` + - **If user provided JQL**: Already enforced "AND resolution = Unresolved" in step 2 **Pagination logic** (if using JQL filter): ``` @@ -53,14 +61,31 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi 4. **For each epic/initiative**: - a. **Fetch child issues WITH PAGINATION**: - ```jql - parent = {EPIC_KEY} AND resolution = Unresolved - ``` + a. **Fetch child issues WITH PAGINATION** (logic varies by issue type): + + **If issue type is Initiative**: + 1. First fetch child Epics: + ```jql + parent = {INITIATIVE_KEY} AND resolution = Unresolved + ``` + Use pagination (max 50 per page) to fetch all child epics + + 2. Then for each child Epic, fetch its child issues: + ```jql + parent = {EPIC_KEY} AND resolution = Unresolved + ``` + Use pagination for each epic's children + + **If issue type is Epic**: + - Directly fetch child issues: + ```jql + parent = {EPIC_KEY} AND resolution = Unresolved + ``` + Use pagination to fetch all children **Note**: Child queries do NOT use base_jql (children can cross project boundaries) - **Pagination logic**: + **Pagination logic** (apply to both Initiative→Epics and Epic→Children): ``` all_children = [] start_at = 0 @@ -71,7 +96,7 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi children = response['issues'] all_children.extend(children) - Print: "Fetched {len(all_children)}/{response['total']} children for {EPIC_KEY}..." + Print: "Fetched {len(all_children)}/{response['total']} children for {PARENT_KEY}..." if start_at + len(children) >= response['total']: break @@ -80,8 +105,8 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi sleep(0.5) # Rate limit ``` - - Get all child stories/tasks (not limited to 50) - - Then analyze activity for ALL children + - Get all child items (not limited to 50) + - Then analyze activity for ALL children across all levels b. **Analyze activity for each child** (past 7 days): - Fetch changelog: GET `/rest/api/3/issue/{childKey}/changelog` @@ -120,10 +145,11 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi - Format as markdown with epic key as header 6. **Ask for confirmation**: - - Prompt: "Post these summaries as comments? (yes/no)" + - If `--dry-run`: Display "DRY RUN - Summaries generated but not posted" and skip to step 8 + - Otherwise prompt: "Post these summaries as comments? (yes/no)" - Allow user to edit summaries before posting -7. **Post summaries**: +7. **Post summaries** (skip if --dry-run): - For each epic/initiative: - POST `/rest/api/3/issue/{epicKey}/comment` - Body: `{"body": "Weekly Activity Summary (YYYY-MM-DD):\n\n{summary_text}"}` @@ -131,6 +157,7 @@ Generate weekly activity summaries for selected epics and initiatives by analyzi 8. **Log results**: - Write to `artifacts/jira-hygiene/operations/activity-summary-{timestamp}.log` + - In --dry-run mode, log "DRY RUN - no comments posted" ## Output diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md b/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md index 694d9f7..645bdb0 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.activity-type.md @@ -23,7 +23,7 @@ Optional: 2. **Query tickets missing Activity Type WITH PAGINATION**: ```jql - ({base_jql}) AND "{ACTIVITY_TYPE_FIELD_NAME}" is EMPTY + ({base_jql}) AND "{ACTIVITY_TYPE_FIELD_ID}" is EMPTY ``` **Pagination logic**: @@ -99,12 +99,15 @@ Optional: → Development (Bug issuetype suggests bug fix, but no clear keywords) ``` -6. **Ask for confirmation**: +6. **Ask for confirmation** (batch mode): - If `--dry-run`: Skip, display "DRY RUN - No changes made" - - Otherwise prompt: "Apply Activity Type suggestions? (yes/no/high-confidence-only)" + - Otherwise, split approved tickets into batches of max 50 + - For each batch, prompt: "Apply Activity Type suggestions for {N} tickets? (yes/no/high-confidence-only)" + - Only proceed on exact response "yes" (reject other responses) + - If "high-confidence-only": apply only tickets with ≥3 keyword matches -7. **Execute updates**: - - For each approved ticket: +7. **Execute updates** (per batch): + - For each approved ticket in the current batch: - Update custom field via PUT `/rest/api/3/issue/{key}` - Payload: `{"fields": {"{FIELD_ID}": {"value": "{ACTIVITY_TYPE}"}}}` - Rate limit: 0.5s between tickets diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md b/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md index 19e2fcf..11a64d5 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.close-stale.md @@ -87,16 +87,18 @@ Optional: Override default thresholds View all candidates: See artifacts/jira-hygiene/candidates/close-stale.json ``` -5. **Ask for confirmation**: +5. **Ask for confirmation** (batch mode): - If `--dry-run`: Skip this step, display "DRY RUN - No changes made" - Otherwise prompt: "Close these stale tickets? (yes/no/by-priority)" - "by-priority": Let user approve each priority group separately + - **Batch limit**: Split each priority group into batches of max 50 tickets + - For each batch, require explicit "yes" response to proceed (deny other responses) -6. **Execute closure**: - - For each approved ticket: +6. **Execute closure** (per batch): + - For each approved ticket in current batch: - Add comment: "Due to lack of activity, this item has been closed. If you feel that it should be addressed, please reopen it." - Transition to "Closed" or "Done" status (use project's closed status) - - POST `/rest/api/3/issue/{key}/comment` then PUT `/rest/api/3/issue/{key}/transitions` + - POST `/rest/api/3/issue/{key}/comment` then POST `/rest/api/3/issue/{key}/transitions` - Rate limit: 0.5s between tickets 7. **Log results**: diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md b/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md index 901ff88..d3d417c 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.link-epics.md @@ -9,6 +9,11 @@ Find stories without epic links and suggest appropriate epics to link them to, u - `/hygiene.setup` must be run first to create `artifacts/jira-hygiene/config.json` - Project key must be configured +## Arguments + +Optional: +- `--dry-run` - Run steps 1-4 (Query, Analyze, Save, Display) only, skip confirmation and API mutations + ## Process 1. **Load configuration**: @@ -108,14 +113,17 @@ Find stories without epic links and suggest appropriate epics to link them to, u ``` 6. **Ask for confirmation**: - - Prompt: "Apply these suggestions? (yes/no/show-details)" + - If `--dry-run`: Display "DRY RUN - No changes made" and skip to step 8 + - Otherwise prompt: "Apply these suggestions? (yes/no/show-details)" - If "show-details": display full candidate list with match details - If "no": exit without changes - If "yes": proceed to execution -7. **Execute linking operations**: +7. **Execute linking operations** (skip if --dry-run): - For each approved linking suggestion: - - Update story via PUT `/rest/api/3/issue/{storyKey}` + - **TOCTOU check**: GET `/rest/api/3/issue/{storyKey}?fields=customfield_epic_link` to verify Epic Link is still empty + - If Epic Link is not empty: skip and log "Story already linked to {existing_epic}" + - Otherwise, update story via PUT `/rest/api/3/issue/{storyKey}` - Set Epic Link field (typically using "update" operation) - Rate limit: 0.5s between requests - For "create epic" suggestions: skip for now, just log recommendation diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md b/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md index 393408d..2375630 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.link-initiatives.md @@ -9,6 +9,11 @@ Find epics without initiative links and suggest appropriate initiatives from con - `/hygiene.setup` must be run first - Initiative projects must be configured in config.json +## Arguments + +Optional: +- `--dry-run` - Run steps 1-4 (Query, Analyze, Save, Display) only, skip confirmation and API modifications + ## Process 1. **Load configuration**: @@ -108,12 +113,15 @@ Find epics without initiative links and suggest appropriate initiatives from con ``` 6. **Ask for confirmation**: - - Prompt: "Apply these suggestions? (yes/no/show-details)" + - If `--dry-run`: Display "DRY RUN - No changes made" and skip to step 8 + - Otherwise prompt: "Apply these suggestions? (yes/no/show-details)" - Only link epics with good matches (≥50%) -7. **Execute linking operations**: +7. **Execute linking operations** (skip if --dry-run): - For each approved linking: - - Update epic via PUT `/rest/api/3/issue/{epicKey}` + - **TOCTOU check**: GET `/rest/api/3/issue/{epicKey}?fields=parent` to verify Parent Link is still empty + - If Parent Link is not empty: skip and log "Epic already linked to {existing_initiative}" + - Otherwise, update epic via PUT `/rest/api/3/issue/{epicKey}` - Set Parent Link field to initiative key - Rate limit: 0.5s between requests diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.report.md b/workflows/jira-hygiene/.claude/commands/hygiene.report.md index 709209b..797bea3 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.report.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.report.md @@ -93,8 +93,9 @@ Optional: h. **Missing Activity Type WITH PAGINATION**: ```jql - ({base_jql}) AND "Activity Type" is EMPTY + ({base_jql}) AND "{activity_type_field_id}" is EMPTY ``` + - Use activity_type_field_id from config.json (e.g., "customfield_10050") - Paginate to count ALL - List top 10 by priority diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md b/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md index d8381d3..c8d0c7b 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.triage-new.md @@ -22,9 +22,11 @@ Optional: 2. **Query untriaged items WITH PAGINATION**: ```jql - ({base_jql}) AND status = New AND created < -{DAYS}d + ({base_jql}) AND status = New AND status changed TO New BEFORE -{DAYS}d ``` + **Note**: Uses time-in-status (status changed TO New) instead of creation date to avoid misclassifying tickets moved back to New + **Pagination logic**: ``` all_untriaged = [] @@ -116,13 +118,15 @@ Optional: → Priority: Medium (default, no similar items) ``` -6. **Ask for confirmation**: +6. **Ask for confirmation** (batch mode): - If `--dry-run`: Skip, display "DRY RUN - No changes made" - - Otherwise prompt: "Apply triage suggestions? (yes/no/high-confidence-only)" + - Otherwise, split approved items into batches of max 50 + - For each batch, prompt: "Apply triage suggestions? (yes/no/high-confidence-only)" + - Only proceed on exact response "yes" (reject other responses) - "high-confidence-only": Only apply suggestions with ≥5 similar items -7. **Execute triage**: - - For each approved item: +7. **Execute triage** (per batch): + - For each approved item in current batch: - Update priority via PUT `/rest/api/3/issue/{key}` - Transition to "Backlog" status - Add comment: "Auto-triaged based on similar items. Priority set to {PRIORITY}." diff --git a/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md b/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md index bf7f90c..2017cbc 100644 --- a/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md +++ b/workflows/jira-hygiene/.claude/commands/hygiene.unassigned-progress.md @@ -16,9 +16,11 @@ Simple query to find tickets that are marked as "In Progress" but have no assign 2. **Query unassigned in-progress tickets WITH PAGINATION**: ```jql - ({base_jql}) AND status = "In Progress" AND assignee is EMPTY + ({base_jql}) AND statusCategory = "In Progress" AND assignee is EMPTY ``` + **Note**: Uses statusCategory instead of hardcoded status name to match all in-progress statuses across different projects + **Pagination logic**: ``` all_tickets = [] diff --git a/workflows/jira-hygiene/CLAUDE.md b/workflows/jira-hygiene/CLAUDE.md index 28439f6..af309db 100644 --- a/workflows/jira-hygiene/CLAUDE.md +++ b/workflows/jira-hygiene/CLAUDE.md @@ -195,7 +195,7 @@ Customize which tickets are included in all operations using base_jql: Before submitting PR, verify: 1. **Validate JSON**: `jq . .ambient/ambient.json` (no syntax errors) -2. **Check commands**: All 10 command files exist in `.claude/commands/` +2. **Check commands**: All 11 command files exist in `.claude/commands/` 3. **Test dry-run**: Run `/hygiene.close-stale --dry-run` without making changes 4. **Verify logging**: Operation logs contain timestamp, action, results 5. **Check rate limiting**: Monitor API call timing (≥0.5s gaps)