feat: add markdown-validator agent and scoped-labels skill
- Add markdown-validator agent for validating Gitea issue descriptions - Add scoped-labels skill for managing exclusive labels (status::, priority::, type::) - Add init-scoped-labels.sh script to create standard label sets - Add GiteaClient methods: createLabel, updateLabel, setScopedLabel, setScopedStatus, setScopedPriority - Support exclusive labels (scoped labels) in Gitea API 1.21+ Created scoped labels: - status::new, status::planned, status::in-progress, status::review, status::testing, status::done, status::blocked, status::cancelled - priority::critical, priority::high, priority::medium, priority::low - type::bug, type::feature, type::enhancement, type::documentation, type::refactor, type::test, type::chore - size::xs, size::s, size::m, size::l, size::xl Scoped labels are mutually exclusive within their scope - applying status::in-progress automatically removes status::new
This commit is contained in:
226
.kilo/agents/markdown-validator.md
Normal file
226
.kilo/agents/markdown-validator.md
Normal file
@@ -0,0 +1,226 @@
|
||||
---
|
||||
description: Validates and corrects Markdown descriptions for Gitea issues
|
||||
mode: subagent
|
||||
model: qwen/qwen3.6-plus:free
|
||||
color: "#F97316"
|
||||
---
|
||||
|
||||
# Markdown Validator Agent
|
||||
|
||||
Validates and fixes Markdown descriptions for Gitea issues, ensuring proper formatting and structure.
|
||||
|
||||
## Role
|
||||
|
||||
You are a technical writer specializing in Markdown validation. You ensure all issue descriptions follow Gitea's Markdown specification and best practices.
|
||||
|
||||
## Input
|
||||
|
||||
- Issue title
|
||||
- Issue body/description
|
||||
- Context (what the issue is about)
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### 1. Checklist Format
|
||||
|
||||
✅ Correct:
|
||||
```markdown
|
||||
## Checklist
|
||||
- [x] Completed task
|
||||
- [ ] Pending task
|
||||
- [ ] Another pending task
|
||||
```
|
||||
|
||||
❌ Incorrect:
|
||||
```markdown
|
||||
## Checklist
|
||||
[x] Completed task (missing dash)
|
||||
- [x] Completed task (missing space after bracket)
|
||||
```
|
||||
|
||||
### 2. Headers
|
||||
|
||||
✅ Correct:
|
||||
```markdown
|
||||
## Description
|
||||
Content here
|
||||
|
||||
## Technical Details
|
||||
### Backend
|
||||
Content
|
||||
|
||||
### Frontend
|
||||
Content
|
||||
```
|
||||
|
||||
❌ Incorrect:
|
||||
```markdown
|
||||
##Description (missing space)
|
||||
## Description (leading spaces)
|
||||
```
|
||||
|
||||
### 3. Code Blocks
|
||||
|
||||
✅ Correct:
|
||||
```markdown
|
||||
```typescript
|
||||
const x = 1
|
||||
```
|
||||
```
|
||||
|
||||
❌ Incorrect:
|
||||
```markdown
|
||||
``typescript (missing backticks)
|
||||
```typescript
|
||||
(no closing backticks)
|
||||
```
|
||||
|
||||
### 4. Links
|
||||
|
||||
✅ Correct:
|
||||
```markdown
|
||||
[Link text](https://example.com)
|
||||
Related to #123
|
||||
```
|
||||
|
||||
❌ Incorrect:
|
||||
```markdown
|
||||
[Link text] (https://example.com) (space in URL)
|
||||
Related to Issue #123 (use shorthand #123)
|
||||
```
|
||||
|
||||
### 5. Tables
|
||||
|
||||
✅ Correct:
|
||||
```markdown
|
||||
| Column 1 | Column 2 |
|
||||
|----------|----------|
|
||||
| Value 1 | Value 2 |
|
||||
```
|
||||
|
||||
❌ Incorrect:
|
||||
```markdown
|
||||
|Column 1|Column 2| (missing spaces)
|
||||
|----------| (missing second column)
|
||||
```
|
||||
|
||||
### 6. Lists
|
||||
|
||||
✅ Correct:
|
||||
```markdown
|
||||
- Item 1
|
||||
- Nested item
|
||||
- Item 2
|
||||
1. Numbered
|
||||
2. Nested
|
||||
```
|
||||
|
||||
❌ Incorrect:
|
||||
```markdown
|
||||
- Item 1
|
||||
- Nested item (should be indented)
|
||||
```
|
||||
|
||||
### 7. Escaping
|
||||
|
||||
- Escape `#` in non-header contexts: `\#123`
|
||||
- Escape `*` in non-bold contexts: `\*literal\*`
|
||||
- Escape backticks: `\`literal backticks\``
|
||||
|
||||
## Output Format
|
||||
|
||||
Return the corrected Markdown:
|
||||
|
||||
```markdown
|
||||
## Description
|
||||
|
||||
[Brief description of what needs to be done]
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Task 1
|
||||
- [ ] Task 2
|
||||
- [ ] Task 3
|
||||
|
||||
## Technical Details
|
||||
|
||||
[Implementation notes]
|
||||
|
||||
## Related
|
||||
|
||||
- Related to #123
|
||||
- Depends on #456
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Criterion 1
|
||||
- [ ] Criterion 2
|
||||
```
|
||||
|
||||
## Common Fixes
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| Missing newline before header | Add `\n\n` before `#` |
|
||||
| Incorrect checkbox syntax | Fix to `- [ ]` or `- [x]` |
|
||||
| Missing language in code block | Add language identifier |
|
||||
| Broken links | Fix URL format |
|
||||
| Improper nesting | Add proper indentation |
|
||||
|
||||
## Example
|
||||
|
||||
**Input:**
|
||||
```
|
||||
Title: Add authentication
|
||||
|
||||
Body:
|
||||
Add auth system
|
||||
[x] Design API
|
||||
- Implement
|
||||
[ ] Test
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```markdown
|
||||
## Description
|
||||
|
||||
Implement authentication system for the application.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] Design API
|
||||
- [ ] Implement authentication logic
|
||||
- [ ] Write unit tests
|
||||
- [ ] Write integration tests
|
||||
- [ ] Update documentation
|
||||
|
||||
## Technical Details
|
||||
|
||||
- Use JWT for session management
|
||||
- Implement OAuth2 providers (Google, GitHub)
|
||||
- Add rate limiting for auth endpoints
|
||||
|
||||
## Related
|
||||
|
||||
- Related to #1
|
||||
- Depends on #2 (database setup)
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Users can log in with email/password
|
||||
- [ ] Users can log in via OAuth2
|
||||
- [ ] Sessions expire after 24 hours
|
||||
- [ ] Rate limiting prevents brute force
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
@markdown-validator <issue-content>
|
||||
```
|
||||
|
||||
The agent will:
|
||||
1. Parse the input Markdown
|
||||
2. Validate against Gitea specification
|
||||
3. Fix common issues automatically
|
||||
4. Return properly formatted Markdown
|
||||
198
.kilo/skills/scoped-labels/SKILL.md
Normal file
198
.kilo/skills/scoped-labels/SKILL.md
Normal file
@@ -0,0 +1,198 @@
|
||||
---
|
||||
name: scoped-labels
|
||||
description: Work with Gitea scoped labels (exclusive labels) for categorizing issues. Create, manage, and apply scoped labels like priority::high, status::new, type::bug etc.
|
||||
---
|
||||
|
||||
# Scoped Labels Skill
|
||||
|
||||
## Purpose
|
||||
|
||||
Manage Gitea scoped labels (exclusive labels) for better issue organization and pipeline workflow.
|
||||
|
||||
## What are Scoped Labels?
|
||||
|
||||
Scoped labels are labels with `::` separator that are mutually exclusive within the same scope. When you apply a scoped label, other labels in the same scope are automatically removed.
|
||||
|
||||
### Examples
|
||||
|
||||
| Scope | Labels | Behavior |
|
||||
|-------|--------|----------|
|
||||
| `status` | `status::new`, `status::in-progress`, `status::done` | Only one status at a time |
|
||||
| `priority` | `priority::critical`, `priority::high`, `priority::medium`, `priority::low` | Only one priority at a time |
|
||||
| `type` | `type::bug`, `type::feature`, `type::enhancement` | Only one type at a time |
|
||||
| `size` | `size::xs`, `size::s`, `size::m`, `size::l`, `size::xl` | Only one size at a time |
|
||||
|
||||
## API for Scoped Labels
|
||||
|
||||
### Create Scoped Label
|
||||
|
||||
```typescript
|
||||
// Create exclusive label (scoped)
|
||||
await client.createLabel({
|
||||
name: 'status::new',
|
||||
color: '0052cc',
|
||||
description: 'New issue, not started',
|
||||
exclusive: true // This makes it a scoped label
|
||||
})
|
||||
```
|
||||
|
||||
### Apply Scoped Label
|
||||
|
||||
```typescript
|
||||
// Apply scoped label (automatically removes other status:: labels)
|
||||
await client.addLabels(issueNumber, ['status::in-progress'])
|
||||
// This removes status::new if it was applied
|
||||
```
|
||||
|
||||
## Standard Label Sets
|
||||
|
||||
### Status Labels
|
||||
|
||||
```json
|
||||
[
|
||||
{ "name": "status::new", "color": "0052cc", "description": "New issue", "exclusive": true },
|
||||
{ "name": "status::planned", "color": "1d76db", "description": "Planned for sprint", "exclusive": true },
|
||||
{ "name": "status::in-progress", "color": "fbca04", "description": "Work in progress", "exclusive": true },
|
||||
{ "name": "status::review", "color": "d93f0b", "description": "Under review", "exclusive": true },
|
||||
{ "name": "status::testing", "color": "d4c5f9", "description": "In testing", "exclusive": true },
|
||||
{ "name": "status::done", "color": "0e8a16", "description": "Completed", "exclusive": true },
|
||||
{ "name": "status::blocked", "color": "b60205", "description": "Blocked", "exclusive": true }
|
||||
]
|
||||
```
|
||||
|
||||
### Priority Labels
|
||||
|
||||
```json
|
||||
[
|
||||
{ "name": "priority::critical", "color": "b60205", "description": "Critical priority", "exclusive": true },
|
||||
{ "name": "priority::high", "color": "d93f0b", "description": "High priority", "exclusive": true },
|
||||
{ "name": "priority::medium", "color": "fbca04", "description": "Medium priority", "exclusive": true },
|
||||
{ "name": "priority::low", "color": "0e8a16", "description": "Low priority", "exclusive": true }
|
||||
]
|
||||
```
|
||||
|
||||
### Type Labels
|
||||
|
||||
```json
|
||||
[
|
||||
{ "name": "type::bug", "color": "d73a4a", "description": "Something is broken", "exclusive": true },
|
||||
{ "name": "type::feature", "color": "0e8a16", "description": "New feature", "exclusive": true },
|
||||
{ "name": "type::enhancement", "color": "a2eeef", "description": "Improvement", "exclusive": true },
|
||||
{ "name": "type::documentation", "color": "0075ca", "description": "Documentation", "exclusive": true },
|
||||
{ "name": "type::refactor", "color": "7057ff", "description": "Code refactoring", "exclusive": true },
|
||||
{ "name": "type::test", "color": "d4c5f9", "description": "Testing", "exclusive": true }
|
||||
]
|
||||
```
|
||||
|
||||
### Size Labels
|
||||
|
||||
```json
|
||||
[
|
||||
{ "name": "size::xs", "color": "cfd3d7", "description": "Extra small (<1 hour)", "exclusive": true },
|
||||
{ "name": "size::s", "color": "c2e0c6", "description": "Small (1-2 hours)", "exclusive": true },
|
||||
{ "name": "size::m", "color": "fbca04", "description": "Medium (2-4 hours)", "exclusive": true },
|
||||
{ "name": "size::l", "color": "d93f0b", "description": "Large (4-8 hours)", "exclusive": true },
|
||||
{ "name": "size::xl", "color": "b60205", "description": "Extra large (>8 hours)", "exclusive": true }
|
||||
]
|
||||
```
|
||||
|
||||
### Component Labels (Non-exclusive)
|
||||
|
||||
```json
|
||||
[
|
||||
{ "name": "component::api", "color": "1d76db", "description": "API related", "exclusive": false },
|
||||
{ "name": "component::ui", "color": "bfdadc", "description": "UI related", "exclusive": false },
|
||||
{ "name": "component::database", "color": "c5def5", "description": "Database related", "exclusive": false },
|
||||
{ "name": "component::auth", "color": "d4c5f9", "description": "Authentication related", "exclusive": false }
|
||||
]
|
||||
```
|
||||
|
||||
## Workflow Integration
|
||||
|
||||
### Pipeline Status Flow
|
||||
|
||||
```
|
||||
status::new → status::planned → status::in-progress → status::review → status::testing → status::done
|
||||
↓
|
||||
status::blocked
|
||||
```
|
||||
|
||||
### Usage in Pipeline
|
||||
|
||||
```typescript
|
||||
// Set initial status
|
||||
await client.addLabels(issueNumber, ['status::new', 'priority::high', 'type::feature'])
|
||||
|
||||
// Later, update status (automatic removal of old status)
|
||||
await client.addLabels(issueNumber, ['status::in-progress']) // Removes status::new
|
||||
|
||||
// Block issue
|
||||
await client.addLabels(issueNumber, ['status::blocked']) // Removes status::in-progress
|
||||
|
||||
// Complete
|
||||
await client.addLabels(issueNumber, ['status::done']) // Removes any previous status
|
||||
```
|
||||
|
||||
## Setup Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Create standard scoped labels
|
||||
API_URL="https://git.softuniq.eu/api/v1"
|
||||
OWNER="UniqueSoft"
|
||||
REPO="APAW"
|
||||
TOKEN=$GITEA_TOKEN
|
||||
|
||||
# Status labels
|
||||
for LABEL in 'status::new:0052cc' 'status::planned:1d76db' 'status::in-progress:fbca04' 'status::review:d93f0b' 'status::testing:d4c5f9' 'status::done:0e8a16' 'status::blocked:b60205'; do
|
||||
NAME=$(echo $LABEL | cut -d: -f1,2)
|
||||
COLOR=$(echo $LABEL | cut -d: -f3)
|
||||
curl -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"$NAME\",\"color\":\"$COLOR\",\"exclusive\":true}" \
|
||||
"$API_URL/repos/$OWNER/$REPO/labels"
|
||||
done
|
||||
|
||||
# Priority labels
|
||||
for LABEL in 'priority::critical:b60205' 'priority::high:d93f0b' 'priority::medium:fbca04' 'priority::low:0e8a16'; do
|
||||
NAME=$(echo $LABEL | cut -d: -f1,2)
|
||||
COLOR=$(echo $LABEL | cut -d: -f3)
|
||||
curl -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"$NAME\",\"color\":\"$COLOR\",\"exclusive\":true}" \
|
||||
"$API_URL/repos/$OWNER/$REPO/labels"
|
||||
done
|
||||
```
|
||||
|
||||
## Label Naming Convention
|
||||
|
||||
1. **Scope first**: `scope::value` (e.g., `status::new`)
|
||||
2. **Lowercase**: Use lowercase letters and hyphens
|
||||
3. **Descriptive**: Make the purpose clear
|
||||
4. **Color coding**: Use semantic colors
|
||||
- Red: Critical/Blocked/Bug
|
||||
- Yellow: In progress/Warning
|
||||
- Green: Done/Success
|
||||
- Blue: Information
|
||||
|
||||
## Query by Label
|
||||
|
||||
```bash
|
||||
# Find all issues with status::in-progress
|
||||
curl -s "$API_URL/repos/$OWNER/$REPO/issues?labels=status::in-progress"
|
||||
|
||||
# Find all critical bugs
|
||||
curl -s "$API_URL/repos/$OWNER/$REPO/issues?labels=priority::critical,type::bug"
|
||||
|
||||
# Find blocked issues in any status
|
||||
curl -s "$API_URL/repos/$OWNER/$REPO/issues?labels=status::blocked"
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Automatic exclusivity**: Applying `status::in-progress` removes `status::new`
|
||||
2. **Clear categorization**: Each scope represents one dimension
|
||||
3. **Better filtering**: Combine scopes for precise queries
|
||||
4. **Visual clarity**: One label per column in board view
|
||||
148
scripts/init-scoped-labels.sh
Executable file
148
scripts/init-scoped-labels.sh
Executable file
@@ -0,0 +1,148 @@
|
||||
#!/bin/bash
|
||||
# Initialize standard scoped labels for Gitea
|
||||
# Usage: ./scripts/init-scoped-labels.sh
|
||||
|
||||
echo "=== Gitea Scoped Labels Initialization ==="
|
||||
echo ""
|
||||
|
||||
# Check for token
|
||||
if [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "❌ GITEA_TOKEN not set!"
|
||||
echo ""
|
||||
echo "Run: ./scripts/create-gitea-token.sh <username> <password>"
|
||||
echo "Or: export GITEA_TOKEN=your_token"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
API_URL="https://git.softuniq.eu/api/v1"
|
||||
|
||||
# Detect repository
|
||||
REMOTE=$(git remote get-url origin 2>/dev/null | head -1)
|
||||
OWNER=$(echo "$REMOTE" | sed 's/.*[:/]\([^/]*\)\/.*/\1/')
|
||||
REPO=$(echo "$REMOTE" | sed 's/.*[:/][^/]*\/\([^/.]*\).*/\1/')
|
||||
|
||||
if [ -z "$OWNER" ] || [ -z "$REPO" ]; then
|
||||
echo "❌ Could not detect repository"
|
||||
echo "Set OWNER and REPO manually:"
|
||||
echo " export OWNER=UniqueSoft"
|
||||
echo " export REPO=APAW"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📦 Repository: $OWNER/$REPO"
|
||||
echo ""
|
||||
|
||||
# Function to create label
|
||||
create_label() {
|
||||
local NAME="$1"
|
||||
local COLOR="$2"
|
||||
local DESC="$3"
|
||||
local EXCLUSIVE="$4"
|
||||
|
||||
# Check if label exists
|
||||
EXISTING=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||
"$API_URL/repos/$OWNER/$REPO/labels?name=$NAME" | grep -o "\"name\":\"$NAME\"")
|
||||
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo " ⏭️ $NAME (already exists)"
|
||||
return
|
||||
fi
|
||||
|
||||
# Create label
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"$NAME\",\"color\":\"$COLOR\",\"description\":\"$DESC\",\"exclusive\":$EXCLUSIVE}" \
|
||||
"$API_URL/repos/$OWNER/$REPO/labels")
|
||||
|
||||
if echo "$RESPONSE" | grep -q '"id"'; then
|
||||
echo " ✅ $NAME"
|
||||
else
|
||||
echo " ❌ $NAME - $(echo $RESPONSE | grep -o '"message":"[^"]*"')"
|
||||
fi
|
||||
}
|
||||
|
||||
# Status labels
|
||||
echo "🏷️ Creating status labels..."
|
||||
create_label "status::new" "0052cc" "New issue, not started" true
|
||||
create_label "status::planned" "1d76db" "Planned for sprint" true
|
||||
create_label "status::in-progress" "fbca04" "Work in progress" true
|
||||
create_label "status::review" "d93f0b" "Under review" true
|
||||
create_label "status::testing" "d4c5f9" "In testing" true
|
||||
create_label "status::done" "0e8a16" "Completed" true
|
||||
create_label "status::blocked" "b60205" "Blocked" true
|
||||
create_label "status::cancelled" "5319e7" "Cancelled" true
|
||||
echo ""
|
||||
|
||||
# Priority labels
|
||||
echo "🏷️ Creating priority labels..."
|
||||
create_label "priority::critical" "b60205" "Critical priority" true
|
||||
create_label "priority::high" "d93f0b" "High priority" true
|
||||
create_label "priority::medium" "fbca04" "Medium priority" true
|
||||
create_label "priority::low" "0e8a16" "Low priority" true
|
||||
echo ""
|
||||
|
||||
# Type labels
|
||||
echo "🏷️ Creating type labels..."
|
||||
create_label "type::bug" "d73a4a" "Something is broken" true
|
||||
create_label "type::feature" "0e8a16" "New feature" true
|
||||
create_label "type::enhancement" "a2eeef" "Improvement" true
|
||||
create_label "type::documentation" "0075ca" "Documentation" true
|
||||
create_label "type::refactor" "7057ff" "Code refactoring" true
|
||||
create_label "type::test" "d4c5f9" "Testing" true
|
||||
create_label "type::chore" "cfd3d7" "Maintenance task" true
|
||||
echo ""
|
||||
|
||||
# Size labels
|
||||
echo "🏷️ Creating size labels..."
|
||||
create_label "size::xs" "cfd3d7" "Extra small (<1 hour)" true
|
||||
create_label "size::s" "c2e0c6" "Small (1-2 hours)" true
|
||||
create_label "size::m" "fbca04" "Medium (2-4 hours)" true
|
||||
create_label "size::l" "d93f0b" "Large (4-8 hours)" true
|
||||
create_label "size::xl" "b60205" "Extra large (>8 hours)" true
|
||||
echo ""
|
||||
|
||||
# Component labels (non-exclusive)
|
||||
echo "🏷️ Creating component labels..."
|
||||
create_label "component::api" "1d76db" "API related" false
|
||||
create_label "component::ui" "bfdadc" "UI related" false
|
||||
create_label "component::database" "c5def5" "Database related" false
|
||||
create_label "component::auth" "d4c5f9" "Authentication related" false
|
||||
create_label "component::pipeline" "7057ff" "Pipeline related" false
|
||||
create_label "component::agent" "5319e7" "Agent related" false
|
||||
echo ""
|
||||
|
||||
# Pipeline status labels (alternative status format)
|
||||
echo "🏷️ Creating pipeline status labels..."
|
||||
create_label "pipeline::new" "0052cc" "New pipeline task" true
|
||||
create_label "pipeline::researching" "1d76db" "Research phase" true
|
||||
create_label "pipeline::designed" "bfd4f2" "Design complete" true
|
||||
create_label "pipeline::testing" "fbca04" "Tests in progress" true
|
||||
create_label "pipeline::implementing" "d93f0b" "Implementation" true
|
||||
create_label "pipeline::reviewing" "d4c5f9" "Code review" true
|
||||
create_label "pipeline::fixing" "e99695" "Fixing issues" true
|
||||
create_label "pipeline::releasing" "c2e0c6" "Release preparation" true
|
||||
create_label "pipeline::evaluated" "fef2c0" "Evaluation complete" true
|
||||
create_label "pipeline::completed" "0e8a16" "Pipeline complete" true
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "✅ Scoped labels initialized!"
|
||||
echo ""
|
||||
echo "📋 Usage examples:"
|
||||
echo ""
|
||||
echo "Set status (exclusive - removes other status:: labels):"
|
||||
echo " client.addLabels(issueNumber, ['status::in-progress', 'priority::high'])"
|
||||
echo ""
|
||||
echo "Set priority:"
|
||||
echo " client.addLabels(issueNumber, ['priority::critical'])"
|
||||
echo ""
|
||||
echo "Set type and size:"
|
||||
echo " client.addLabels(issueNumber, ['type::feature', 'size::m'])"
|
||||
echo ""
|
||||
echo "Add component (multiple allowed):"
|
||||
echo " client.addLabels(issueNumber, ['component::api', 'component::auth'])"
|
||||
echo ""
|
||||
echo "🔗 View all labels:"
|
||||
echo " https://git.softuniq.eu/$OWNER/$REPO/labels"
|
||||
echo ""
|
||||
@@ -312,21 +312,115 @@ export class GiteaClient {
|
||||
)
|
||||
}
|
||||
|
||||
async getLabel(labelId: number): Promise<Label> {
|
||||
async getLabel(labelId: number | string): Promise<Label> {
|
||||
return this.request(
|
||||
"GET",
|
||||
`/repos/${this.owner}/${this.repo}/labels/${labelId}`
|
||||
)
|
||||
}
|
||||
|
||||
async createLabel(label: { name: string; color: string; description?: string }): Promise<Label> {
|
||||
async createLabel(label: {
|
||||
name: string
|
||||
color: string
|
||||
description?: string
|
||||
exclusive?: boolean
|
||||
}): Promise<Label> {
|
||||
// Ensure color doesn't have # prefix
|
||||
const color = label.color.startsWith('#') ? label.color.slice(1) : label.color
|
||||
|
||||
return this.request(
|
||||
"POST",
|
||||
`/repos/${this.owner}/${this.repo}/labels`,
|
||||
label
|
||||
{
|
||||
name: label.name,
|
||||
color,
|
||||
description: label.description,
|
||||
exclusive: label.exclusive ?? false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async updateLabel(labelId: number | string, label: {
|
||||
name?: string
|
||||
color?: string
|
||||
description?: string
|
||||
exclusive?: boolean
|
||||
}): Promise<Label> {
|
||||
const body: Record<string, unknown> = {}
|
||||
if (label.name) body.name = label.name
|
||||
if (label.color) {
|
||||
body.color = label.color.startsWith('#') ? label.color.slice(1) : label.color
|
||||
}
|
||||
if (label.description !== undefined) body.description = label.description
|
||||
if (label.exclusive !== undefined) body.exclusive = label.exclusive
|
||||
|
||||
return this.request(
|
||||
"PATCH",
|
||||
`/repos/${this.owner}/${this.repo}/labels/${labelId}`,
|
||||
body
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== Scoped Labels ====================
|
||||
|
||||
/**
|
||||
* Set status using scoped labels (exclusive within scope)
|
||||
* Applies status::<status> label and removes other status:: labels
|
||||
*/
|
||||
async setScopedStatus(issueNumber: number, status: string): Promise<Label[]> {
|
||||
const statusLabel = `status::${status}`
|
||||
return this.setScopedLabel(issueNumber, 'status', statusLabel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set priority using scoped labels
|
||||
* Applies priority::<priority> label and removes other priority:: labels
|
||||
*/
|
||||
async setScopedPriority(issueNumber: number, priority: string): Promise<Label[]> {
|
||||
const priorityLabel = `priority::${priority}`
|
||||
return this.setScopedLabel(issueNumber, 'priority', priorityLabel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic method to set a scoped label (exclusive within scope)
|
||||
* Removes all labels with the same scope prefix before adding the new one
|
||||
*/
|
||||
async setScopedLabel(issueNumber: number, scope: string, labelName: string): Promise<Label[]> {
|
||||
// Get current labels
|
||||
const currentLabels = await this.getIssueLabels(issueNumber)
|
||||
|
||||
// Find and remove labels with the same scope
|
||||
const scopePrefix = `${scope}::`
|
||||
const labelsToRemove = currentLabels.filter(l => l.name.startsWith(scopePrefix))
|
||||
|
||||
for (const label of labelsToRemove) {
|
||||
await this.removeLabel(issueNumber, label.id)
|
||||
}
|
||||
|
||||
// Find or create the new label
|
||||
const allLabels = await this.getRepoLabels()
|
||||
let targetLabel = allLabels.find(l => l.name === labelName)
|
||||
|
||||
if (!targetLabel) {
|
||||
// Create the label if it doesn't exist
|
||||
const colors: Record<string, string> = {
|
||||
'status': '0052cc',
|
||||
'priority': 'd93f0b',
|
||||
'type': '0e8a16',
|
||||
'size': 'fbca04',
|
||||
'component': '1d76db'
|
||||
}
|
||||
targetLabel = await this.createLabel({
|
||||
name: labelName,
|
||||
color: colors[scope] || '0052cc',
|
||||
description: `${scope} label: ${labelName.split('::')[1]}`,
|
||||
exclusive: true
|
||||
})
|
||||
}
|
||||
|
||||
return this.addLabels(issueNumber, [targetLabel.id])
|
||||
}
|
||||
|
||||
// ==================== Status Management ====================
|
||||
|
||||
async getStatusLabels(issueNumber: number): Promise<Label[]> {
|
||||
|
||||
Reference in New Issue
Block a user