Learn-skills.dev jira-project-management
Administer Jira projects. Use when creating/archiving projects, managing components, versions, roles, permissions, or project configuration.
install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/01000001-01001110/agent-jira-skills/jira-project-management" ~/.claude/skills/neversight-learn-skills-dev-jira-project-management && rm -rf "$T"
manifest:
data/skills-md/01000001-01001110/agent-jira-skills/jira-project-management/SKILL.mdsource content
Jira Project Management Skill
Purpose
Comprehensive project administration including CRUD operations, components, versions, roles, permissions, and configuration.
When to Use
- Creating/updating/deleting/archiving projects
- Managing project components (modules, teams)
- Managing versions/releases
- Configuring project roles and permissions
- Setting project properties and metadata
- Validating project keys and names
Prerequisites
- Authenticated JiraClient (see jira-auth skill)
- Jira admin or project admin permissions
- Project key format: 2-10 uppercase letters
Implementation Pattern
Step 1: Define Types
interface Project { id: string; key: string; name: string; self: string; projectTypeKey: 'software' | 'service_desk' | 'business'; simplified: boolean; style: 'classic' | 'next-gen'; isPrivate: boolean; lead: { accountId: string; displayName: string; }; description?: string; url?: string; avatarUrls: Record<string, string>; projectCategory?: { id: string; name: string; }; } interface Component { id: string; name: string; description?: string; lead?: { accountId: string; displayName: string }; assigneeType: 'PROJECT_DEFAULT' | 'COMPONENT_LEAD' | 'PROJECT_LEAD' | 'UNASSIGNED'; project: string; projectId: number; } interface Version { id: string; name: string; description?: string; archived: boolean; released: boolean; startDate?: string; releaseDate?: string; projectId: number; overdue?: boolean; } interface ProjectRole { id: number; name: string; description: string; actors: Array<{ id: number; displayName: string; type: 'atlassian-user-role-actor' | 'atlassian-group-role-actor'; actorUser?: { accountId: string }; actorGroup?: { name: string; displayName: string }; }>; }
Step 2: Project CRUD Operations
// Create Project interface CreateProjectInput { key: string; // 2-10 uppercase letters name: string; projectTypeKey: 'software' | 'service_desk' | 'business'; leadAccountId: string; description?: string; assigneeType?: 'PROJECT_LEAD' | 'UNASSIGNED'; categoryId?: number; } async function createProject( client: JiraClient, input: CreateProjectInput ): Promise<Project> { return client.request<Project>('/project', { method: 'POST', body: JSON.stringify({ key: input.key, name: input.name, projectTypeKey: input.projectTypeKey, leadAccountId: input.leadAccountId, description: input.description, assigneeType: input.assigneeType || 'UNASSIGNED', categoryId: input.categoryId, }), }); } // Update Project async function updateProject( client: JiraClient, projectKeyOrId: string, updates: Partial<{ key: string; name: string; description: string; leadAccountId: string; assigneeType: string; categoryId: number; }> ): Promise<Project> { return client.request<Project>(`/project/${projectKeyOrId}`, { method: 'PUT', body: JSON.stringify(updates), }); } // Delete Project (moves to trash, recoverable for 60 days) async function deleteProject( client: JiraClient, projectKeyOrId: string, enableUndo: boolean = true ): Promise<void> { await client.request(`/project/${projectKeyOrId}?enableUndo=${enableUndo}`, { method: 'DELETE', }); } // Archive Project async function archiveProject( client: JiraClient, projectKeyOrId: string ): Promise<void> { await client.request(`/project/${projectKeyOrId}/archive`, { method: 'POST', }); } // Restore Project async function restoreProject( client: JiraClient, projectKeyOrId: string ): Promise<Project> { return client.request<Project>(`/project/${projectKeyOrId}/restore`, { method: 'POST', }); }
Step 3: List and Search Projects
interface ProjectSearchOptions { startAt?: number; maxResults?: number; orderBy?: 'category' | '-category' | 'key' | '-key' | 'name' | '-name' | 'owner' | '-owner'; query?: string; // Search in name/key typeKey?: string; // software, service_desk, business categoryId?: number; expand?: ('description' | 'lead' | 'issueTypes' | 'url' | 'projectKeys' | 'permissions' | 'insight')[]; } async function searchProjects( client: JiraClient, options: ProjectSearchOptions = {} ): Promise<{ values: Project[]; total: number; isLast: boolean }> { const params = new URLSearchParams(); if (options.startAt) params.set('startAt', String(options.startAt)); if (options.maxResults) params.set('maxResults', String(options.maxResults)); if (options.orderBy) params.set('orderBy', options.orderBy); if (options.query) params.set('query', options.query); if (options.typeKey) params.set('typeKey', options.typeKey); if (options.categoryId) params.set('categoryId', String(options.categoryId)); if (options.expand) params.set('expand', options.expand.join(',')); return client.request(`/project/search?${params.toString()}`); } // Get recent projects async function getRecentProjects( client: JiraClient, maxResults: number = 20 ): Promise<Project[]> { const params = new URLSearchParams(); params.set('maxResults', String(maxResults)); params.set('expand', 'description,lead'); return client.request(`/project/recent?${params.toString()}`); }
Step 4: Component Management
// List Components async function getProjectComponents( client: JiraClient, projectKeyOrId: string ): Promise<Component[]> { return client.request(`/project/${projectKeyOrId}/components`); } // Create Component interface CreateComponentInput { project: string; // Project key name: string; description?: string; leadAccountId?: string; assigneeType?: 'PROJECT_DEFAULT' | 'COMPONENT_LEAD' | 'PROJECT_LEAD' | 'UNASSIGNED'; } async function createComponent( client: JiraClient, input: CreateComponentInput ): Promise<Component> { return client.request<Component>('/component', { method: 'POST', body: JSON.stringify({ project: input.project, name: input.name, description: input.description, leadAccountId: input.leadAccountId, assigneeType: input.assigneeType || 'PROJECT_DEFAULT', }), }); } // Update Component async function updateComponent( client: JiraClient, componentId: string, updates: Partial<{ name: string; description: string; leadAccountId: string; assigneeType: string; }> ): Promise<Component> { return client.request<Component>(`/component/${componentId}`, { method: 'PUT', body: JSON.stringify(updates), }); } // Delete Component async function deleteComponent( client: JiraClient, componentId: string, moveIssuesTo?: string // Component ID to move issues to ): Promise<void> { const query = moveIssuesTo ? `?moveIssuesTo=${moveIssuesTo}` : ''; await client.request(`/component/${componentId}${query}`, { method: 'DELETE', }); } // Get Component Issue Counts async function getComponentIssueCounts( client: JiraClient, componentId: string ): Promise<{ issueCount: number }> { return client.request(`/component/${componentId}/relatedIssueCounts`); }
Step 5: Version/Release Management
// List Versions async function getProjectVersions( client: JiraClient, projectKeyOrId: string, options: { startAt?: number; maxResults?: number; orderBy?: 'description' | '-description' | 'name' | '-name' | 'releaseDate' | '-releaseDate' | 'sequence' | '-sequence' | 'startDate' | '-startDate'; status?: 'released' | 'unreleased' | 'archived'; expand?: string; } = {} ): Promise<{ values: Version[]; total: number; isLast: boolean }> { const params = new URLSearchParams(); if (options.startAt) params.set('startAt', String(options.startAt)); if (options.maxResults) params.set('maxResults', String(options.maxResults)); if (options.orderBy) params.set('orderBy', options.orderBy); if (options.status) params.set('status', options.status); if (options.expand) params.set('expand', options.expand); return client.request(`/project/${projectKeyOrId}/version?${params.toString()}`); } // Create Version interface CreateVersionInput { projectId: number; name: string; description?: string; startDate?: string; // YYYY-MM-DD releaseDate?: string; // YYYY-MM-DD released?: boolean; archived?: boolean; } async function createVersion( client: JiraClient, input: CreateVersionInput ): Promise<Version> { return client.request<Version>('/version', { method: 'POST', body: JSON.stringify(input), }); } // Update Version async function updateVersion( client: JiraClient, versionId: string, updates: Partial<{ name: string; description: string; startDate: string; releaseDate: string; released: boolean; archived: boolean; moveUnfixedIssuesTo: string; // Version ID when releasing }> ): Promise<Version> { return client.request<Version>(`/version/${versionId}`, { method: 'PUT', body: JSON.stringify(updates), }); } // Release Version (mark as released) async function releaseVersion( client: JiraClient, versionId: string, moveUnfixedIssuesTo?: string ): Promise<Version> { return updateVersion(client, versionId, { released: true, releaseDate: new Date().toISOString().split('T')[0], moveUnfixedIssuesTo, }); } // Delete Version async function deleteVersion( client: JiraClient, versionId: string, options: { moveFixedIssuesTo?: string; moveAffectedIssuesTo?: string; } = {} ): Promise<void> { const params = new URLSearchParams(); if (options.moveFixedIssuesTo) params.set('moveFixedIssuesTo', options.moveFixedIssuesTo); if (options.moveAffectedIssuesTo) params.set('moveAffectedIssuesTo', options.moveAffectedIssuesTo); const query = params.toString() ? `?${params.toString()}` : ''; await client.request(`/version/${versionId}${query}`, { method: 'DELETE', }); } // Get Version Issue Counts async function getVersionIssueCounts( client: JiraClient, versionId: string ): Promise<{ issuesFixedCount: number; issuesAffectedCount: number; issueCountWithCustomFieldsShowingVersion: number; }> { return client.request(`/version/${versionId}/relatedIssueCounts`); } // Get Unresolved Issue Count async function getUnresolvedIssueCount( client: JiraClient, versionId: string ): Promise<{ issuesUnresolvedCount: number; self: string }> { return client.request(`/version/${versionId}/unresolvedIssueCount`); }
Step 6: Project Roles
// Get Project Roles async function getProjectRoles( client: JiraClient, projectKeyOrId: string ): Promise<Record<string, string>> { // Returns map of role name -> role URL return client.request(`/project/${projectKeyOrId}/role`); } // Get Role Details async function getProjectRole( client: JiraClient, projectKeyOrId: string, roleId: number ): Promise<ProjectRole> { return client.request(`/project/${projectKeyOrId}/role/${roleId}`); } // Add User to Role async function addUserToRole( client: JiraClient, projectKeyOrId: string, roleId: number, accountId: string ): Promise<ProjectRole> { return client.request(`/project/${projectKeyOrId}/role/${roleId}`, { method: 'POST', body: JSON.stringify({ user: [accountId], }), }); } // Add Group to Role async function addGroupToRole( client: JiraClient, projectKeyOrId: string, roleId: number, groupName: string ): Promise<ProjectRole> { return client.request(`/project/${projectKeyOrId}/role/${roleId}`, { method: 'POST', body: JSON.stringify({ group: [groupName], }), }); } // Remove Actor from Role async function removeActorFromRole( client: JiraClient, projectKeyOrId: string, roleId: number, actorType: 'user' | 'group', actorValue: string // accountId or groupName ): Promise<void> { const param = actorType === 'user' ? 'user' : 'group'; await client.request( `/project/${projectKeyOrId}/role/${roleId}?${param}=${encodeURIComponent(actorValue)}`, { method: 'DELETE' } ); }
Step 7: Project Properties
// List Project Properties async function getProjectProperties( client: JiraClient, projectKeyOrId: string ): Promise<{ keys: Array<{ key: string; self: string }> }> { return client.request(`/project/${projectKeyOrId}/properties`); } // Get Property async function getProjectProperty( client: JiraClient, projectKeyOrId: string, propertyKey: string ): Promise<{ key: string; value: any }> { return client.request(`/project/${projectKeyOrId}/properties/${propertyKey}`); } // Set Property async function setProjectProperty( client: JiraClient, projectKeyOrId: string, propertyKey: string, value: any ): Promise<void> { await client.request(`/project/${projectKeyOrId}/properties/${propertyKey}`, { method: 'PUT', body: JSON.stringify(value), }); } // Delete Property async function deleteProjectProperty( client: JiraClient, projectKeyOrId: string, propertyKey: string ): Promise<void> { await client.request(`/project/${projectKeyOrId}/properties/${propertyKey}`, { method: 'DELETE', }); }
Step 8: Project Validation
// Validate Project Key async function validateProjectKey( client: JiraClient, key: string ): Promise<{ errorMessages: string[]; errors: Record<string, string> }> { return client.request(`/projectvalidate/key?key=${encodeURIComponent(key)}`); } // Get Valid Project Key Suggestion async function getValidProjectKey( client: JiraClient, key: string ): Promise<string> { return client.request(`/projectvalidate/validProjectKey?key=${encodeURIComponent(key)}`); } // Get Valid Project Name async function getValidProjectName( client: JiraClient, name: string ): Promise<string> { return client.request(`/projectvalidate/validProjectName?name=${encodeURIComponent(name)}`); } // Get Project Types async function getProjectTypes( client: JiraClient ): Promise<Array<{ key: string; formattedKey: string; descriptionI18nKey: string; icon: string; color: string; }>> { return client.request('/project/type'); }
Step 9: High-Level Helpers
// Full project setup with components and version async function setupProject( client: JiraClient, config: { key: string; name: string; leadAccountId: string; description?: string; components?: string[]; initialVersion?: string; } ): Promise<{ project: Project; components: Component[]; version?: Version; }> { // Create project const project = await createProject(client, { key: config.key, name: config.name, projectTypeKey: 'software', leadAccountId: config.leadAccountId, description: config.description, }); // Create components const components: Component[] = []; for (const compName of config.components || []) { const comp = await createComponent(client, { project: project.key, name: compName, }); components.push(comp); } // Create initial version let version: Version | undefined; if (config.initialVersion) { version = await createVersion(client, { projectId: parseInt(project.id), name: config.initialVersion, }); } return { project, components, version }; } // Clone project structure (components + unreleased versions) async function cloneProjectStructure( client: JiraClient, sourceProjectKey: string, targetProjectKey: string ): Promise<{ componentsCloned: number; versionsCloned: number; }> { // Get source components const sourceComponents = await getProjectComponents(client, sourceProjectKey); // Get source versions (unreleased only) const sourceVersions = await getProjectVersions(client, sourceProjectKey, { status: 'unreleased', }); // Get target project const targetProject = await client.request<Project>(`/project/${targetProjectKey}`); // Clone components for (const comp of sourceComponents) { await createComponent(client, { project: targetProjectKey, name: comp.name, description: comp.description, }); } // Clone versions for (const ver of sourceVersions.values) { await createVersion(client, { projectId: parseInt(targetProject.id), name: ver.name, description: ver.description, startDate: ver.startDate, releaseDate: ver.releaseDate, }); } return { componentsCloned: sourceComponents.length, versionsCloned: sourceVersions.values.length, }; }
curl Examples
Create Project
curl -X POST "$JIRA_BASE_URL/rest/api/3/project" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)" \ -H "Content-Type: application/json" \ -d '{ "key": "NEWPROJ", "name": "New Project", "projectTypeKey": "software", "leadAccountId": "5b10a2844c20165700ede21g", "description": "Project description" }'
Update Project
curl -X PUT "$JIRA_BASE_URL/rest/api/3/project/SCRUM" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)" \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Project Name", "description": "Updated description" }'
Delete Project
curl -X DELETE "$JIRA_BASE_URL/rest/api/3/project/SCRUM?enableUndo=true" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)"
Search Projects
curl -X GET "$JIRA_BASE_URL/rest/api/3/project/search?query=scrum&expand=description,lead" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)" \ -H "Accept: application/json"
Create Component
curl -X POST "$JIRA_BASE_URL/rest/api/3/component" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)" \ -H "Content-Type: application/json" \ -d '{ "project": "SCRUM", "name": "Backend", "description": "Backend services" }'
Create Version
curl -X POST "$JIRA_BASE_URL/rest/api/3/version" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)" \ -H "Content-Type: application/json" \ -d '{ "projectId": 10000, "name": "v1.0.0", "description": "First release", "releaseDate": "2025-01-15" }'
Release Version
curl -X PUT "$JIRA_BASE_URL/rest/api/3/version/10001" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)" \ -H "Content-Type: application/json" \ -d '{ "released": true, "releaseDate": "2025-12-10" }'
Get Project Roles
curl -X GET "$JIRA_BASE_URL/rest/api/3/project/SCRUM/role" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)" \ -H "Accept: application/json"
Add User to Role
curl -X POST "$JIRA_BASE_URL/rest/api/3/project/SCRUM/role/10002" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)" \ -H "Content-Type: application/json" \ -d '{ "user": ["5b10a2844c20165700ede21g"] }'
Validate Project Key
curl -X GET "$JIRA_BASE_URL/rest/api/3/projectvalidate/key?key=NEWPROJ" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)" \ -H "Accept: application/json"
Set Project Property
curl -X PUT "$JIRA_BASE_URL/rest/api/3/project/SCRUM/properties/custom-config" \ -H "Authorization: Basic $(echo -n 'email:token' | base64)" \ -H "Content-Type: application/json" \ -d '{"setting1": "value1", "setting2": true}'
API Endpoints Summary
| Operation | Method | Path |
|---|---|---|
| Create project | POST | |
| Get project | GET | |
| Update project | PUT | |
| Delete project | DELETE | |
| Archive project | POST | |
| Restore project | POST | |
| Search projects | GET | |
| Recent projects | GET | |
| List components | GET | |
| Create component | POST | |
| Update component | PUT | |
| Delete component | DELETE | |
| List versions | GET | |
| Create version | POST | |
| Update version | PUT | |
| Delete version | DELETE | |
| Get roles | GET | |
| Get role | GET | |
| Add to role | POST | |
| Remove from role | DELETE | |
| List properties | GET | |
| Get property | GET | |
| Set property | PUT | |
| Delete property | DELETE | |
| Validate key | GET | |
| Valid key | GET | |
| Project types | GET | |
Common Patterns
Project Key Rules
- 2-10 uppercase letters only
- Must be unique across instance
- Cannot be reused for 60 days after deletion
Permission Requirements
| Operation | Required Permission |
|---|---|
| Create project | Jira admin |
| Update project | Project admin |
| Delete project | Jira admin |
| Manage components | Project admin |
| Manage versions | Project admin |
| Manage roles | Project admin |
Project Types
| Type | Use Case |
|---|---|
| Scrum/Kanban dev projects |
| Customer support projects |
| Simple task tracking |
Common Mistakes
- Using lowercase in project keys
- Forgetting to get projectId (numeric) for version creation
- Not handling 404 for deleted/archived projects
- Assuming role IDs are consistent (query first)
- Not using enableUndo=true for safe deletion
References
Version History
- 2025-12-10: Created comprehensive project management skill