feat(gitea): add organization-level label creation
- Add create_org_label() method to gitea_client.py for org-level labels - Add create_label_smart() to labels.py that auto-detects correct level - Register both tools in server.py - Update labels-sync.md to use create_label_smart Label level detection: - Organization: Type/*, Priority/*, Complexity/*, Effort/*, Risk/*, Source/*, Agent/* - Repository: Component/*, Tech/* Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -621,6 +621,40 @@ class GiteaClient:
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
def create_org_label(
|
||||||
|
self,
|
||||||
|
org: str,
|
||||||
|
name: str,
|
||||||
|
color: str,
|
||||||
|
description: Optional[str] = None
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
Create a new label at the organization level.
|
||||||
|
|
||||||
|
Organization labels are shared across all repositories in the org.
|
||||||
|
Use this for workflow labels (Type, Priority, Complexity, Effort, etc.)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
org: Organization name
|
||||||
|
name: Label name (e.g., 'Type/Bug', 'Priority/High')
|
||||||
|
color: Hex color code (with or without #)
|
||||||
|
description: Optional label description
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Created label dictionary
|
||||||
|
"""
|
||||||
|
url = f"{self.base_url}/orgs/{org}/labels"
|
||||||
|
data = {
|
||||||
|
'name': name,
|
||||||
|
'color': color.lstrip('#') # Remove # if present
|
||||||
|
}
|
||||||
|
if description:
|
||||||
|
data['description'] = description
|
||||||
|
logger.info(f"Creating organization label '{name}' in {org}")
|
||||||
|
response = self.session.post(url, json=data)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# PULL REQUEST OPERATIONS
|
# PULL REQUEST OPERATIONS
|
||||||
# ========================================
|
# ========================================
|
||||||
|
|||||||
@@ -622,13 +622,65 @@ class GiteaMCPServer:
|
|||||||
),
|
),
|
||||||
Tool(
|
Tool(
|
||||||
name="create_label",
|
name="create_label",
|
||||||
description="Create a new label in the repository",
|
description="Create a new label in the repository (for repo-specific labels like Component/*, Tech/*)",
|
||||||
inputSchema={
|
inputSchema={
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Label name"
|
"description": "Label name (e.g., 'Component/Backend', 'Tech/Python')"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Label color (hex code)"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Label description"
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Repository name (owner/repo format)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "color"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="create_org_label",
|
||||||
|
description="Create a new label at organization level (for workflow labels like Type/*, Priority/*, Complexity/*, Effort/*)",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"org": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Organization name"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Label name (e.g., 'Type/Bug', 'Priority/High')"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Label color (hex code)"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Label description"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["org", "name", "color"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="create_label_smart",
|
||||||
|
description="Create a label at the appropriate level (org or repo) based on category. Org: Type/*, Priority/*, Complexity/*, Effort/*, Risk/*, Source/*, Agent/*. Repo: Component/*, Tech/*",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Label name (e.g., 'Type/Bug', 'Component/Backend')"
|
||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -880,6 +932,20 @@ class GiteaMCPServer:
|
|||||||
arguments.get('description'),
|
arguments.get('description'),
|
||||||
arguments.get('repo')
|
arguments.get('repo')
|
||||||
)
|
)
|
||||||
|
elif name == "create_org_label":
|
||||||
|
result = self.client.create_org_label(
|
||||||
|
arguments['org'],
|
||||||
|
arguments['name'],
|
||||||
|
arguments['color'],
|
||||||
|
arguments.get('description')
|
||||||
|
)
|
||||||
|
elif name == "create_label_smart":
|
||||||
|
result = await self.label_tools.create_label_smart(
|
||||||
|
arguments['name'],
|
||||||
|
arguments['color'],
|
||||||
|
arguments.get('description'),
|
||||||
|
arguments.get('repo')
|
||||||
|
)
|
||||||
# Pull Request tools
|
# Pull Request tools
|
||||||
elif name == "list_pull_requests":
|
elif name == "list_pull_requests":
|
||||||
result = await self.pr_tools.list_pull_requests(**arguments)
|
result = await self.pr_tools.list_pull_requests(**arguments)
|
||||||
|
|||||||
@@ -259,3 +259,70 @@ class LabelTools:
|
|||||||
return lookup[category_lower][value_lower]
|
return lookup[category_lower][value_lower]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Organization-level label categories (workflow labels shared across repos)
|
||||||
|
ORG_LABEL_CATEGORIES = {'agent', 'complexity', 'effort', 'efforts', 'priority', 'risk', 'source', 'type'}
|
||||||
|
|
||||||
|
# Repository-level label categories (project-specific labels)
|
||||||
|
REPO_LABEL_CATEGORIES = {'component', 'tech'}
|
||||||
|
|
||||||
|
async def create_label_smart(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
color: str,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
repo: Optional[str] = None
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
Create a label at the appropriate level (org or repo) based on category.
|
||||||
|
|
||||||
|
Organization labels: Agent, Complexity, Effort, Priority, Risk, Source, Type
|
||||||
|
Repository labels: Component, Tech
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Label name (e.g., 'Type/Bug', 'Component/Backend')
|
||||||
|
color: Hex color code
|
||||||
|
description: Optional label description
|
||||||
|
repo: Repository in 'owner/repo' format
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Created label dictionary with 'level' key indicating where it was created
|
||||||
|
"""
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
target_repo = repo or self.gitea.repo
|
||||||
|
if not target_repo or '/' not in target_repo:
|
||||||
|
raise ValueError("Use 'owner/repo' format (e.g. 'org/repo-name')")
|
||||||
|
|
||||||
|
# Parse category from label name
|
||||||
|
category = None
|
||||||
|
if '/' in name:
|
||||||
|
category = name.split('/')[0].lower().rstrip('s')
|
||||||
|
elif ':' in name:
|
||||||
|
category = name.split(':')[0].strip().lower().rstrip('s')
|
||||||
|
|
||||||
|
# Determine level
|
||||||
|
owner = target_repo.split('/')[0]
|
||||||
|
is_org = await loop.run_in_executor(
|
||||||
|
None,
|
||||||
|
lambda: self.gitea.is_org_repo(target_repo)
|
||||||
|
)
|
||||||
|
|
||||||
|
# If it's an org repo and the category is an org-level category, create at org level
|
||||||
|
if is_org and category in self.ORG_LABEL_CATEGORIES:
|
||||||
|
result = await loop.run_in_executor(
|
||||||
|
None,
|
||||||
|
lambda: self.gitea.create_org_label(owner, name, color, description)
|
||||||
|
)
|
||||||
|
result['level'] = 'organization'
|
||||||
|
logger.info(f"Created organization label '{name}' in {owner}")
|
||||||
|
else:
|
||||||
|
# Create at repo level
|
||||||
|
result = await loop.run_in_executor(
|
||||||
|
None,
|
||||||
|
lambda: self.gitea.create_label(name, color, description, target_repo)
|
||||||
|
)
|
||||||
|
result['level'] = 'repository'
|
||||||
|
logger.info(f"Created repository label '{name}' in {target_repo}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|||||||
@@ -62,12 +62,20 @@ Verify these required label categories exist:
|
|||||||
|
|
||||||
### Step 6: Create Missing Labels (if any)
|
### Step 6: Create Missing Labels (if any)
|
||||||
|
|
||||||
For each missing required label, call:
|
Use `create_label_smart` which automatically creates labels at the correct level:
|
||||||
|
- **Organization level**: Type/*, Priority/*, Complexity/*, Effort/*, Risk/*, Source/*, Agent/*
|
||||||
|
- **Repository level**: Component/*, Tech/*
|
||||||
|
|
||||||
```
|
```
|
||||||
mcp__plugin_projman_gitea__create_label(repo=REPO_NAME, name="Type: Bug", color="d73a4a")
|
mcp__plugin_projman_gitea__create_label_smart(repo=REPO_NAME, name="Type/Bug", color="d73a4a")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This automatically detects whether to create at org or repo level based on the category.
|
||||||
|
|
||||||
|
**Alternative (explicit control):**
|
||||||
|
- Org labels: `create_org_label(org="org-name", name="Type/Bug", color="d73a4a")`
|
||||||
|
- Repo labels: `create_label(repo=REPO_NAME, name="Component/Backend", color="5319e7")`
|
||||||
|
|
||||||
Use the label format that matches existing labels in the repo (slash `/` or colon-space `: `).
|
Use the label format that matches existing labels in the repo (slash `/` or colon-space `: `).
|
||||||
|
|
||||||
### Step 7: Report Results
|
### Step 7: Report Results
|
||||||
|
|||||||
Reference in New Issue
Block a user