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()
|
||||
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
|
||||
# ========================================
|
||||
|
||||
@@ -622,13 +622,65 @@ class GiteaMCPServer:
|
||||
),
|
||||
Tool(
|
||||
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={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"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": {
|
||||
"type": "string",
|
||||
@@ -880,6 +932,20 @@ class GiteaMCPServer:
|
||||
arguments.get('description'),
|
||||
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
|
||||
elif name == "list_pull_requests":
|
||||
result = await self.pr_tools.list_pull_requests(**arguments)
|
||||
|
||||
@@ -259,3 +259,70 @@ class LabelTools:
|
||||
return lookup[category_lower][value_lower]
|
||||
|
||||
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)
|
||||
|
||||
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 `: `).
|
||||
|
||||
### Step 7: Report Results
|
||||
|
||||
Reference in New Issue
Block a user