Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:56:16 +08:00
commit 19b5d00a1f
23 changed files with 6153 additions and 0 deletions

View File

@@ -0,0 +1,906 @@
---
name: ado-resource-validator
description: Validates Azure DevOps projects and resources exist, creates missing resources automatically. Smart enough to prompt user to select existing or create new projects. Supports multiple projects for project-per-team strategy, area paths for area-path-based strategy, and teams for team-based strategy. NEW - Per-project configuration support - AZURE_DEVOPS_AREA_PATHS_{ProjectName} and AZURE_DEVOPS_TEAMS_{ProjectName} for hierarchical organization. Activates for ado setup, ado validation, ado configuration, missing ado project, azure devops .env setup, per-project area paths, per-project teams.
allowed-tools: Read, Bash, Write, Edit
---
# Azure DevOps Resource Validator Skill
**Purpose**: Validate and auto-create Azure DevOps projects and resources, ensuring .env configuration is correct.
**Auto-Activation**: Triggers when Azure DevOps setup or validation is needed.
## What This Skill Does
This skill ensures your Azure DevOps configuration in `.env` is valid and all resources exist. It's **smart enough** to:
1. **Validate Azure DevOps projects** - Check if projects exist (multiple for project-per-team)
2. **Prompt for action** - Select existing project or create new one
3. **Validate area paths** - Check if area paths exist (for area-path-based strategy)
4. **Create missing area paths** - Auto-create area paths if missing
5. **Validate teams** - Check if teams exist (for team-based strategy)
6. **Update .env with correct values** - Ensure configuration is valid
## When This Skill Activates
**Automatically activates when**:
- You set up Azure DevOps integration for the first time
- You run `/specweave-ado:sync` and resources are missing
- Your `.env` has invalid Azure DevOps configuration
- You mention "ado setup" or "azure devops validation"
## Azure DevOps Configuration Structure
### Required .env Variables
```bash
AZURE_DEVOPS_PAT=your_token_here
AZURE_DEVOPS_ORG=yourorganization
AZURE_DEVOPS_STRATEGY=project-per-team # or area-path-based, team-based
```
### Strategy-Specific Variables
**Strategy 1: Project-per-team** (Multiple Projects)
```bash
AZURE_DEVOPS_STRATEGY=project-per-team
AZURE_DEVOPS_PROJECTS=WebApp,MobileApp,Platform
```
→ Validates that WebApp, MobileApp, and Platform projects exist
**Strategy 2: Area-path-based** (One Project, Multiple Area Paths)
```bash
AZURE_DEVOPS_STRATEGY=area-path-based
AZURE_DEVOPS_PROJECT=MainProduct
AZURE_DEVOPS_AREA_PATHS=Frontend,Backend,Mobile
```
→ Validates MainProduct project exists
→ Creates area paths if missing: MainProduct\Frontend, MainProduct\Backend, MainProduct\Mobile
**Strategy 3: Team-based** (One Project, Multiple Teams)
```bash
AZURE_DEVOPS_STRATEGY=team-based
AZURE_DEVOPS_PROJECT=MainProduct
AZURE_DEVOPS_TEAMS=Alpha Team,Beta Team,Gamma Team
```
→ Validates MainProduct project exists
→ Creates teams if missing: Alpha Team, Beta Team, Gamma Team
**NEW: Per-Project Configuration** (Advanced - Multiple Projects × Resources)
```bash
# Multiple projects with their own area paths and teams
AZURE_DEVOPS_STRATEGY=project-per-team
AZURE_DEVOPS_PROJECTS=Backend,Frontend,Mobile
# Per-project area paths (hierarchical naming)
AZURE_DEVOPS_AREA_PATHS_Backend=API,Database,Cache
AZURE_DEVOPS_AREA_PATHS_Frontend=Web,Admin,Public
AZURE_DEVOPS_AREA_PATHS_Mobile=iOS,Android,Shared
# Per-project teams (optional)
AZURE_DEVOPS_TEAMS_Backend=Alpha,Beta
AZURE_DEVOPS_TEAMS_Frontend=Gamma
```
→ Validates 3 projects exist: Backend, Frontend, Mobile
→ Creates area paths per project:
- Backend\API, Backend\Database, Backend\Cache
- Frontend\Web, Frontend\Admin, Frontend\Public
- Mobile\iOS, Mobile\Android, Mobile\Shared
→ Creates teams per project:
- Backend: Alpha, Beta
- Frontend: Gamma
**Naming Convention**: `{PROVIDER}_{RESOURCE_TYPE}_{PROJECT_NAME}`
## Validation Flow
### Step 1: Strategy Detection
**Read .env and detect strategy**:
```bash
AZURE_DEVOPS_STRATEGY=project-per-team
```
**Result**:
```
🔍 Detected strategy: Project-per-team
Projects to validate: WebApp, MobileApp, Platform
```
### Step 2: Project Validation (Project-per-team)
**Check if projects exist**:
```bash
# API calls to Azure DevOps
GET https://dev.azure.com/{org}/_apis/projects/WebApp
GET https://dev.azure.com/{org}/_apis/projects/MobileApp
GET https://dev.azure.com/{org}/_apis/projects/Platform
```
**If all projects exist**:
```
✅ All projects validated:
• WebApp (ID: abcd1234)
• MobileApp (ID: efgh5678)
• Platform (ID: ijkl9012)
```
**If some projects don't exist**:
```
⚠️ Projects not found:
✅ WebApp (exists)
❌ MobileApp (not found)
❌ Platform (not found)
What would you like to do?
1. Create missing projects
2. Select existing projects
3. Fix project names manually
4. Cancel
Your choice [1]:
```
**Option 1: Create Missing Projects**:
```
📦 Creating Azure DevOps projects...
Creating project: MobileApp...
✅ Project created: MobileApp (ID: mnop3456)
Creating project: Platform...
✅ Project created: Platform (ID: qrst7890)
✅ All projects now exist!
```
**Option 2: Select Existing Projects**:
```
Available projects in organization:
1. WebApp
2. ApiGateway
3. AuthService
4. NotificationService
5. DataPipeline
Select projects (comma-separated numbers) [2,3]:
✅ Updated .env: AZURE_DEVOPS_PROJECTS=WebApp,ApiGateway,AuthService
```
### Step 3: Area Path Validation (Area-path-based)
**Scenario**: One project with area paths
```bash
AZURE_DEVOPS_STRATEGY=area-path-based
AZURE_DEVOPS_PROJECT=MainProduct
AZURE_DEVOPS_AREA_PATHS=Frontend,Backend,Mobile,QA
```
**Validation**:
```
Checking project: MainProduct...
✅ Project "MainProduct" exists
Checking area paths...
✅ MainProduct\Frontend (exists)
✅ MainProduct\Backend (exists)
⚠️ MainProduct\Mobile (not found)
⚠️ MainProduct\QA (not found)
📦 Creating missing area paths...
✅ Created: MainProduct\Mobile
✅ Created: MainProduct\QA
✅ All area paths validated/created successfully
```
### Step 4: Team Validation (Team-based)
**Scenario**: One project with multiple teams
```bash
AZURE_DEVOPS_STRATEGY=team-based
AZURE_DEVOPS_PROJECT=MainProduct
AZURE_DEVOPS_TEAMS=Alpha Team,Beta Team,Gamma Team
```
**Validation**:
```
Checking project: MainProduct...
✅ Project "MainProduct" exists
Checking teams...
✅ Alpha Team (exists)
⚠️ Beta Team (not found)
⚠️ Gamma Team (not found)
📦 Creating missing teams...
✅ Created: Beta Team
✅ Created: Gamma Team
✅ All teams validated/created successfully
```
## Usage Examples
### Example 1: Fresh Azure DevOps Setup (Project-per-team)
**Scenario**: New setup with multiple projects for different teams
**Action**: Run `/specweave-ado:sync`
**What Happens**:
```bash
🔍 Validating Azure DevOps configuration...
Strategy: Project-per-team
Checking projects: WebApp, MobileApp, Platform...
⚠️ Projects not found:
• WebApp
• MobileApp
• Platform
What would you like to do?
1. Create new projects
2. Select existing projects
3. Cancel
Your choice [1]: 1
📦 Creating Azure DevOps projects...
Creating project: WebApp
Description: Web application frontend
Process template: Agile
✅ Created: WebApp (ID: proj-001)
Creating project: MobileApp
Description: Mobile application
Process template: Agile
✅ Created: MobileApp (ID: proj-002)
Creating project: Platform
Description: Backend platform services
Process template: Agile
✅ Created: Platform (ID: proj-003)
🎉 Azure DevOps configuration complete! All resources ready.
```
### Example 2: Migrate from Single to Multi-Project
**Scenario**: Currently using single project, want to split into multiple
**Current .env**:
```bash
AZURE_DEVOPS_PROJECT=MainProduct
```
**New .env**:
```bash
AZURE_DEVOPS_STRATEGY=project-per-team
AZURE_DEVOPS_PROJECTS=MainProduct-Frontend,MainProduct-Backend,MainProduct-Mobile
```
**What Happens**:
```bash
🔍 Detected strategy change: team-based → project-per-team
Validating new projects...
✅ MainProduct-Frontend (exists from previous split)
⚠️ MainProduct-Backend (not found)
⚠️ MainProduct-Mobile (not found)
Would you like to:
1. Create missing projects
2. Keep single project with area paths instead
3. Cancel
Your choice [1]: 1
📦 Creating projects...
✅ Created: MainProduct-Backend
✅ Created: MainProduct-Mobile
💡 Tip: You can now organize specs by project:
.specweave/docs/internal/specs/MainProduct-Frontend/
.specweave/docs/internal/specs/MainProduct-Backend/
.specweave/docs/internal/specs/MainProduct-Mobile/
```
### Example 3: Area Path Setup
**Scenario**: Large monolithic project with area-based organization
**Action**: Setup area paths for team organization
**What Happens**:
```bash
🔍 Validating Azure DevOps configuration...
Strategy: Area-path-based
Project: EnterpriseApp
Area Paths: Core, UserManagement, Billing, Reports, Analytics
Checking project: EnterpriseApp...
✅ Project exists
Checking area paths...
✅ EnterpriseApp\Core
✅ EnterpriseApp\UserManagement
⚠️ EnterpriseApp\Billing (not found)
⚠️ EnterpriseApp\Reports (not found)
⚠️ EnterpriseApp\Analytics (not found)
📦 Creating area paths...
Creating: EnterpriseApp\Billing
✅ Area path created with default team
Creating: EnterpriseApp\Reports
✅ Area path created with default team
Creating: EnterpriseApp\Analytics
✅ Area path created with default team
✅ All area paths ready!
Work items will be organized by area:
• Billing features → EnterpriseApp\Billing
• Report features → EnterpriseApp\Reports
• Analytics features → EnterpriseApp\Analytics
```
## Implementation Details
**Location**: `src/utils/external-resource-validator.ts`
**Core Classes**:
```typescript
// Main validator class
export class AzureDevOpsResourceValidator {
private pat: string;
private organization: string;
private envPath: string;
constructor(envPath: string = '.env') {
this.envPath = envPath;
const env = this.loadEnv();
this.pat = env.AZURE_DEVOPS_PAT || '';
this.organization = env.AZURE_DEVOPS_ORG || '';
}
// Main validation entry point
async validate(): Promise<AzureDevOpsValidationResult> {
const env = this.loadEnv();
const strategy = env.AZURE_DEVOPS_STRATEGY || 'project-per-team';
// Validate based on strategy
if (strategy === 'project-per-team') {
return this.validateMultipleProjects(projectNames);
} else if (strategy === 'area-path-based') {
return this.validateAreaPaths(projectName, areaPaths);
} else if (strategy === 'team-based') {
return this.validateTeams(projectName, teams);
}
}
}
// Public API function
export async function validateAzureDevOpsResources(
envPath: string = '.env'
): Promise<AzureDevOpsValidationResult> {
const validator = new AzureDevOpsResourceValidator(envPath);
return validator.validate();
}
```
**Key Implementation Features**:
1. **Async Project Creation** (ADO-specific):
```typescript
// ADO creates projects asynchronously - need to poll for completion
async createProject(name: string): Promise<AzureDevOpsProject> {
const result = await this.callAzureDevOpsApi('projects?api-version=7.0', 'POST', body);
// Wait for project to be fully created (ADO async behavior)
await this.waitForProjectCreation(result.id);
return { id: result.id, name, description };
}
// Poll until project is in 'wellFormed' state
private async waitForProjectCreation(projectId: string): Promise<void> {
const maxAttempts = 30; // 30 seconds max wait
for (let i = 0; i < maxAttempts; i++) {
const project = await this.getProject(projectId);
if (project.state === 'wellFormed') {
return; // Project is ready!
}
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second
}
throw new Error('Project creation timeout');
}
```
2. **Interactive Prompts** (when resources missing):
```typescript
const { action } = await inquirer.prompt([
{
type: 'select',
name: 'action',
message: `Project "${projectName}" not found. What would you like to do?`,
choices: [
{ name: 'Create new project', value: 'create' },
{ name: 'Select existing project', value: 'select' },
{ name: 'Skip this project', value: 'skip' },
{ name: 'Cancel', value: 'cancel' }
]
}
]);
```
3. **Automatic .env Updates**:
```typescript
// After creating projects, update .env
updateEnv(key: string, value: string): void {
const envContent = fs.readFileSync(this.envPath, 'utf-8');
const updated = envContent.replace(
new RegExp(`^${key}=.*$`, 'm'),
`${key}=${value}`
);
fs.writeFileSync(this.envPath, updated);
}
```
## CLI Command
**Automatic validation** (during setup):
```bash
# Runs automatically during specweave init
npx specweave init
# Also runs automatically before sync
/specweave-ado:sync 0014
```
**Manual validation**:
```bash
# Via skill activation
"Can you validate my Azure DevOps configuration?"
# Via TypeScript directly
npx tsx -e "import { validateAzureDevOpsResources } from './dist/utils/external-resource-validator.js'; await validateAzureDevOpsResources();"
# Via CLI (future command - planned)
specweave validate-ado
```
**Validation output**:
```typescript
interface AzureDevOpsValidationResult {
valid: boolean;
strategy: 'project-per-team' | 'area-path-based' | 'team-based';
projects: Array<{
name: string;
id: string;
exists: boolean;
}>;
created: string[]; // Names of newly created resources
envUpdated: boolean; // Whether .env was modified
}
// Example output:
{
valid: true,
strategy: 'project-per-team',
projects: [
{ name: 'WebApp', id: 'proj-001', exists: true },
{ name: 'MobileApp', id: 'proj-002', exists: true, created: true },
{ name: 'Platform', id: 'proj-003', exists: true, created: true }
],
created: ['MobileApp', 'Platform'],
envUpdated: false
}
```
## Smart Project Detection
### Auto-detect Based on Work Item Patterns
The skill can intelligently suggest project organization based on your existing work items:
```typescript
// Analyze existing work items
const workItems = await analyzeWorkItems(org, project);
// Detect patterns
const patterns = {
byArea: workItems.groupBy('areaPath'), // Area-based organization
byTeam: workItems.groupBy('assignedTeam'), // Team-based organization
byType: workItems.groupBy('workItemType') // Type-based organization
};
// Suggest strategy
if (patterns.byArea.length > 3) {
console.log('💡 Detected area-based organization');
console.log(' Suggested strategy: area-path-based');
} else if (patterns.byTeam.length > 2) {
console.log('💡 Detected team-based organization');
console.log(' Suggested strategy: team-based or project-per-team');
}
```
## Project Creation API
**Azure DevOps REST API** (v7.0):
### Create Project
```bash
POST https://dev.azure.com/{org}/_apis/projects?api-version=7.0
Content-Type: application/json
Authorization: Basic {base64(":PAT")}
{
"name": "MobileApp",
"description": "Mobile application project",
"capabilities": {
"versioncontrol": {
"sourceControlType": "Git"
},
"processTemplate": {
"templateTypeId": "adcc42ab-9882-485e-a3ed-7678f01f66bc" # Agile
}
}
}
Response:
{
"id": "proj-002",
"name": "MobileApp",
"state": "wellFormed"
}
```
### Create Area Path
```bash
POST https://dev.azure.com/{org}/{project}/_apis/wit/classificationnodes/areas?api-version=7.0
Content-Type: application/json
{
"name": "Frontend",
"attributes": {
"startDate": null,
"finishDate": null
}
}
Response:
{
"id": 123,
"name": "Frontend",
"path": "\\MainProduct\\Area\\Frontend"
}
```
### Create Team
```bash
POST https://dev.azure.com/{org}/_apis/projects/{projectId}/teams?api-version=7.0
Content-Type: application/json
{
"name": "Alpha Team",
"description": "Alpha development team"
}
Response:
{
"id": "team-001",
"name": "Alpha Team",
"projectName": "MainProduct"
}
```
## Configuration Examples
### Example 1: Microservices Architecture (Project-per-team)
**Before** (`.env`):
```bash
AZURE_DEVOPS_ORG=mycompany
AZURE_DEVOPS_PAT=xxx
```
**After validation**:
```bash
AZURE_DEVOPS_ORG=mycompany
AZURE_DEVOPS_PAT=xxx
AZURE_DEVOPS_STRATEGY=project-per-team
AZURE_DEVOPS_PROJECTS=AuthService,UserService,PaymentService,NotificationService
```
**Folder structure created**:
```
.specweave/docs/internal/specs/
├── AuthService/
│ └── spec-001-oauth-implementation.md
├── UserService/
│ └── spec-001-user-management.md
├── PaymentService/
│ └── spec-001-stripe-integration.md
└── NotificationService/
└── spec-001-email-notifications.md
```
### Example 2: Monolithic Application (Area-path-based)
**Before** (`.env`):
```bash
AZURE_DEVOPS_PROJECT=ERP
```
**After validation**:
```bash
AZURE_DEVOPS_ORG=enterprise
AZURE_DEVOPS_PAT=xxx
AZURE_DEVOPS_STRATEGY=area-path-based
AZURE_DEVOPS_PROJECT=ERP
AZURE_DEVOPS_AREA_PATHS=Finance,HR,Inventory,Sales,Reports
```
**Work item organization**:
```
ERP
├── Finance/ → Finance module features
├── HR/ → HR module features
├── Inventory/ → Inventory management
├── Sales/ → Sales module features
└── Reports/ → Reporting features
```
### Example 3: Platform Teams (Team-based)
**Before** (`.env`):
```bash
AZURE_DEVOPS_PROJECT=Platform
```
**After validation**:
```bash
AZURE_DEVOPS_ORG=techcorp
AZURE_DEVOPS_PAT=xxx
AZURE_DEVOPS_STRATEGY=team-based
AZURE_DEVOPS_PROJECT=Platform
AZURE_DEVOPS_TEAMS=Infrastructure,Security,Data,DevOps
```
**Team assignments**:
- Infrastructure Team → Cloud resources, networking
- Security Team → Auth, compliance, auditing
- Data Team → Databases, analytics, ML
- DevOps Team → CI/CD, monitoring, tooling
## Error Handling
### Error 1: Invalid Credentials
**Symptom**: API calls fail with 401 Unauthorized
**Solution**:
```
❌ Azure DevOps API authentication failed
Please check:
1. AZURE_DEVOPS_PAT is correct
2. Token has not expired
3. AZURE_DEVOPS_ORG is correct
Generate new token at:
https://dev.azure.com/{org}/_usersSettings/tokens
```
### Error 2: Insufficient Permissions
**Symptom**: Cannot create projects (403 Forbidden)
**Solution**:
```
❌ Insufficient permissions to create projects
You need:
- Project Collection Administrator role (for creating projects)
- Project Administrator role (for area paths and teams)
Contact your Azure DevOps administrator to request permissions.
```
### Error 3: Project Name Conflicts
**Symptom**: Project creation fails (name exists)
**Solution**:
```
❌ Project name "WebApp" already exists
Options:
1. Use a different project name
2. Select the existing project
3. Add a suffix (e.g., WebApp-v2)
Your choice [2]:
```
### Error 4: Organization Limits
**Symptom**: Cannot create more projects
**Solution**:
```
❌ Organization project limit reached (250 projects)
Consider:
1. Using area-path-based strategy (one project)
2. Archiving old projects
3. Upgrading organization plan
Contact Azure DevOps support for limit increases.
```
## Integration with SpecWeave Workflow
### Automatic Validation
When using `/specweave-ado:sync`, validation runs automatically:
```bash
/specweave-ado:sync 0014
# Internally calls:
1. validateAzureDevOpsResources()
2. Fix missing projects/area paths/teams
3. Create folder structure for specs
4. Proceed with sync
```
### Manual Validation
Run validation independently:
```bash
# Via skill
"Validate my Azure DevOps configuration"
# Via TypeScript
npx tsx src/utils/external-resource-validator.ts --provider=ado
# Via CLI (future)
specweave validate-ado
```
## Best Practices
✅ **Choose the right strategy**:
- **Project-per-team**: Best for autonomous teams, microservices
- **Area-path-based**: Best for monolithic apps, shared codebase
- **Team-based**: Best for small organizations, simple structure
✅ **Use descriptive names**:
```bash
# Good
AZURE_DEVOPS_PROJECTS=UserManagement,PaymentProcessing,NotificationEngine
# Bad
AZURE_DEVOPS_PROJECTS=Proj1,Proj2,Proj3
```
✅ **Document project mapping** (in README):
```markdown
## Azure DevOps Projects
- UserManagement: User authentication and profile management
- PaymentProcessing: Payment gateway integrations
- NotificationEngine: Email, SMS, and push notifications
```
✅ **Keep .env in version control** (gitignored tokens):
```bash
# Commit project structure
AZURE_DEVOPS_STRATEGY=project-per-team
AZURE_DEVOPS_PROJECTS=WebApp,MobileApp,Platform
# Don't commit sensitive data
AZURE_DEVOPS_PAT=<redacted>
```
## Folder Organization
Based on strategy, the skill creates appropriate folder structure:
### Project-per-team Structure
```
.specweave/docs/internal/specs/
├── WebApp/
│ ├── spec-001-user-interface.md
│ └── spec-002-responsive-design.md
├── MobileApp/
│ ├── spec-001-ios-features.md
│ └── spec-002-android-features.md
└── Platform/
├── spec-001-api-design.md
└── spec-002-database-schema.md
```
### Area-path-based Structure
```
.specweave/docs/internal/specs/MainProduct/
├── Frontend/
│ └── spec-001-ui-components.md
├── Backend/
│ └── spec-001-api-endpoints.md
└── Mobile/
└── spec-001-mobile-sync.md
```
### Team-based Structure
```
.specweave/docs/internal/specs/MainProduct/
├── AlphaTeam/
│ └── spec-001-feature-a.md
├── BetaTeam/
│ └── spec-001-feature-b.md
└── GammaTeam/
└── spec-001-feature-c.md
```
## Key Differences from JIRA
**Azure DevOps vs JIRA Resource Creation**:
| Aspect | Azure DevOps | JIRA |
|--------|-------------|------|
| **Project Creation** | Asynchronous (polling required) | Synchronous (immediate) |
| **Creation Time** | 5-30 seconds | <1 second |
| **Status Tracking** | Poll `state` field ('wellFormed') | No polling needed |
| **API Complexity** | Higher (async handling) | Lower (sync operations) |
| **Board Creation** | Auto-created with project | Requires separate API call |
| **Process Templates** | Required (Agile, Scrum, CMMI) | Not applicable |
**Why Async Matters**:
When you create an ADO project, the API returns immediately with `state: 'new'`, but the project isn't usable yet. The validator polls every 1 second (max 30 attempts) until `state: 'wellFormed'`:
```typescript
// Create project (returns immediately)
const project = await createProject('MobileApp'); // state: 'new'
// Poll until ready
await waitForProjectCreation(project.id); // Polls until state: 'wellFormed'
// Now safe to use!
console.log('✅ Project ready for work items');
```
**Impact on UX**:
- JIRA: "✅ Project created" (instant)
- ADO: "📦 Creating project... ⏳ Waiting for Azure DevOps to complete setup... ✅ Project ready!" (5-30s)
## Summary
This skill ensures your Azure DevOps configuration is **always valid** by:
1.**Validating projects** - Check if projects exist, prompt to select or create
2.**Supporting multiple strategies** - Project-per-team, area-path-based, team-based
3.**Auto-creating resources** - Projects, area paths, teams (with async handling)
4.**Organizing specs** - Create folder structure based on projects
5.**Clear error messages** - Actionable guidance for all failures
6.**Handles ADO async behavior** - Polls for project creation completion
**Result**: Zero manual Azure DevOps setup - system handles everything, including ADO's async project creation!
---
**Skill Version**: 1.1.0
**Introduced**: SpecWeave v0.17.0
**Last Updated**: 2025-11-11
**Key Changes v1.1.0**: Added implementation details, async project creation handling, JIRA comparison