diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 93cf7985d..79e9bc84a 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -92,7 +92,7 @@ "name": "jira", "source": "./plugins/jira", "description": "A plugin to automate tasks with Jira", - "version": "0.9.1", + "version": "0.9.2", "category": "productivity", "keywords": [ "jira", diff --git a/docs/index.html b/docs/index.html index 077cc25d4..281a4fe21 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1490,7 +1490,7 @@

ai-helpers

{ "name": "jira", "description": "A plugin to automate tasks with Jira", - "version": "0.9.1", + "version": "0.9.2", "has_readme": true, "commands": [ { @@ -1533,6 +1533,14 @@

ai-helpers

"synopsis": "/jira:clone-from-github \u003cissue-number\u003e [issue-number...] [options]", "body_html": "\u003cp\u003eThe \u003ccode\u003ejira:clone-from-github\u003c/code\u003e command clones one or more GitHub issues to Jira, preserving the original issue content and establishing a link between the GitHub and Jira issues. This command is useful for:\u003c/p\u003e\u003cp\u003e- Migrating GitHub issues to Jira for project tracking\u003cbr\u003e- Creating Jira tickets that track upstream GitHub issues\u003cbr\u003e- Maintaining synchronization between GitHub and Jira workflows\u003cbr\u003e- Bulk importing GitHub issues into Jira projects\u003c/p\u003e\u003cp\u003eThe command uses the \u003ca href=\"https://github.com/oceanc80/gh2jira\"\u003egh2jira utility\u003c/a\u003e to perform the cloning operation. Use \u003ccode\u003e/jira:setup-gh2jira\u003c/code\u003e to install and configure gh2jira if you haven't already.\u003c/p\u003e" }, + { + "name": "create-jira-from-file", + "full_name": "create-jira-from-file - Create Jira issues from a markdown file", + "description": "Create Jira issues from a markdown file (works with any Jira, includes RH defaults)", + "description_html": "Create Jira issues from a markdown file (works with any Jira, includes RH defaults)", + "synopsis": "/jira:create-jira-from-file \u003cpath-to-markdown-file\u003e", + "body_html": "\u003cp\u003eCreate one or more Jira issues from a markdown file. Works with any Jira project and extracts all metadata from the file itself. Includes Red Hat-specific defaults (security level, project conventions) that are automatically omitted when not available.\u003c/p\u003e\u003cp\u003eRequired metadata (project key) and any optional fields (component, version, type, parent) must be specified in the markdown file itself. Issue type can be set explicitly or auto-detected from content.\u003c/p\u003e\u003cp\u003e### Required Metadata\u003c/p\u003e\u003cp\u003eEach issue must specify:\u003cbr\u003e- \u003ccode\u003e\u003cstrong\u003eProject:\u003c/strong\u003e <project-key>\u003c/code\u003e โ€” REQUIRED\u003c/p\u003e\u003cp\u003e### Strongly Recommended Metadata\u003c/p\u003e\u003cp\u003e- \u003ccode\u003e\u003cstrong\u003eType:\u003c/strong\u003e <issue-type>\u003c/code\u003e โ€” Story, Bug, Task, Epic, Feature, Initiative, or Sub-task. If omitted, the skill auto-detects from content patterns and confirms with the user when ambiguous.\u003c/p\u003e\u003cp\u003e### Optional Metadata\u003c/p\u003e\u003cp\u003e- \u003ccode\u003e\u003cstrong\u003eComponent:\u003c/strong\u003e <component-name>\u003c/code\u003e\u003cbr\u003e- \u003ccode\u003e\u003cstrong\u003eVersion:\u003c/strong\u003e <version>\u003c/code\u003e\u003cbr\u003e- \u003ccode\u003e\u003cstrong\u003eParent:\u003c/strong\u003e <parent-issue-key>\u003c/code\u003e\u003cbr\u003e- \u003ccode\u003e\u003cstrong\u003ePriority:\u003c/strong\u003e <priority-name>\u003c/code\u003e\u003cbr\u003e- \u003ccode\u003e\u003cstrong\u003eLabels:\u003c/strong\u003e <label1>, <label2>\u003c/code\u003e\u003c/p\u003e" + }, { "name": "create-release-note", "full_name": "jira:create-release-note", @@ -1649,6 +1657,12 @@

ai-helpers

"description_html": "Create Jira issues โ€” story, bug, epic, feature, initiative, task, or feature-request โ€” with CNTRLPLANE, OCPBUGS, GCP, HyperShift, ARO, ROSA conventions and type-specific templates", "meta": "" }, + { + "name": "create-jira-from-file", + "description": "Create Jira issues from a markdown file (single or batch). Works with any Jira project but includes Red Hat-specific defaults (security level, project conventions) that are automatically omitted when not available. Use whenever the user asks to create Jira from a file, create tickets/issues from markdown, batch-create Jira issues from a planning doc, or run /jira:create-jira-from-file. Extracts Project, Type, Component, Version, Parent, Priority, and Labels from the file.\n", + "description_html": "Create Jira issues from a markdown file (single or batch). Works with any Jira project but includes Red Hat-specific defaults (security level, project conventions) that are automatically omitted when not available. Use whenever the user asks to create Jira from a file, create tickets/issues from markdown, batch-create Jira issues from a planning doc, or run /jira:create-jira-from-file. Extracts Project, Type, Component, Version, Parent, Priority, and Labels from the file.", + "meta": "" + }, { "name": "create-release-note", "description": "Detailed implementation guide for generating bug fix release notes from Jira and GitHub PRs", diff --git a/plugins/jira/.claude-plugin/plugin.json b/plugins/jira/.claude-plugin/plugin.json index d9bcc7613..febc2b04c 100644 --- a/plugins/jira/.claude-plugin/plugin.json +++ b/plugins/jira/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "jira", "description": "A plugin to automate tasks with Jira", - "version": "0.9.1", + "version": "0.9.2", "author": { "name": "github.com/openshift-eng" } diff --git a/plugins/jira/README.md b/plugins/jira/README.md index db7f34db9..abb89a758 100644 --- a/plugins/jira/README.md +++ b/plugins/jira/README.md @@ -10,7 +10,7 @@ Comprehensive Jira integration for Claude Code, providing AI-powered tools to an - ๐Ÿ“‹ **Backlog Grooming** - Analyze new bugs and cards for grooming meetings - ๐Ÿท๏ธ **Activity Type Classification** - AI-powered classification of JIRA tickets into Sankey activity types, with single-issue and batch modes - ๐Ÿงช **Test Generation** - Generate comprehensive test steps for JIRA issues by analyzing related PRs -- โœจ **Issue Creation** - Create well-formed stories, epics, features, tasks, bugs, and feature requests with guided workflows +- โœจ **Issue Creation** - Create well-formed stories, epics, features, tasks, bugs, and feature requests with guided workflows, or batch-create from markdown files - ๐Ÿ“ **Release Note Generation** - Automatically generate bug fix release notes from Jira and linked GitHub PRs - ๐Ÿค– **Automated Workflows** - From issue analysis to PR creation, fully automated - ๐Ÿ’ฌ **Smart Comment Analysis** - Extracts blockers, risks, and key insights from comments @@ -224,6 +224,31 @@ See [commands/create.md](commands/create.md) for full documentation. --- +### `/jira:create-jira-from-file` - Create Issues from Markdown + +Create one or more Jira issues from a markdown file. Project-agnostic: metadata (`**Project:**`, `**Type:**`, component, version, parent, etc.) comes from the file. Supports single-issue and batch modes with security scanning and parent hierarchy validation. + +**Usage:** +```bash +# Single issue +/jira:create-jira-from-file feature-spec.md + +# Batch (multiple issues in one file) +/jira:create-jira-from-file sprint-planning.md +``` + +**Key Features:** +- **File-driven metadata** โ€” no project hard-coding; prompts when required fields are missing +- **Batch mode** โ€” multiple type-prefixed or non-content H2 sections in one file +- **Security scanning** โ€” blocks credential exposure before any create +- **Parent linking** โ€” validates hierarchy for pre-existing and in-batch parents + +**Examples:** [examples/create-jira-from-file-single-story.md](examples/create-jira-from-file-single-story.md), [examples/create-jira-from-file-batch.md](examples/create-jira-from-file-batch.md) + +See [commands/create-jira-from-file.md](commands/create-jira-from-file.md) for full documentation. + +--- + ### `/jira:create-release-note` - Generate Bug Fix Release Notes Automatically generate bug fix release notes by analyzing Jira bug tickets and their linked GitHub pull requests. The command extracts Cause and Consequence from the bug description, analyzes PR content (description, commits, code changes, comments), synthesizes the information into a cohesive release note, and updates the Jira ticket. diff --git a/plugins/jira/commands/create-jira-from-file.md b/plugins/jira/commands/create-jira-from-file.md new file mode 100644 index 000000000..cdcdb0fe9 --- /dev/null +++ b/plugins/jira/commands/create-jira-from-file.md @@ -0,0 +1,122 @@ +--- +name: create-jira-from-file +description: Create Jira issues from a markdown file (works with any Jira, includes RH defaults) +argument-hint: "" +--- + +## Name + +create-jira-from-file - Create Jira issues from a markdown file + +## Synopsis + +```bash +/jira:create-jira-from-file +``` + +## Description + +Create one or more Jira issues from a markdown file. Works with any Jira project and extracts all metadata from the file itself. Includes Red Hat-specific defaults (security level, project conventions) that are automatically omitted when not available. + +Required metadata (project key) and any optional fields (component, version, type, parent) must be specified in the markdown file itself. Issue type can be set explicitly or auto-detected from content. + +### Required Metadata + +Each issue must specify: +- `**Project:** ` โ€” REQUIRED + +### Strongly Recommended Metadata + +- `**Type:** ` โ€” Story, Bug, Task, Epic, Feature, Initiative, or Sub-task. If omitted, the skill auto-detects from content patterns and confirms with the user when ambiguous. + +### Optional Metadata + +- `**Component:** ` +- `**Version:** ` +- `**Parent:** ` +- `**Priority:** ` +- `**Labels:** , ` + +## Examples + +### Single Issue + +```bash +/jira:create-jira-from-file feature-spec.md +``` + +### Batch Mode + +```bash +/jira:create-jira-from-file sprint-planning.md +``` + +## Markdown Format + +### Single Issue Example + +```markdown +# Enable autoscaling for clusters + +**Project:** PLATFORM +**Type:** Story +**Component:** Infrastructure +**Version:** 2.5 + +As a cluster admin, I want to configure autoscaling, so that I can handle traffic spikes. + +## Acceptance Criteria +- [ ] Scales up when CPU > 80% +- [ ] Scales down when CPU < 30% +``` + +### Batch Mode Example + +```markdown +## Story: Add user dashboard +**Project:** FRONTEND +**Component:** Console + +As a developer, I want a dashboard to monitor applications. + +### Acceptance Criteria +- [ ] Shows running pods + +--- + +## Bug: API returns 500 error +**Project:** BACKEND +**Priority:** High + +Description: API crashes on special characters. + +Steps to Reproduce: +1. Create resource with special chars +2. Observe 500 error + +Expected: 400 with validation error +``` + +## Features + +- **Project-agnostic:** Works with any Jira project +- **Batch processing:** Create multiple issues from one file +- **Security scanning:** Detects and blocks credential exposure +- **Type auto-detection:** Infers issue type from content patterns +- **Parent linking:** Validates and links parent-child relationships +- **Error handling:** Reports missing components and versions with suggestions, logs permission errors, and continues batch processing on per-issue failures + +## Notes + +- The skill makes no assumptions about projects or components +- Metadata should be provided in the markdown file; missing required fields are prompted interactively +- Batch mode is auto-detected from file structure (multiple type-prefixed or non-content H2 headers; `---` alone is not enough) + +## Arguments + +- **path-to-markdown-file** (required): Path to the markdown file containing Jira issue definitions + - File must contain issue metadata fields (**Project**, **Type**, etc.) + - Can contain single issue or multiple issues (for batch processing) + - Example: `feature-spec.md`, `sprint-planning.md` + +Invokes the `create-jira-from-file` skill. diff --git a/plugins/jira/examples/create-jira-from-file-batch.md b/plugins/jira/examples/create-jira-from-file-batch.md new file mode 100644 index 000000000..ad651e48d --- /dev/null +++ b/plugins/jira/examples/create-jira-from-file-batch.md @@ -0,0 +1,133 @@ +# Sprint 42 - Q3 Feature Development + +**Project:** CNTRLPLANE + +--- + +## Story: Add real-time metrics dashboard + +**Type:** Story +**Component:** Console +**Priority:** High +**Labels:** ui, dashboard, metrics + +### User Story + +As a platform engineer, I want a real-time dashboard showing cluster health metrics, so that I can quickly identify and respond to performance issues. + +### Acceptance Criteria + +- [ ] Dashboard displays CPU, memory, disk, and network metrics +- [ ] Metrics update in real-time (refresh every 5 seconds) +- [ ] Historical data available for last 7 days +- [ ] Filtering by cluster, node pool, and namespace +- [ ] Exportable to CSV/JSON for reporting + +### Technical Notes + +Use Grafana embedded panels with Prometheus as data source. Dashboard should be accessible at `/console/metrics`. + +--- + +## Bug: API returns 500 error on special characters in resource names + +**Type:** Bug +**Component:** API Gateway +**Priority:** Critical +**Labels:** api, security + +### Description + +API server crashes when creating resources with special characters in names, returning HTTP 500 instead of validation error. + +### Steps to Reproduce + +1. Send POST request to `/api/v1/clusters` with payload: + ```json + { + "name": "my-cluster", + "region": "us-east-1" + } + ``` +2. Observe 500 Internal Server Error response +3. Check API server logs โ€” shows panic/crash + +### Expected Behavior + +API should return HTTP 400 with validation error: +```json +{ + "error": "Invalid cluster name: contains illegal characters", + "allowed_pattern": "^[a-z0-9-]+$" +} +``` + +### Actual Behavior + +Returns 500 error, crashes API server process, requires restart. + +### Environment + +- API version: v1.5.2 +- Platform: OpenShift 4.15 +- Affected endpoints: `/api/v1/clusters`, `/api/v1/nodepools` + +--- + +## Task: Update API documentation for v2 endpoints + +**Type:** Task +**Component:** Documentation +**Priority:** Medium +**Labels:** docs, api + +### Description + +Update API reference documentation to include new v2 endpoints introduced in Sprint 41. + +### Definition of Done + +- [ ] Swagger/OpenAPI spec updated with v2 endpoints +- [ ] Code examples added for all CRUD operations +- [ ] Authentication/authorization requirements documented +- [ ] Migration guide from v1 to v2 added +- [ ] Documentation published to developer portal + +### Context + +New v2 endpoints were added in CNTRLPLANE-500 but documentation was not updated. Customers are filing support tickets asking about v2 usage. + +--- + +## Story: Implement cluster backup and restore + +**Type:** Story +**Component:** Storage +**Priority:** High +**Version:** openshift-4.17 +**Labels:** backup, disaster-recovery + +### User Story + +As a cluster admin, I want to backup and restore cluster state (including etcd, configs, and persistent volumes), so that I can recover from disasters or migrate clusters. + +### Acceptance Criteria + +- [ ] Automated daily backups of etcd snapshots +- [ ] Backup includes cluster configs, secrets, and RBAC policies +- [ ] Backup stored in object storage (S3-compatible) +- [ ] Restore operation validated in test environment +- [ ] Recovery Time Objective (RTO) < 1 hour +- [ ] Recovery Point Objective (RPO) < 24 hours + +### Technical Notes + +Use Velero for backup/restore orchestration. Integration with: +- AWS S3 for backup storage +- Encrypted backups with KMS keys +- Automated retention policy (keep 30 daily, 12 monthly) + +### Dependencies + +- CNTRLPLANE-200 โ€” Object storage integration must be complete +- CNTRLPLANE-201 โ€” Encryption key rotation implemented diff --git a/plugins/jira/examples/create-jira-from-file-single-story.md b/plugins/jira/examples/create-jira-from-file-single-story.md new file mode 100644 index 000000000..38dc24c53 --- /dev/null +++ b/plugins/jira/examples/create-jira-from-file-single-story.md @@ -0,0 +1,48 @@ +# Enable autoscaling configuration for clusters + +**Project:** CNTRLPLANE +**Type:** Story +**Component:** HyperShift +**Version:** openshift-4.16 +**Priority:** High + +## User Story + +As a cluster admin, I want to configure autoscaling for my HyperShift-managed clusters, so that I can automatically handle traffic spikes without manual intervention. + +## Acceptance Criteria + +- [ ] Node pools scale up when average CPU utilization exceeds 80% for 5 minutes +- [ ] Node pools scale down when average CPU utilization drops below 30% for 10 minutes +- [ ] Scaling operations respect configured min/max node limits +- [ ] Scaling events are logged and visible in cluster audit logs +- [ ] Autoscaling can be enabled/disabled per node pool via CLI and UI + +## Context + +Currently, cluster admins must manually scale node pools by adjusting replica counts. This leads to: +- Over-provisioning (wasted resources during low traffic) +- Under-provisioning (degraded performance during traffic spikes) +- Operational overhead (24/7 monitoring required) + +Competitors (EKS, GKE, AKS) all provide native autoscaling. We need parity. + +## Technical Notes + +Implementation should use: +- Kubernetes Cluster Autoscaler for node-level scaling +- HorizontalPodAutoscaler for pod-level scaling +- Custom metrics from Prometheus for scaling decisions + +Integration points: +- CNTRLPLANE-100 โ€” Metrics collection pipeline must be deployed +- CNTRLPLANE-101 โ€” IAM permissions for node provisioning required + +## Testing Notes + +Test scenarios: +1. Gradual load increase (5% CPU โ†’ 95% CPU over 30 minutes) +2. Sudden load spike (10% โ†’ 90% in 1 minute) +3. Scale-down behavior after traffic drop +4. Min/max limit enforcement +5. Multiple concurrent node pools with different configs diff --git a/plugins/jira/skills/_shared/README.md b/plugins/jira/skills/_shared/README.md new file mode 100644 index 000000000..ae5db0431 --- /dev/null +++ b/plugins/jira/skills/_shared/README.md @@ -0,0 +1,22 @@ +# Shared Jira Skills Logic + +This directory contains common validation and execution logic shared across multiple Jira creation skills (`create`, `create-jira-from-file`, etc.). + +## Files + +- **`validation-common.md`** โ€” Security credential scanning, summary validation, parent hierarchy checks, component/version validation +- **`execution-common.md`** โ€” Universal defaults, custom field ID resolution, MCP error handling patterns, jira-conventions invocation + +## Usage + +Skills include these files via reference in their SKILL.md: + +```markdown +## Phase 4: Validate + +**Load and follow:** [`../_shared/validation-common.md`](../_shared/validation-common.md) +``` + +## Maintenance + +When updating validation or execution logic that applies to ALL Jira creation workflows, update the shared files here. Skill-specific logic (CLI parsing, file ingestion, batch handling) stays in the individual skill directories. diff --git a/plugins/jira/skills/_shared/execution-common.md b/plugins/jira/skills/_shared/execution-common.md new file mode 100644 index 000000000..195f90ac8 --- /dev/null +++ b/plugins/jira/skills/_shared/execution-common.md @@ -0,0 +1,302 @@ +# Common Jira Issue Execution Logic + +**Shared across:** `jira:create`, `jira:create-jira-from-file`, and other Jira creation skills + +This file documents common patterns for creating Jira issues via MCP tools. Invoking skills should adapt these patterns to their specific workflows (single vs batch, interactive vs automated, etc.). + +--- + +## Universal Defaults + +Apply to **ALL** issues regardless of project: + +```json +{ + "labels": ["ai-generated-jira"], + "contentFormat": "markdown" +} +``` + +**Security Level (conditional, Red Hat-specific default):** + +**Note:** This default is optimized for Red Hat's Jira instance. For other organizations: +- Modify `"Red Hat Employee"` to your organization's security level name +- Or remove this block entirely if security levels are not used + +```python +# Check if project supports "Red Hat Employee" security level +security_levels = getJiraProjectIssueTypesMetadata(issue.project).get("securityLevels", []) +if any(level["name"] == "Red Hat Employee" for level in security_levels): + fields["security"] = {"name": "Red Hat Employee"} +# Otherwise omit โ€” not all projects have this security level +``` + +--- + +## Project-Specific Conventions + +Before creating issues, invoke the `jira:jira-conventions` skill for each distinct project to apply team-specific defaults: + +```python +for project_key in {issue.project for issue in issues}: + try: + conventions = invoke_skill("jira:jira-conventions", project=project_key) + if conventions: + apply_conventions(issues, project_key, conventions) + # Conventions layer ON TOP of universal defaults + except SkillNotFoundError: + pass # Not installed โ€” skip silently + except SkillNotApplicableError: + pass # Installed but doesn't cover this project +``` + +**Do NOT hard-code project names** โ€” convention support is discovered at runtime. + +--- + +## Custom Field ID Resolution + +Jira custom field IDs vary by project and instance. Resolve them dynamically via `getJiraIssueTypeMetaWithFields`: + +```python +type_meta = getJiraIssueTypeMetaWithFields(project=issue.project, issuetype=issue.type) +field_ids = resolve_field_ids(type_meta) +# resolve_field_ids looks up fields by name/schema: +# "Epic Name" โ†’ epic_name_field (e.g., customfield_10011) +# "Target Version" โ†’ target_version_field (e.g., customfield_10855) +# Prefer conventions overrides when jira-conventions returned IDs +``` + +**Target Version field** (common example): + +```python +if issue.version: + version_id = find_version_id(type_meta, issue.version) + target_version_field = field_ids.get("target_version") + if version_id and target_version_field: + # Format varies by project โ€” delegate to conventions if available + version_value = apply_version_format(issue.project, version_id) + fields[target_version_field] = version_value if version_value else [{"id": version_id}] +``` + +**Epic Name field** (for Epic types): + +```python +if issue.type == "Epic" and field_ids.get("epic_name"): + fields[field_ids["epic_name"]] = issue.summary +``` + +--- + +## Parent Linking + +Set `parent` field when `parent_key` is present: + +```python +if issue.parent_key: + fields["parent"] = {"key": issue.parent_key} +``` + +**Pre-existing parents** (e.g., CNTRLPLANE-100 from user input) โ€” validated in Phase 4; set unconditionally when present. + +**In-batch parents** โ€” create parent issues first (hierarchy ordering), then rewrite child's `parent_key` to the newly created Jira key before creating the child. + +--- + +## MCP Error Handling Patterns + +Create issues with an ordered exception handler chain โ€” specific exceptions before generic fallback: + +### Pattern: ParentLinkError Fallback + +If parent linking fails during creation, create without parent and link via `editJiraIssue`: + +```python +try: + result = createJiraIssue( + project=issue.project, + issuetype=issue.type, + summary=issue.summary, + description=description, + additional_fields=fields, + contentFormat="markdown" + ) + created_keys[issue.id] = result["key"] + successes.append({"key": result["key"], "summary": issue.summary, "url": result["url"]}) + +except ParentLinkError as e: + # Create without parent using same contract + extra = {k: v for k, v in fields.items() + if k not in ("project", "issuetype", "summary", "description", "parent")} + result = createJiraIssue( + project=issue.project, + issuetype=issue.type, + summary=issue.summary, + description=description, + additional_fields=extra, + contentFormat="markdown" + ) + created_keys[issue.id] = result["key"] + # Retry parent link via edit + try: + editJiraIssue( + issue_key=result["key"], + update_fields={"parent": {"key": issue.parent_key}}, + contentFormat="markdown" + ) + except Exception as link_error: + warnings.append({ + "key": result["key"], + "message": f"Created but failed to link parent {issue.parent_key}: {link_error}" + }) +``` + +### Pattern: ComponentNotFoundError Retry + +Phase 4 validation should catch invalid components, but if one slips through, retry without the component: + +```python +except ComponentNotFoundError as e: + project_meta = getJiraProjectIssueTypesMetadata(project=issue.project) + available = [c["name"] for c in project_meta.get("components", [])] + fields.pop("components", None) + try: + result = createJiraIssue(...) # Same call without component + successes.append(...) + warnings.append({ + "key": result["key"], + "message": f"Created without component '{issue.component}'. Available: {', '.join(available[:5])}" + }) + except Exception as retry_error: + failures.append({ + "summary": issue.summary, + "error": f"Component '{issue.component}' not found; retry failed: {retry_error}" + }) +``` + +### Pattern: VersionNotFoundError Retry + +Similar to component handling: + +```python +except VersionNotFoundError as e: + available = [v["name"] for v in type_meta.get("versions", {}).get("allowedValues", [])] + if field_ids.get("target_version"): + fields.pop(field_ids["target_version"], None) + try: + result = createJiraIssue(...) # Same call without version + successes.append(...) + warnings.append({ + "key": result["key"], + "message": f"Created without version '{issue.version}'. Available: {', '.join(available[:5])}" + }) + except Exception as retry_error: + failures.append({ + "summary": issue.summary, + "error": f"Version '{issue.version}' not found; retry failed: {retry_error}" + }) +``` + +### Pattern: Permission and Validation Errors + +These are fatal โ€” no retry: + +```python +except PermissionError as e: + failures.append({ + "summary": issue.summary, + "error": f"Permission denied: {str(e)}", + "suggestion": f"Check Jira permissions for project {issue.project}", + "fatal": True + }) + +except FieldValidationError as e: + failures.append({ + "summary": issue.summary, + "error": f"Field validation failed: {e.field} โ€” {e.message}", + "suggestion": "Check field format" + }) +``` + +### Pattern: Generic Fallback + +Catches any unclassified MCP error โ€” MUST be last in the chain: + +```python +except Exception as e: + failures.append({ + "summary": issue.summary, + "error": str(e), + "project": issue.project, + "type": issue.type + }) +``` + +--- + +## Batch Processing Notes + +For batch creation (create-jira-from-file), track state across issues: + +```python +created_jira_keys = {} # Maps issue.id โ†’ result Jira key +jira_key_to_issue_id = {} # Reverse: result key โ†’ issue.id +failed_batch_ids = set() # issue.id values that failed + +# Skip children of failed in-batch parents: +parent_batch_id = jira_key_to_issue_id.get(issue.parent_key) +if parent_batch_id and parent_batch_id in failed_batch_ids: + failures.append({"summary": issue.summary, "error": f"Parent {issue.parent_key} failed"}) + failed_batch_ids.add(issue.id) + continue +``` + +Mark failures immediately in all exception handlers so dependent children are skipped: + +```python +except ComponentNotFoundError as e: + failed_batch_ids.add(issue.id) # Mark before recording failure + # ... rest of handler +``` + +--- + +## MCP Tools Reference + +| Tool | Purpose | +|------|---------| +| `createJiraIssue` | Create issue with project, issuetype, summary, description, additional_fields | +| `editJiraIssue` | Update existing issue (parent linking fallback, field corrections) | +| `getJiraIssue` | Fetch issue metadata (parent validation) | +| `getJiraProjectIssueTypesMetadata` | Discover available types, components, security levels for a project | +| `getJiraIssueTypeMetaWithFields` | Fetch custom field IDs and allowed values for a project/type pair | + +--- + +## Field Format Reference + +**Correct MCP field formats:** + +```python +{ + "project": {"key": "CNTRLPLANE"}, + "issuetype": {"name": "Story"}, + "summary": "Issue title", + "description": "Full description (markdown if contentFormat='markdown')", + "labels": ["label1", "label2"], + "components": [{"name": "ComponentName"}], + "priority": {"name": "High"}, + "parent": {"key": "PARENT-123"}, + "security": {"name": "Red Hat Employee"}, + "customfield_10011": "Epic Name value", # Epic Name + "customfield_10855": [{"id": "12345"}] # Target Version (array format) +} +``` + +**Common mistakes:** + +| Wrong | Correct | +|-------|---------| +| `"project": "CNTRLPLANE"` | `"project": {"key": "CNTRLPLANE"}` | +| `"components": "Name"` | `"components": [{"name": "Name"}]` | +| `"customfield_10855": "openshift-4.21"` | `"customfield_10855": [{"id": "VERSION_ID"}]` (or string if project uses that format) | diff --git a/plugins/jira/skills/_shared/validation-common.md b/plugins/jira/skills/_shared/validation-common.md new file mode 100644 index 000000000..b71192d4f --- /dev/null +++ b/plugins/jira/skills/_shared/validation-common.md @@ -0,0 +1,327 @@ +# Common Jira Issue Validation Logic + +**Shared across:** `jira:create`, `jira:create-jira-from-file`, and other Jira creation skills + +**Model note (optional):** Prefer a stronger model for this security-critical validation when the host allows model selection; otherwise continue with the current model. + +Validate all parsed issue(s) before creation using a **two-pass approach** that separates non-interactive checks from interactive remediation: + +1. **Pass 1 โ€” Non-interactive checks (run first, no user prompts):** + - Security credential scanning (BLOCKS immediately if credentials found) + - Summary anti-pattern detection + - Parent hierarchy validation (API fetch, no user input) + - Component/version existence checks (API lookup, no user input) + +2. **Pass 2 โ€” Interactive remediation (single prompt session after all checks complete):** + - Collect all issues requiring user input across all batch items + - Present all required inputs together (missing type, missing project, summary fixes, etc.) + - Do NOT interleave prompts with API calls + +This separation ensures that API lookups can proceed without blocking on user input, and that all prompts are batched into one interaction phase. If security credentials are detected in Pass 1, stop immediately โ€” do not proceed to Pass 2. + +**Invoking skills must provide:** Issue objects with fields: `summary`, `description`, `project`, `type`, `parent_key` (optional), `component` (optional), `version` (optional), and any extracted content fields. + +### Security Validation (CRITICAL) + +**Priority:** Run FIRST โ€” credentials detected = immediate BLOCK, no creation. + +Scan **all text fields** that will be sent to Jira (summary, description, acceptance criteria, steps to reproduce, expected behavior, actual behavior, user story, environment, technical notes, testing notes) for exposed credentials and secrets: + +**Credential patterns to detect:** + +- **AWS credentials:** Access keys (`AKIA[0-9A-Z]{16}`, `ASIA[0-9A-Z]{16}`), secret keys (base64url, ~40 chars, near `aws_secret`/`AWS_SECRET`), session tokens (`FwoGZXIvYXdzE`-prefixed) +- **API tokens:** Bearer tokens, OAuth tokens, JWT tokens (three base64url segments separated by `.`) +- **GCP service accounts:** JSON with `"private_key"`, `"client_email"`, `"type": "service_account"` +- **Azure secrets:** Storage account keys (base64, 88 chars), SAS tokens (`sig=`), client secrets +- **Private keys:** PEM header lines of the form `BEGIN PRIVATE KEY` (RSA, EC, OpenSSH, PKCS8, PGP variants) +- **Kubeconfigs:** YAML with `clusters:`, `users:`, `certificate-authority-data:`, client certificates +- **Database credentials:** Connection strings with embedded passwords (PostgreSQL, MySQL, MongoDB URIs) +- **Generic secrets:** High-entropy strings (>20 chars) near keywords `password`, `secret`, `token`, `key` +- **URLs with credentials:** `https://user:pass@host`, URLs with `token=` or `key=` query parameters + +**Action on detection:** + +```plaintext +BLOCKED: Credentials detected in issue content + +Found: (e.g., "AWS access key", "kubeconfig", "API token") +Location: (e.g., "description", "acceptance criteria") + +DO NOT create this issue. Credentials must never be stored in Jira. + +Next steps: +1. Remove or redact the credential from the markdown file +2. Use placeholders: YOUR_API_KEY, YOUR_AWS_ACCESS_KEY, +3. Re-run the creation command after sanitizing content +``` + +**CRITICAL:** Do NOT echo the actual credential value in the error message. Only report the credential type and location. + +### Summary Validation + +Check each issue's summary for common anti-patterns: + +**Anti-pattern 1: User story in summary** + +If summary contains: +- Starts with "As a" / "As an" +- Contains "I want" or "so that" +- Longer than 100 characters + +```plaintext +Summary looks like a full user story. Summaries should be concise titles (โ‰ค100 chars). + +Current: "As a cluster admin, I want to configure autoscaling so that I can handle traffic spikes" + +Suggested: "Enable autoscaling configuration for clusters" + +Fix automatically? (yes/no/edit) +``` + +**Anti-pattern 2: Overly long summary** + +If summary exceeds 100 characters but doesn't match user story pattern: + +```plaintext +Summary is too long (125 chars). Consider shortening to โ‰ค100 chars. + +Current: "" + +Options: +1. Auto-truncate to 100 chars +2. Manually edit summary +3. Proceed anyway (not recommended) +``` + +**Anti-pattern 3: Missing action verb (for tasks)** + +If type is Task and summary lacks an action verb (Configure, Update, Implement, Refactor, Document, etc.): + +```plaintext +Task summaries should start with an action verb. + +Current: "API documentation" + +Suggested: "Update API documentation" + +Apply suggestion? (yes/no/edit) +``` + +**Action on detection:** + +- Offer automatic fix (extract concise title from user story, truncate long summaries) +- Allow user to edit manually +- Allow proceeding anyway (for edge cases) +- Update the parsed issue object with corrected summary if user accepts + +### Required Field Validation + +Verify presence of mandatory fields for each issue: + +**Required fields:** + +- `project` โ€” Project key (e.g., PLATFORM, OCPBUGS, CNTRLPLANE) +- `type` โ€” Issue type (Story, Bug, Epic, Task, Feature, Initiative, Sub-task) + +**Pass 1 โ€” detect silently (no prompts yet):** + +```python +missing = [] +for issue in issues: + if not issue.project: + missing.append((issue, "project")) + if not issue.type: + detected_type = auto_detect_type(issue) + if detected_type: + issue.type = detected_type # apply silently + issue.type_auto_detected = True + else: + missing.append((issue, "type")) +``` + +**Auto-detection heuristics for type:** + +- Contains "bug", "error", "crash", "broken" โ†’ Bug +- Contains "epic:", "theme:", or has child issues โ†’ Epic +- Contains "story:", "as a", acceptance criteria โ†’ Story +- Contains "task:", action verbs (configure, update, refactor) โ†’ Task +- Contains "feature:", strategic language โ†’ Feature +- Contains "initiative:", portfolio/strategic language โ†’ Initiative +- Contains "sub-task:" โ†’ Sub-task +- **Do NOT** treat `**Parent:**` alone as Sub-task โ€” Stories, Tasks, and Epics can also have parents + +**Pass 2 โ€” prompt for everything missing in one session:** + +```python +for issue, field in missing: + if field == "project": + issue.project = prompt_user(f"Issue '{issue.summary}': enter project key (e.g., PLATFORM, OCPBUGS): ") + elif field == "type": + issue.type = prompt_user(f"Issue '{issue.summary}': enter type (Story/Bug/Epic/Task/Feature/Initiative/Sub-task): ") + +# Confirm auto-detected types together +auto_detected = [i for i in issues if getattr(i, "type_auto_detected", False)] +if auto_detected: + show_user([(i.summary, i.type) for i in auto_detected]) + if not confirm("Use these auto-detected types? (yes/no/edit)"): + # allow per-issue correction +``` + +### Parent Hierarchy Validation (if parent specified) + +**Only run if** `parent_key` field is present in the parsed issue. + +Skip hierarchy fetch for in-batch parent references that are not yet real Jira keys (resolve those after the parent is created in Phase 5). For pre-existing keys, validate now. + +**Step 1: Fetch parent issue** + +Use `getJiraIssue` to retrieve parent metadata: + +```json +{ + "issueKey": "" +} +``` + +**Step 2: Handle fetch errors (Pass 1 โ€” record, do not prompt yet)** + +| Error | Record as | +|-------|-----------| +| Parent not found (404) | `issue.parent_validation_error = "not_found"` | +| Permission denied (403) | `issue.parent_validation_error = "permission_denied"` | +| Network/API error (5xx) | Retry once; if still failing: `issue.parent_validation_error = "api_error"` | + +**Step 3: Validate hierarchy level (Pass 1)** + +If parent exists, check that its `hierarchyLevel` is exactly one level above the child: + +| Creating (Child) | Required Parent Level | Parent Type Examples | +|------------------|-----------------------|----------------------| +| Story (level 0) | Level 1 | Epic | +| Task (level 0) | Level 1 | Epic | +| Bug (level 0) | Level 1 | Epic | +| Epic (level 1) | Level 2 | Feature, Initiative | +| Feature (level 2) | Level 3 | Outcome | +| Initiative (level 2) | Level 3 | Outcome | +| Sub-task (level -1) | Level 0 | Story, Task, Bug | + +```python +child_level = get_hierarchy_level(issue.type) +parent_level = parent_issue.fields.issuetype.hierarchyLevel + +if parent_level != child_level + 1: + # Record the mismatch โ€” prompt user in Pass 2 + issue.parent_validation_error = "wrong_level" + issue.parent_validation_detail = { + "expected_types": get_types_at_level(child_level + 1, project), + "actual_parent_type": parent_issue.fields.issuetype.name + } +``` + +**Step 4: Cross-project validation (Pass 1)** + +```python +if parent_issue.fields.project.key != issue.project: + issue.parent_validation_error = "cross_project" + issue.parent_validation_detail = {"parent_project": parent_issue.fields.project.key} +``` + +**Pass 2 โ€” prompt for parent issues in batch:** + +After all API checks complete, present all parent problems together and collect resolutions: +``` +Parent validation issues found: + Issue "Add user dashboard": parent PROJ-100 not found + โ†’ (1) Proceed without parent (2) Enter different parent key (3) Skip this issue + + Issue "Update API docs": parent type mismatch (Bug cannot have Epic as parent; expected Feature or Initiative) + โ†’ (1) Remove parent link (2) Enter different parent key (3) Skip this issue +``` + +### Component/Version Validation + +**Optional fields** โ€” validate if specified. Record failures in Pass 1; offer alternatives in Pass 2. + +**Component validation (Pass 1):** + +```python +project_meta = getJiraProjectIssueTypesMetadata(issue.project) +valid_components = [c["name"] for c in project_meta.get("components", [])] + +if issue.component not in valid_components: + issue.component_suggestion = find_closest_match(issue.component, valid_components) + issue.component_validation_error = True +``` + +**Pass 2 โ€” present all component/version issues together:** +``` +Component/version issues found: + Issue "Enable autoscaling": component 'Infra' not found in PLATFORM + Closest match: 'Infrastructure' + โ†’ (1) Use 'Infrastructure' (2) Remove component (3) Enter manually +``` + +**Version validation (Pass 1):** + +```python +valid_versions = get_project_versions(issue.project) + +if issue.version not in valid_versions: + issue.version_suggestion = find_closest_match(issue.version, valid_versions) + issue.version_validation_error = True +``` + +All version issues are presented together in Pass 2 alongside component issues (see above). + +### Validation Result Aggregation + +After running all validation checks: + +**If all validations pass:** + +```plaintext +โœ“ All validations passed (N issue(s)) + - Security: No credentials detected + - Required fields: Present + - Summary: Valid + - Parent hierarchy: Valid (if applicable) + - Component/version: Valid (if specified) + +Proceeding to Phase 5 (Create Issues)... +``` + +**If any validation fails:** + +```plaintext +โœ— Validation failed for M of N issue(s): + +Issue 1: "Enable autoscaling" + โœ— Summary anti-pattern: User story in summary (auto-fix available) + โœ“ Required fields: Present + โœ“ Security: No credentials + +Issue 2: "Fix API crash" + โœ— BLOCKED: AWS access key detected in description + โœ“ Required fields: Present + +Cannot proceed. Fix issues above and re-run. +``` + +**CRITICAL:** If any issue has a BLOCKED status (credentials detected), do NOT create ANY issues, even those that passed validation. Security failures block the entire batch. + +**Partial success option (non-security failures):** + +If failures are non-security (missing fields, invalid component, etc.), offer to create only the passing issues: + +```plaintext +3 of 5 issues passed validation. Create the valid issues? (yes/no) + +Valid issues: + - "Enable autoscaling" (PLATFORM-TBD) + - "Add user dashboard" (FRONTEND-TBD) + - "Update API docs" (BACKEND-TBD) + +Failed issues (skipped): + - "Fix API crash" (missing Project field) + - "Implement search" (invalid component "Serch") +``` diff --git a/plugins/jira/skills/create-jira-from-file/SKILL.md b/plugins/jira/skills/create-jira-from-file/SKILL.md new file mode 100644 index 000000000..817384955 --- /dev/null +++ b/plugins/jira/skills/create-jira-from-file/SKILL.md @@ -0,0 +1,1201 @@ +--- +name: create-jira-from-file +description: > + Create Jira issues from a markdown file (single or batch). Works with any Jira project + but includes Red Hat-specific defaults (security level, project conventions) that are + automatically omitted when not available. Use whenever the user asks to create Jira from + a file, create tickets/issues from markdown, batch-create Jira issues from a planning + doc, or run /jira:create-jira-from-file. Extracts Project, Type, Component, Version, + Parent, Priority, and Labels from the file. +argument-hint: "" +effort: medium +--- + +# Create Jira from Markdown + +Skill for creating one or more Jira issues from a markdown file. Works with any Jira project and extracts ALL metadata from the markdown file itself โ€” makes no assumptions about projects, components, or versions. Includes Red Hat-specific optimizations (security level, project conventions via jira-conventions skill) that are automatically omitted for non-Red Hat instances. + +**Examples:** [`../../examples/create-jira-from-file-single-story.md`](../../examples/create-jira-from-file-single-story.md), [`../../examples/create-jira-from-file-batch.md`](../../examples/create-jira-from-file-batch.md) + +## Model notes (optional) + +When the host allows choosing models, prefer a lighter model for ingest/plan/summarize and a stronger model for parse, validate, and execute. Most sessions run on a single model โ€” treat the table as guidance only, not a hard requirement. + +| Phase | Prefer | Why | +|-------|--------|-----| +| 1. Ingest | lighter | File I/O and pattern detection | +| 2. Parse | stronger | Markdown structure extraction | +| 3. Plan | lighter | Table formatting | +| 4. Validate | stronger | Credential scanning | +| 5. Execute | stronger | API orchestration | +| 6. Summarize | lighter | Result reporting | + +--- + +## Configuration + +### Default Security Level + +This skill attempts to set `Red Hat Employee` security level on all created issues (see Phase 5): + +```python +security_levels = getJiraProjectIssueTypesMetadata(issue.project).get("securityLevels", []) +if any(level["name"] == "Red Hat Employee" for level in security_levels): + fields["security"] = {"name": "Red Hat Employee"} +``` + +**For non-Red Hat Jira instances:** +- This security level will be omitted automatically if not available in your project +- No configuration needed โ€” the skill queries available security levels dynamically + +**To customize security level behavior:** +- Modify the check in Phase 5 to use your organization's security level name +- Or remove the security level logic entirely for public Jira instances + +### Project-Specific Conventions + +The skill invokes `jira:jira-conventions` dynamically for known Red Hat projects (CNTRLPLANE, OCPBUGS, GCP, etc.). For non-Red Hat projects: +- Convention detection is skipped silently via `SkillNotFoundError` / `SkillNotApplicableError` handling +- Universal defaults (`ai-generated-jira` label, markdown format) still apply +- All metadata must be provided in the markdown file +- The skill remains fully functional without the conventions layer + +--- + +## Prerequisites + +Before starting: +1. Atlassian MCP server must be authenticated (`/mcp` โ†’ Atlassian โ†’ check status) +2. User must provide a markdown file path (via `$ARGUMENTS` or message) +3. User must have Jira create permissions for the target project(s) +4. The markdown file must be readable and contain required metadata + +If MCP server is not authenticated, instruct the user to run `/mcp` and authenticate before proceeding. + +--- + +## Phase 1: Ingest & Mode Detection + +**Task Complexity:** Low (file I/O and pattern matching) + +### Read the Markdown File + +```markdown +1. Read file from $ARGUMENTS (first argument is the file path) +2. Verify file is readable and exists +3. Check file is markdown (.md) or text format +4. Do NOT process binary files or executables +``` + +### Detect Mode + +Determine if this is single-issue mode or batch mode: + +**Batch Mode Indicators:** +- File contains multiple H2 headers with type prefixes (two equivalent formats are supported): + - Colon format: `## Story: `, `## Bug: `, `## Task: `, `## Epic: `, `## Feature: `, `## Initiative: `, `## Sub-task: ` + - Bracket format: `## [Story] `, `## [Bug] `, `## [Task] `, `## [Epic] `, `## [Feature] `, `## [Initiative] `, `## [Sub-task] ` + +**OR:** +- File contains multiple issue-boundary headings separated by horizontal rules (`---`) โ€” see rule 3 below (a lone `---` is not enough) + +**Single Issue Mode:** +- Everything else (one H1, no type-prefixed H2s, or simple structure) + +**Detection Logic (checked in order):** + +1. **Type-prefixed H2 headers:** Count headers matching `^## (Story|Bug|Task|Epic|Feature|Initiative|Sub-task)[:\[]` + - If count >= 2: BATCH MODE + +2. **Plain H2 headers:** Count H2 headers that are NOT known content-section names + - Known content sections (not issue boundaries): Description, Overview, Acceptance Criteria, AC, Steps to Reproduce, Repro Steps, Expected Behavior, Actual Behavior, Environment, Technical Notes, Implementation Notes, Testing Notes, Context, Dependencies, Notes, Background, Definition of Done, Reminder, User Story + - If count of non-content H2s >= 2 AND no type prefixes: BATCH MODE + - A single issue with only content-section H2s (e.g., `## Acceptance Criteria`, `## Context`) is NOT batch + +3. **Separator-based (strict):** Strip YAML front matter (`---` block at the very start of the file) first + - Count remaining horizontal rules (`---` alone on a line) + - Count issue-boundary titles in remaining content: H1 headers **or** non-content H2 headers (same exclusion list as rule 2) + - BATCH MODE only if **both** are true: + - separators >= 1 **and** issue-boundary titles >= 2 + - A single-issue doc with one thematic `---` break must stay SINGLE ISSUE MODE + +Otherwise: SINGLE ISSUE MODE + +### Output + +Inform user which mode was detected and proceed to Phase 2. + +--- + +## Phase 2: Parse & Extract + +**Goal:** Parse markdown input and extract structured metadata, summaries, and content sections for each issue. Auto-detect issue types and handle both single-issue and batch modes. + +--- + +### Metadata Extraction + +Extract inline metadata from **bold key:** patterns anywhere in the markdown: + +- **Project:** โ†’ `project` (REQUIRED; if missing, prompt user: "Which project should this issue be created in?" โ€” do not fail immediately) +- **Type:** โ†’ `type` (Story, Task, Bug, Epic, Feature, Initiative, Sub-task; optional, can be auto-detected) +- **Component:** โ†’ `component` (optional) +- **Version:** โ†’ `version` (optional) +- **Parent:** โ†’ `parent_key` (epic/story key like PROJ-123; optional) +- **Priority:** โ†’ `priority` (optional) +- **Labels:** โ†’ `labels` (comma-separated; optional) + +**Rules:** +- Case-insensitive matching for keys (e.g., **project:**, **PROJECT:**, **Project:** all match) +- Values are trimmed strings after the colon +- **Labels:** can be comma/space-separated; normalize to array +- If multiple instances of same key exist, last one wins +- Remove metadata lines from content sections after extraction + +--- + +### Summary Extraction + +**Single mode:** Extract from first H1 (`# Summary text`) +- Use full H1 text as summary +- If no H1 exists, try fallbacks in order: (1) first non-empty sentence, (2) filename without extension +- If all fallbacks fail, prompt user: "No title found. Enter a summary for this issue:" + +**Batch mode:** Extract from H2 headers with type prefixes +- Pattern: `## [Type] Summary text` or `## Summary text` +- Examples: `## [Story] User login`, `## Add dashboard widget` +- Strip type prefix if present (handled in Type Auto-Detection) +- Use remaining text as summary + +**Validation:** +- Summary must be non-empty after trimming +- Warn if summary exceeds 255 characters (Jira limit) + +--- + +### Content Section Extraction + +Map H2/H3 headings to Jira fields based on common patterns: + +**Standard mappings:** +- `Description` / `Overview` โ†’ `description` +- `Acceptance Criteria` / `AC` โ†’ `acceptance_criteria` +- `Steps to Reproduce` / `Repro Steps` โ†’ `steps_to_reproduce` +- `Expected Behavior` / `Expected` โ†’ `expected_behavior` +- `Actual Behavior` / `Actual` โ†’ `actual_behavior` +- `Environment` โ†’ `environment` +- `Technical Notes` / `Implementation Notes` โ†’ `technical_notes` +- `Testing Notes` โ†’ `testing_notes` + +**User story patterns:** +- `As a ... I want ... So that ...` โ†’ extract to `user_story` field +- `Story` / `User Story` heading โ†’ `user_story` + +**Extraction rules:** +- Collect all content under heading until next same-level or higher heading +- Preserve markdown formatting (lists, code blocks, etc.) +- If multiple sections map to same field, concatenate with double newline +- Sections not matching known patterns โ†’ append to `description` with heading as bold prefix + +--- + +### Type Auto-Detection + +If **Type:** not explicitly provided, detect from content patterns: + +**Story indicators:** +- H2/H3 heading contains "User Story", "Story", "Acceptance Criteria" +- Content contains "As a ... I want ... So that ..." pattern +- Default type if batch mode H2 starts with `[Story]` + +**Bug indicators:** +- Contains sections: "Steps to Reproduce", "Expected Behavior", "Actual Behavior" +- H2/H3 headings: "Repro Steps", "Environment", "Error", "Bug" +- Default type if batch mode H2 starts with `[Bug]` + +**Task indicators:** +- No story or bug patterns detected +- Contains: "TODO", "Implementation", "Technical Notes" + +**Epic indicators:** +- Explicitly set via **Type:** or batch prefix `[Epic]` / `Epic:` +- Cannot be auto-detected from content + +**Feature indicators:** +- Explicitly set via **Type:** or batch prefix `[Feature]` / `Feature:` +- Cannot be auto-detected from content + +**Initiative indicators:** +- Explicitly set via **Type:** or batch prefix `[Initiative]` / `Initiative:` +- Cannot be auto-detected from content + +**Sub-task indicators:** +- Explicit `**Type:** Sub-task` in metadata +- Batch mode H2 prefix `[Sub-task]` / `Sub-task:` +- Cannot be auto-detected from content โ€” a `**Parent:**` field alone does NOT imply Sub-task (Stories, Tasks, and Epics can also have parents) + +**Fallback:** Leave type as `null` โ€” Phase 4 validation will detect the missing type and prompt the user to specify it interactively + +--- + +### Batch Mode Splitting + +**Detection:** Input contains multiple H2 headers OR `---` separators + +**Splitting strategies:** + +1. **Type-prefixed H2 headers** (colon or bracket format): + ```markdown + ## Story: First issue # colon format + Content... + + ## [Bug] Second issue # bracket format + Content... + ``` + Split on `## :` or `## []` patterns + +2. **Plain H2 headers:** + ```markdown + ## First issue summary + Content... + + ## Second issue summary + Content... + ``` + Split on each `##` at start of line + +3. **Separator-based:** + ```markdown + # First issue + Content... + + --- + + # Second issue + Content... + ``` + Split on `---` (three or more dashes on own line) + +**Batch processing:** +- Each section inherits metadata from top-level **Key:** declarations +- Section-local **Key:** overrides inherited values +- Parse and extract each section independently +- Return array of issue objects with metadata + content + +**Validation:** +- Each batch entry must have summary (H1 or H2) +- Each batch entry must have **Project:** (inherited or local) +- Warn if batch contains mixed projects + +--- + +### Output Format + +Return structured data for Phase 3: + +```json +{ + "mode": "single | batch", + "issues": [ + { + "id": 0, + "project": "PROJ", + "type": "Story", + "summary": "Issue summary", + "component": "ComponentName", + "version": "v1.2.3", + "parent_key": "PROJ-123", + "priority": "High", + "labels": ["label1", "label2"], + "description": "Full description text...", + "acceptance_criteria": "AC text...", + "user_story": "As a... I want... So that...", + "expected_behavior": "Expected outcome...", + "actual_behavior": "Actual outcome...", + "environment": "Environment details...", + "technical_notes": "Implementation details...", + "testing_notes": "Testing details..." + } + ] +} +``` + +**Critical:** Never assume or infer project codes, component names, or version strings. Only use explicitly provided values. If required fields are missing, prompt the user interactively rather than failing immediately. + +`id` is assigned during parsing as a zero-based sequential integer matching the issue's position in the `issues` array. It is used as the stable key for all creation-state tracking maps (`created_jira_keys`, `jira_key_to_issue_id`, `failed_batch_ids`) throughout Phase 5. + +--- + +## Phase 3: Plan & Review + +**Task Complexity:** Low-Medium (formatting and presentation) + +### Present Extracted Issues + +**For Batch Mode:** + +```text +Extracted Issues from Markdown: +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +| # | Type | Project | Summary | Component | Version | Parent | +|---|-------|---------|--------------------------|-----------|---------|--------| +| 1 | Story | PROJ-A | Add user dashboard | Frontend | 2.5 | - | +| 2 | Bug | PROJ-B | API returns 500 error | Backend | 3.1 | - | +| 3 | Task | PROJ-A | Update API docs | Docs | 2.5 | PROJ-A-100 | + +Total: 3 issues +Projects: PROJ-A (2), PROJ-B (1) +Types: Story (1), Bug (1), Task (1) +``` + +For each issue, show: +- Type, Project, Summary +- Component (or "none" if not specified) +- Version (or "none" if not specified) +- Parent (or "none" if not specified) +- Priority, custom labels (if specified) +- First 100 characters of description + +**For Single Issue Mode:** + +```text +Extracted Issue: +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +Type: Story +Project: MYPROJECT +Summary: Enable automatic scaling +Component: Infrastructure (from markdown) +Version: 2.5 (from markdown) +Parent: none + +Description Preview: +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +As a cluster admin, I want to configure autoscaling... + +## Acceptance Criteria +- [ ] Scales up when CPU > 80% +[... 100 chars total ...] + +Metadata: +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +- Project: MYPROJECT (from **Project:** field) +- Type: Story (auto-detected from user story format) +- Component: Infrastructure (from **Component:** field) +- Version: 2.5 (from **Version:** field) + +Applied Universal Defaults: +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +- Labels: ["ai-generated-jira"] +- Security: Red Hat Employee (if supported by project, omitted otherwise) +- Content Format: Markdown +``` + +### User Options + +Present options to user: + +```text +What would you like to do? + +1. View full details for issue # +2. Edit issue # (change fields) +3. Skip issue # (don't create) +4. Create all issues (proceed to validation) +5. Cancel (abort) +``` + +**Interactive Flow:** +- If user selects "View details": Show full description and all metadata for selected issue +- If user selects "Edit": Prompt for which fields to change, update in memory +- If user selects "Skip": Mark issue to skip, remove from creation list +- If user selects "Create all": Proceed to Phase 4 (Validation) +- If user selects "Cancel": Abort skill execution + +--- + +## Phase 4: Validate + +**Load and follow:** [`../_shared/validation-common.md`](../_shared/validation-common.md) โ€” shared validation logic used across all Jira creation skills. + +Do not skip this phase. After validation completes (all checks pass, or user accepts partial success for non-security failures), proceed to Phase 5. + +--- + +## Phase 5: Execute + +This phase creates Jira issues in the correct order with proper defaults and error handling. + +### Execution Order + +Create issues in **hierarchy order** to ensure parent issues exist before children: + +1. **Level 3:** Outcomes (if any) +2. **Level 2:** Features, Initiatives (if any) +3. **Level 1:** Epics +4. **Level 0:** Stories, Tasks, Bugs +5. **Level -1:** Sub-tasks + +Within each level, maintain the document order from the markdown file. + +### Universal Defaults + +Apply to **ALL** issues regardless of project: + +```json +{ + "labels": ["ai-generated-jira"], + "contentFormat": "markdown" +} +``` + +**Security Level (conditional):** Applied per-issue during execution โ€” see the "Creating Issues via MCP" section. Security is checked per `issue.project` (not pre-loop) to avoid referencing `project_key` before it is defined. + +These fields are non-negotiable and set automatically without prompting. + +### Project-Specific Defaults (Optional) + +**Before creating issues**, attempt to invoke the `jira:jira-conventions` skill for each distinct project. The skill decides whether conventions apply โ€” this skill does not hard-code any project or component names: + +```python +# Discover conventions dynamically โ€” no hard-coded allowlists +for project_key in {issue.project for issue in issues}: + try: + conventions = invoke_skill("jira:jira-conventions", project=project_key) + if conventions: + # Apply returned defaults for all issues in this project + apply_conventions(issues, project_key, conventions) + # Layered ON TOP of universal defaults, never replacing them + except SkillNotFoundError: + pass # jira:jira-conventions not installed โ€” skip silently + except SkillNotApplicableError: + pass # Skill is installed but doesn't cover this project โ€” skip +``` + +**Do NOT hard-code project names or component names** in this skill. Convention support is discovered at runtime by delegating to `jira:jira-conventions`. + +**Project-specific defaults are layered ON TOP of universal defaults**, never replacing them. + +### Creating Issues via MCP + +For each issue in execution order: + +```python +# Assemble full description from all extracted sections +description_parts = [] +if issue.description: + description_parts.append(issue.description) +if issue.user_story: + description_parts.append(f"## User Story\n{issue.user_story}") +if issue.acceptance_criteria: + description_parts.append(f"## Acceptance Criteria\n{issue.acceptance_criteria}") +if issue.steps_to_reproduce: + description_parts.append(f"## Steps to Reproduce\n{issue.steps_to_reproduce}") +if issue.expected_behavior: + description_parts.append(f"## Expected Behavior\n{issue.expected_behavior}") +if issue.actual_behavior: + description_parts.append(f"## Actual Behavior\n{issue.actual_behavior}") +if issue.environment: + description_parts.append(f"## Environment\n{issue.environment}") +if issue.technical_notes: + description_parts.append(f"## Technical Notes\n{issue.technical_notes}") +if issue.testing_notes: + description_parts.append(f"## Testing Notes\n{issue.testing_notes}") +full_description = "\n\n".join(description_parts) + +# Prepare fields +fields = { + "project": {"key": issue.project}, + "issuetype": {"name": issue.type}, + "summary": issue.summary, + "description": full_description, + "labels": list(set(issue.labels or []) | {"ai-generated-jira"}), # Merge with user labels +} + +# Add security level if available for the project +security_levels = getJiraProjectIssueTypesMetadata(issue.project).get("securityLevels", []) +if any(level["name"] == "Red Hat Employee" for level in security_levels): + fields["security"] = {"name": "Red Hat Employee"} + +# Add optional fields +if issue.component: + fields["components"] = [{"name": issue.component}] + +if issue.priority: + fields["priority"] = {"name": issue.priority} + +# Resolve field IDs from issue-type metadata (project-agnostic โ€” do NOT hard-code customfield IDs) +type_meta = getJiraIssueTypeMetaWithFields(project=issue.project, issuetype=issue.type) +field_ids = resolve_field_ids(type_meta) # maps logical names โ†’ customfield_* when present +# resolve_field_ids looks up fields by name/schema, e.g.: +# "Epic Name" / epic name schema โ†’ epic_name_field +# "Target Version" / version picker custom fields โ†’ target_version_field +# Prefer conventions overrides when jira-conventions returned field IDs for this project. + +if issue.version: + version_id = find_version_id(type_meta, issue.version) + target_version_field = field_ids.get("target_version") + if version_id and target_version_field: + # Value format varies by project โ€” delegate to jira-conventions if installed, + # otherwise default to array form; plain string is used by some projects + # and must be set by the conventions layer. + version_value = apply_version_format(issue.project, version_id) # from conventions + fields[target_version_field] = version_value if version_value else [{"id": version_id}] + elif issue.version and not target_version_field: + warnings.append({ + "summary": issue.summary, + "message": f"Version '{issue.version}' requested but no Target Version field found for {issue.project}/{issue.type}; omitting" + }) + +# Parent: always set when present. Pre-existing keys were validated in Phase 4; +# in-batch parents are resolved earlier (see Parent Resolution below) into real Jira keys. +if issue.parent_key: + fields["parent"] = {"key": issue.parent_key} + +# Epic Name โ€” only when the project/type exposes that field +if issue.type == "Epic" and field_ids.get("epic_name"): + fields[field_ids["epic_name"]] = issue.summary + +# Create via MCP โ€” ordered handler chain: specific exceptions before generic fallback +try: + result = createJiraIssue( + project=issue.project, + issuetype=issue.type, + summary=issue.summary, + description=full_description, + additional_fields=fields, + contentFormat="markdown" + ) + created_jira_keys[issue.id] = result["key"] + jira_key_to_issue_id[result["key"]] = issue.id + successes.append({ + "key": result["key"], + "summary": issue.summary, + "url": result["url"] + }) + +except ParentLinkError as e: + # Create without parent using same named-parameter contract as primary call + extra = {k: v for k, v in fields.items() if k not in ("project", "issuetype", "summary", "description", "parent")} + result = createJiraIssue( + project=issue.project, + issuetype=issue.type, + summary=issue.summary, + description=full_description, + additional_fields=extra, + contentFormat="markdown" + ) + created_jira_keys[issue.id] = result["key"] + jira_key_to_issue_id[result["key"]] = issue.id + try: + editJiraIssue( + issue_key=result["key"], + update_fields={"parent": {"key": issue.parent_key}}, + contentFormat="markdown" + ) + except Exception as link_error: + warnings.append({ + "key": result["key"], + "message": f"Created but failed to link parent {issue.parent_key}: {link_error}" + }) + +except ComponentNotFoundError as e: + # Phase 4 should have caught this; if it still happens, retry without component + project_meta = getJiraProjectIssueTypesMetadata(project=issue.project) + available = [c["name"] for c in project_meta.get("components", [])] + fields.pop("components", None) + try: + result = createJiraIssue( + project=issue.project, + issuetype=issue.type, + summary=issue.summary, + description=full_description, + additional_fields={k: v for k, v in fields.items() if k not in ("project", "issuetype", "summary", "description")}, + contentFormat="markdown" + ) + created_jira_keys[issue.id] = result["key"] + jira_key_to_issue_id[result["key"]] = issue.id + successes.append({"key": result["key"], "summary": issue.summary, "url": result["url"]}) + warnings.append({ + "key": result["key"], + "message": f"Created without component '{issue.component}' (not found). Available: {', '.join(available[:5])}" + }) + except Exception as retry_error: + failed_batch_ids.add(issue.id) + failures.append({ + "summary": issue.summary, + "error": f"Component '{issue.component}' not found; retry without component also failed: {retry_error}", + "suggestion": f"Available: {', '.join(available[:5])}", + "project": issue.project, + "type": issue.type + }) + +except VersionNotFoundError as e: + # Phase 4 should have caught this; if it still happens, retry without version + available = [v["name"] for v in type_meta.get("versions", {}).get("allowedValues", [])] + if field_ids.get("target_version"): + fields.pop(field_ids["target_version"], None) + try: + result = createJiraIssue( + project=issue.project, + issuetype=issue.type, + summary=issue.summary, + description=full_description, + additional_fields={k: v for k, v in fields.items() if k not in ("project", "issuetype", "summary", "description")}, + contentFormat="markdown" + ) + created_jira_keys[issue.id] = result["key"] + jira_key_to_issue_id[result["key"]] = issue.id + successes.append({"key": result["key"], "summary": issue.summary, "url": result["url"]}) + warnings.append({ + "key": result["key"], + "message": f"Created without version '{issue.version}' (not found). Available: {', '.join(available[:5])}" + }) + except Exception as retry_error: + failed_batch_ids.add(issue.id) + failures.append({ + "summary": issue.summary, + "error": f"Version '{issue.version}' not found; retry without version also failed: {retry_error}", + "suggestion": f"Available: {', '.join(available[:5])}", + "project": issue.project, + "type": issue.type + }) + +except PermissionError as e: + failed_batch_ids.add(issue.id) # mark failed so children are skipped + failures.append({ + "summary": issue.summary, + "error": f"Permission denied: {str(e)}", + "suggestion": f"Check Jira permissions for project {issue.project}", + "fatal": True, + "project": issue.project, + "type": issue.type + }) + +except FieldValidationError as e: + failed_batch_ids.add(issue.id) # mark failed so children are skipped + failures.append({ + "summary": issue.summary, + "error": f"Field validation failed: {e.field} โ€” {e.message}", + "suggestion": "Check field format in markdown", + "project": issue.project, + "type": issue.type + }) + +except Exception as e: + # Generic fallback โ€” catches any unclassified MCP error + failed_batch_ids.add(issue.id) + failures.append({ + "summary": issue.summary, + "error": str(e), + "project": issue.project, + "type": issue.type + }) +``` + +### Parent Linking Notes + +`issue.parent_key` must be a real Jira issue key at create time: + +1. **Pre-existing parents** (e.g. `CNTRLPLANE-100` in markdown) โ€” validated in Phase 4; set `fields["parent"]` unconditionally when `parent_key` is present. +2. **In-batch parents** โ€” create parents first (hierarchy order). After a parent succeeds, if any child referenced that batch issue (by parse `id` or summary), rewrite the child's `parent_key` to the new Jira key from `created_jira_keys` before creating the child. +3. Use `created_jira_keys` / `jira_key_to_issue_id` / `failed_batch_ids` only for in-batch failure skipping โ€” **never** as a gate for whether to set `parent` on pre-existing keys. + +**Do not** require `parent_key in created_jira_keys` (or a nonexistent `created_issues` map) before linking โ€” that silently drops valid pre-existing parents. + +### Batch Mode Partial Failures + +Track successes and failures separately, including tracking failed parent issues: + +```python +results = { + "successes": [], # [{key, summary, url}] + "failures": [], # [{summary, error, suggestion, project, type}] + "warnings": [] # [{key, message}] +} + +failed_batch_ids = set() # issue.id values for batch issues that failed +created_jira_keys = {} # Maps issue.id -> result Jira key (e.g., "PROJ-789") +jira_key_to_issue_id = {} # Reverse map: result Jira key -> issue.id (for child lookup) + +# Continue execution even if some issues fail +for issue in sorted_issues: + # Skip if this issue's parent was a batch-created issue that failed. + # Pre-existing parents (Jira keys from outside this batch) cannot fail here. + parent_batch_id = jira_key_to_issue_id.get(issue.parent_key) + if parent_batch_id and parent_batch_id in failed_batch_ids: + results["failures"].append({ + "summary": issue.summary, + "error": f"Parent {issue.parent_key} failed to create", + "project": issue.project, + "type": issue.type + }) + failed_batch_ids.add(issue.id) + continue + + try: + result = createJiraIssue(...) + created_jira_keys[issue.id] = result["key"] + jira_key_to_issue_id[result["key"]] = issue.id + results["successes"].append({ + "key": result["key"], + "summary": issue.summary, + "url": result["url"] + }) + except Exception as e: + # Track this issue as failed โ€” use issue.id (result may not exist) + failed_batch_ids.add(issue.id) + results["failures"].append({ + "summary": issue.summary, + "error": str(e), + "project": issue.project, + "type": issue.type + }) + # CONTINUE to next issue + +# Report at end +return results +``` + +**Output format:** + +```text +Created 7 of 10 issues: + +โœ… SUCCESSES (7): + CNTRLPLANE-100: Enable autoscaling + CNTRLPLANE-101: Add metrics dashboard + ... + +โŒ FAILURES (3): + "API rate limiting" - Component 'Backend' not found in PLATFORM + Suggestion: Available components: Frontend, Infrastructure, CLI + + "Database optimization" - Version '4.25' not found + Suggestion: Available versions: openshift-4.21, openshift-4.22, openshift-4.23 + +โš ๏ธ WARNINGS (1): + CNTRLPLANE-100: Created but failed to link parent CNTRLPLANE-99 + +Next steps: +- Fix failed issues and retry +- Review warnings and manually link parents if needed +``` + +### Return to Command + +Pass structured results back to the calling command for user reporting: + +```python +return { + "successes": results["successes"], + "failures": results["failures"], + "warnings": results["warnings"], + "total_attempted": len(issues), + "total_created": len(results["successes"]) +} +``` + +--- + +## Phase 6: Summarize + +**Task Complexity:** Low (simple reporting) + +### Generate Results Report + +**For Single Issue Mode:** + +```text +โœ“ Created Jira Issue +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +Issue Key: MYPROJECT-456 +Title: Enable automatic scaling +URL: https://your-jira.atlassian.net/browse/MYPROJECT-456 + +Metadata Applied: +- Project: MYPROJECT +- Type: Story +- Component: Infrastructure +- Version: 2.5 + +Universal Defaults: +- Labels: ai-generated-jira +- Security: Red Hat Employee (applied โ€” project supports this security level) + (omitted if project does not support this security level) +``` + +**For Batch Mode (All Successful):** + +```text +โœ“ Created 3 Jira Issues +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +| Key | Type | Summary | URL | +|------------|-------|--------------------------|----------------------------------------| +| PROJ-A-789 | Story | Add user dashboard | https://jira.atlassian.net/browse/... | +| PROJ-B-101 | Bug | API returns 500 error | https://jira.atlassian.net/browse/... | +| PROJ-A-790 | Task | Update API docs | https://jira.atlassian.net/browse/... | + +Summary: +- 3 issues created successfully +- Projects: PROJ-A (2), PROJ-B (1) +- Types: Story (1), Bug (1), Task (1) +``` + +**For Batch Mode (Partial Failures):** + +```text +โš  Created 2/3 Jira Issues (1 failed) +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +Successful: + +| Key | Type | Summary | Status | +|------------|-------|--------------------------|-----------| +| PROJ-A-789 | Story | Add user dashboard | โœ“ Created | +| PROJ-A-790 | Task | Update API docs | โœ“ Created | + +Failed: + +| # | Type | Project | Summary | Error | +|---|------|---------|-----------------------|-------------------------| +| 2 | Bug | PROJ-B | API returns 500 error | Permission denied | + +Summary: +- 2 issues created successfully +- 1 issue failed (permission denied for PROJ-B) +- Successfully created: PROJ-A (2) +``` + +### Reminder + +```text +Reminder: If you granted write permissions during this session, +revoke them via /permissions (remove editJiraIssue allowance). +``` + +--- + +## Markdown Format Reference + +### Required Metadata Fields + +```markdown +**Project:** # REQUIRED - Jira project key +**Type:** # Recommended; auto-detected from content if omitted +``` + +Valid types: `Story`, `Bug`, `Task`, `Epic`, `Feature`, `Initiative`, `Sub-task` + +### Optional Metadata Fields + +```markdown +**Component:** +**Version:** +**Parent:** # e.g., PROJ-123 +**Priority:** # e.g., High, Critical +**Labels:** , # comma-separated +``` + +### Single Issue Example + +```markdown +# Enable autoscaling for clusters + +**Project:** PLATFORM +**Type:** Story +**Component:** Infrastructure +**Version:** 2.5 + +As a cluster admin, I want to configure autoscaling, so that I can handle traffic spikes. + +## Acceptance Criteria +- [ ] Node pools scale up when CPU > 80% +- [ ] Node pools scale down when CPU < 30% +- [ ] Scaling respects min/max limits + +## Context +Current state: Admins manually scale node pools. + +## Dependencies +- PLATFORM-100 โ€” Monitoring infrastructure must be deployed +``` + +### Batch Mode Example + +```markdown +# Sprint 42 Planning + +## Story: Add user dashboard +**Project:** FRONTEND +**Type:** Story +**Component:** Console +**Version:** 1.5 + +As a developer, I want a dashboard to monitor applications. + +### Acceptance Criteria +- [ ] Shows running pods +- [ ] Shows resource usage + +--- + +## Bug: API returns 500 error +**Project:** BACKEND +**Type:** Bug +**Component:** API Gateway +**Priority:** High + +Description: API crashes on special characters. + +Steps to Reproduce: +1. Create resource with special chars +2. Observe 500 error + +Expected: Should return 400 with validation error + +--- + +## Task: Update API documentation +**Project:** FRONTEND +**Type:** Task +**Parent:** FRONTEND-456 + +Update docs for new endpoints. + +### Definition of Done +- [ ] Swagger spec updated +- [ ] Examples added +``` + +--- + +## MCP Tools Used + +| Tool | Phase | Purpose | +|------|-------|---------| +| `createJiraIssue` | Execute | Create each Jira issue | +| `editJiraIssue` | Execute | Fallback for parent linking if create fails | +| `getJiraIssue` | Validate | Verify parent exists and hierarchy level matches | +| `getJiraIssueTypeMetaWithFields` | Validate/Execute | Fetch components, versions, custom fields for project | +| `getJiraProjectIssueTypesMetadata` | Validate | Discover available issue types and hierarchy levels | + +--- + +## Edge Cases and Handling + +### 1. Missing Project Metadata + +**Scenario:** Markdown has no `**Project:**` field + +**Handling:** +```markdown +1. Prompt user: "Which project should this issue be created in?" +2. Wait for user input +3. Use provided project key +4. Do NOT assume or default to any project +``` + +### 2. Missing Type Metadata + +**Scenario:** Markdown has no `**Type:**` field and auto-detection fails + +**Handling:** +```markdown +1. Attempt auto-detection from content patterns (see Phase 2) +2. If auto-detection is ambiguous or fails: + - Prompt user: "What type of issue? (Story, Bug, Task, Epic, Feature, Initiative, Sub-task)" + - Wait for user input +3. Do NOT default to any type +``` + +### 3. Component Not Valid for Project + +**Scenario:** User specifies `**Component:** XYZ` but XYZ doesn't exist in project + +**Handling:** +```markdown +1. createJiraIssue fails with "Component not found" error +2. Fetch available components via getJiraIssueTypeMetaWithFields +3. Show user: "Component 'XYZ' not found in PROJECT. Available components: [list]" +4. Ask: "Select a component from the list, or proceed without component?" +5. Retry creation with selected component or omit component field +``` + +### 4. Version Format Mismatch + +**Scenario:** User provides version string "2.5", but project requires version ID + +**Handling:** +```markdown +1. createJiraIssue fails with "Version not found" error +2. Fetch available versions via getJiraIssueTypeMetaWithFields +3. Attempt fuzzy match (e.g., "2.5" โ†’ find version with name containing "2.5") +4. If match found, use version ID +5. If no match, show available versions to user and ask which to use +6. Retry creation with correct version format +``` + +### 5. Invalid Parent Hierarchy + +**Scenario:** User specifies Story with Feature or Initiative as parent (should be Epic) + +**Handling:** +```markdown +1. In Phase 4 validation, fetch parent via getJiraIssue +2. Extract parent's issuetype.hierarchyLevel (Feature/Initiative = level 2) +3. Extract child's hierarchyLevel (Story = level 0) +4. Detect mismatch: Story (level 0) needs Epic (level 1) parent, not Feature/Initiative (level 2) +5. Warn user: "Story cannot have Feature or Initiative as parent. Expected Epic (level 1)." +6. Offer options: + a) Proceed without parent + b) Provide different parent key + c) Cancel creation +7. User decides; proceed accordingly +``` + +### 6. Unstructured Markdown + +**Scenario:** File is prose with no sections, no metadata + +**Handling:** +```markdown +1. No **Project:** found โ†’ prompt user: "Which project?" +2. No **Type:** found, no auto-detection match โ†’ prompt user: "What type?" +3. Summary extraction uses Phase 2 fallback order: first H1 โ†’ first sentence โ†’ filename without extension โ†’ prompt user +4. Entire file content becomes description +5. Proceed with interactive prompts for all missing required fields +``` + +### 7. Mixed Projects in Batch Mode + +**Scenario:** File has issues for PROJ-A, PROJ-B, PROJ-C + +**Handling:** +```markdown +1. Parse all issues independently +2. Apply universal defaults to all (labels: ai-generated-jira, content format: markdown) +3. Apply security level per project if supported (check each project independently) +4. Optionally invoke jira-conventions for each distinct project +5. Group by project in review phase: "PROJ-A (2 issues), PROJ-B (1), PROJ-C (1)" +6. Create each issue with its own project's metadata +7. Report results grouped by project +``` + +--- + +## Integration with Existing Skills (Optional) + +### jira-conventions Skill + +**Purpose:** Apply project-specific defaults and transformations + +**How to check if available:** +```markdown +Attempt to invoke: "Load and apply jira-conventions for project {PROJECT_KEY}" +If skill not found or invocation fails, skip this step +``` + +**What to extract (if available):** +- Custom field IDs and default values +- Component requirements +- Version format (string vs array vs custom field) +- Additional project-specific labels +- Template validation rules + +**What NOT to do:** +- Do NOT require this skill to exist +- Do NOT hard-code project names +- If skill unavailable, use only universal defaults + +### Type-Specific Templates + +**Purpose:** Validate content structure + +**Templates (if they exist):** +- Story โ†’ `../../reference/create-story.md` +- Bug โ†’ `../../reference/create-bug.md` +- Epic โ†’ `../../reference/create-epic.md` +- Task โ†’ `../../reference/create-task.md` +- Feature โ†’ `../../reference/create-feature.md` +- Initiative โ†’ `../../reference/create-initiative.md` + +There is no dedicated Sub-task reference file; use Task guidance plus parent-link rules from Phase 4/5. + +**What to extract:** +- Expected section names +- Description format recommendations +- Validation rules (e.g., "Stories should have Acceptance Criteria") + +**What NOT to do:** +- Do NOT require templates to exist +- Do NOT enforce strict compliance (warn only) + +### Markdown Formatting Guide + +**Reference:** `../../reference/markdown-for-jira.md` (if exists) + +**Key points:** +- Use `contentFormat: "markdown"` for all MCP calls +- Checkboxes `- [ ]` render as actionable checkboxes in Jira +- Code blocks render as Jira code blocks +- Issue keys auto-link + +--- + +## Best Practices + +1. **Be explicit with metadata:** Always include `**Project:**` and `**Type:**` in markdown +2. **Use descriptive summaries:** Avoid vague titles like "Fix issue" or "Update thing" +3. **Sanitize sensitive data:** Never include credentials, API keys, or secrets +4. **Structure content:** Use H2/H3 headings for sections (Acceptance Criteria, Context, etc.) +5. **Batch mode separator:** Use `---` between issues if not using type-prefixed H2s +6. **Parent linking:** Verify parent exists and is correct type before specifying +7. **Component names:** Check project's components before including `**Component:**` + +--- + +## Anti-Patterns to Avoid + +โŒ **Don't put user story in summary:** +```markdown +# As a developer, I want to add a dashboard so that I can monitor apps +``` +โœ… **Do this instead:** +```markdown +# Add developer dashboard + +As a developer, I want a dashboard to monitor applications, so that... +``` + +โŒ **Don't include secrets:** +```markdown +API Key: AKIA... (redacted AWS key) +``` +โœ… **Use placeholders:** +```markdown +API Key: YOUR_API_KEY +``` + +โŒ **Don't omit required metadata:** +```markdown +# Add feature + +This is a story about adding a feature. +``` +โœ… **Include Project and Type:** +```markdown +# Add feature + +**Project:** MYPROJECT +**Type:** Story + +As a user, I want... +``` + +--- + +**Last Updated:** 2026-07-23 diff --git a/plugins/jira/skills/create/SKILL.md b/plugins/jira/skills/create/SKILL.md index bf9dce4b9..4e9baee51 100644 --- a/plugins/jira/skills/create/SKILL.md +++ b/plugins/jira/skills/create/SKILL.md @@ -63,39 +63,29 @@ Project and team defaults (version, component, labels) come from the `jira-conve Follow the type-specific reference file's interactive workflow to collect missing information (story format, bug template sections, epic scope, etc.). -### Phase 5: Summary Validation +### Phase 5: Validation -Check for anti-patterns before creation: +**Load and follow:** [`../_shared/validation-common.md`](../_shared/validation-common.md) for: +- Security credential scanning (CRITICAL โ€” blocks creation if credentials detected) +- Summary anti-pattern detection (user story in summary, excessive length) +- Required field validation (project, type) +- Parent hierarchy validation (if `--parent` provided) +- Component/version validation (if provided) -1. Summary starts with "As a" or contains "I want" / "so that" โ†’ belongs in description -2. Summary exceeds 100 characters โ†’ likely too long +Follow the two-pass validation approach from validation-common.md: non-interactive API checks first (Pass 1), then all interactive remediation in a single prompt session (Pass 2). For single-issue creation, Pass 2 reduces to inline prompting since there is only one issue to present. -If detected: -```plaintext -The summary looks like a full user story. Summaries should be concise titles. - -Current: "As a cluster admin, I want to configure ImageTagMirrorSet in HostedCluster CRs so that I can enable tag-based image proxying" - -Suggested: "Enable ImageTagMirrorSet configuration in HostedCluster CRs" - -Use the suggested summary? (yes/no/edit) -``` - -### Phase 6: Security Validation - -Scan all content (summary, description) for sensitive data: - -- Credentials, API tokens, cloud keys (AWS, GCP, Azure) -- Kubeconfigs, SSH keys, certificates, PEM files -- URLs with embedded credentials +### Phase 6: Execution -If detected: STOP creation, inform user of the type found (without echoing it), suggest placeholder values. +**Reference patterns from:** [`../_shared/execution-common.md`](../_shared/execution-common.md) for: +- Universal defaults (labels, security level, contentFormat) +- Custom field ID resolution +- MCP error handling (ParentLinkError fallback, ComponentNotFoundError retry, etc.) -### Phase 7: Create Issue via MCP +**Create the issue:** Use `createJiraIssue` with collected parameters. Include universal fields and any project/team-specific fields. -### Phase 8: Return Result +### Phase 7: Return Result ```plaintext Created: PROJECT-1234