infra(forgejo): migrate gh-tickets scripts + skills to Forgejo API

This commit is contained in:
Jens Reinemann 2026-05-19 22:41:20 +02:00
parent 00b28d2f58
commit 2af82d60d0
6 changed files with 161 additions and 204 deletions

View file

@ -1,11 +1,11 @@
--- ---
name: gh-tickets name: gh-tickets
description: "Konventionen für GitHub-Issues in diesem Workspace: Aufgabentyp-Labels, Sortierung über das Project Board (Number-Feld 'Order') und CLI-Abfragen. Verwende diesen Skill immer dann, wenn Issues angelegt, abgefragt, sortiert oder vom nextstep-Router verarbeitet werden. Trigger-Phrasen: 'nächstes Ticket', 'Issue anlegen', 'Reihenfolge', 'Priorität', 'nextstep', 'Order', 'Board'." description: "Konventionen für Forgejo-Issues in diesem Workspace: Aufgabentyp-Labels, Sortierung über status:todo-Labels und CLI-Abfragen. Verwende diesen Skill immer dann, wenn Issues angelegt, abgefragt, sortiert oder vom nextstep-Router verarbeitet werden. Trigger-Phrasen: 'nächstes Ticket', 'Issue anlegen', 'Reihenfolge', 'Priorität', 'nextstep', 'Status', 'Board'."
--- ---
# Skill: GitHub Tickets (gh-tickets) # Skill: Forgejo Tickets (gh-tickets)
Dieses Dokument definiert die verbindlichen Konventionen für GitHub-Issues im Repository `jreinemann-euris/bollwerk`. Dieses Dokument definiert die verbindlichen Konventionen für Issues im Forgejo-Repository `bollwerkadmin/bollwerk` (`https://git.bollwerk.online`).
--- ---
@ -49,83 +49,69 @@ Stoppe und fordere den User zur Zuordnung auf. Starte den Workflow **nicht** ohn
--- ---
## 2. Sortierung über Project Board ## 2. Sortierung über Status-Labels
Die Abarbeitungsreihenfolge wird über das **Project Board** gesteuert, nicht über Labels. Die Abarbeitungsreihenfolge wird über **Status-Labels** in Forgejo gesteuert.
### Board-Daten ### Status-Labels
| Label | Bedeutung |
| ------------------- | ------------------------------------------------- |
| `status:todo` | Nächste offene Aufgabe (wird von next-ticket.ps1 gefunden) |
| `status:in-progress`| Aktuell in Bearbeitung |
| `status:done` | Abgeschlossen |
| `status:backlog` | Im Backlog wird erst nach todo-Items bearbeitet |
### Repository
| Eigenschaft | Wert | | Eigenschaft | Wert |
| ------------ | ---------------------------------------------------- | | ------------ | ------------------------------------------------- |
| Projekt-Name | `Bollwerk` | | URL | https://git.bollwerk.online/bollwerkadmin/bollwerk |
| Projekt-Nr | `2` | | API-Base | https://git.bollwerk.online/api/v1 |
| Owner | `jreinemann-euris` | | Token | `.github/skills/gh-tickets/forgejo-token.txt` (lokal, nicht committed) |
| Sortierfeld | `Order` (Number-Feld) |
| URL | https://github.com/users/jreinemann-euris/projects/2 |
### Order-Feld Konventionen
- **Kleinere Zahl = höhere Priorität** (wird zuerst abgearbeitet)
- **Jeder Order-Wert MUSS eindeutig sein** keine zwei Items dürfen denselben Wert haben
- Standardwerte: 10, 20, 30, 40, … (in 10er-Schritten, um Platz für Einfügungen zu lassen)
- Ein dringend vorgezogenes Ticket bekommt einen Wert **zwischen** den bestehenden (z.B. 15 zwischen 10 und 20, oder 5 vor 10)
- Wenn keine bestimmte Position vorgegeben ist, erhält das Ticket den **höchsten bestehenden Order-Wert + 10** (= wird am Ende eingefügt)
- Items ohne Order-Wert sind ein **Fehler** und müssen sofort einen Order-Wert erhalten
### Nächstes offenes Issue ermitteln ### Nächstes offenes Issue ermitteln
**Skript:** `.github/skills/gh-tickets/next-ticket.ps1` **Skript:** `.github/skills/gh-tickets/next-ticket.ps1`
```powershell ```powershell
# Nächstes Ticket (priority:high zuerst, dann Order aufsteigend): # Nächstes Ticket (priority:high zuerst, dann Issue-Nummer aufsteigend):
& ".github/skills/gh-tickets/next-ticket.ps1" & ".github/skills/gh-tickets/next-ticket.ps1"
# Bestimmtes Issue abfragen (Typ-Label prüfen): # Bestimmtes Issue abfragen (Typ-Label prüfen):
& ".github/skills/gh-tickets/next-ticket.ps1" -IssueNumber 98 & ".github/skills/gh-tickets/next-ticket.ps1" -IssueNumber 128
``` ```
Ausgabe: `#68 [M] CRM: Erweiterte Kundensuche (Order: 120)` Ausgabe: `#128 [I] infra(forgejo): Projektlinks, Skills und Referenzen umstellen (Order: 128)`
### Issue zum Board hinzufügen ### Status aktualisieren
```powershell
# Issue zum Board hinzufügen
gh project item-add 2 --owner jreinemann-euris --url "https://github.com/jreinemann-euris/bollwerk/issues/<N>"
# Order-Wert setzen (erfordert Item-ID und Field-ID)
gh project item-edit --id <ITEM_ID> --field-id <ORDER_FIELD_ID> --project-id <PROJECT_ID> --number <ORDER_VALUE>
```
### Board-Status aktualisieren
**Skript:** `.github/skills/gh-tickets/set-board-status.ps1` **Skript:** `.github/skills/gh-tickets/set-board-status.ps1`
```powershell ```powershell
# Status auf "In Progress" setzen # Status auf "In Progress" setzen
& ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 68 -Status InProgress & ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 128 -Status InProgress
# Status auf "Done" setzen # Status auf "Done" setzen
& ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 68 -Status Done & ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 128 -Status Done
# Status auf "Todo" zurücksetzen # Status auf "Todo" zurücksetzen
& ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 68 -Status Todo & ".github/skills/gh-tickets/set-board-status.ps1" -IssueNumber 128 -Status Todo
``` ```
Gültige Status-Werte: `Todo`, `InProgress`, `Done` Gültige Status-Werte: `Todo`, `InProgress`, `Done`, `Backlog`
--- ---
## 3. Issue anlegen (Checkliste) ## 3. Issue anlegen (Checkliste)
Beim Anlegen eines neuen Issues **MÜSSEN alle 5 Schritte** durchgeführt werden: Beim Anlegen eines neuen Issues **MÜSSEN alle 3 Schritte** durchgeführt werden:
1. **Titel**: Beschreibend, ggf. mit Modulpräfix (z.B. `CRM:`, `feat(core):`) 1. **Titel**: Beschreibend, ggf. mit Modulpräfix (z.B. `feat(core):`, `infra(forgejo):`)
2. **Type-Label**: Genau eines von `migration`, `tech-decision`, `infrastructure`, `planning`, `test` 2. **Type-Label**: Genau eines von `migration`, `tech-decision`, `infrastructure`, `block-planning`, `feature`, `refactoring`, `planning`, `test`
3. **Weitere Labels**: Optional (z.B. `crm`, `enhancement`) 3. **Status-Label**: `status:todo` (sofort bearbeiten) oder `status:backlog` (später)
4. **Board**: Issue **sofort** zum Project Board hinzufügen (ein Issue ohne Board-Eintrag existiert nicht für die Abarbeitung!)
5. **Order**: **Eindeutigen** Order-Wert setzen. Vor dem Setzen die bestehenden Order-Werte abfragen und sicherstellen, dass der gewählte Wert noch nicht vergeben ist. Ohne spezifische Positionsvorgabe: höchsten bestehenden Wert + 10
> **Kein Issue ohne Board + Order!** Ein Issue ohne Board-Eintrag wird bei „nächste Aufgabe" nicht gefunden. > **Kein Issue ohne Status-Label!** Ein Issue ohne `status:todo` oder `status:backlog` wird bei „nächste Aufgabe" nicht gefunden.
### Dringend vorgezogenes Issue anlegen ("als nächstes") ### Dringend vorgezogenes Issue anlegen ("als nächstes")
@ -134,21 +120,24 @@ Beim Anlegen eines neuen Issues **MÜSSEN alle 5 Schritte** durchgeführt werden
Wenn während einer laufenden Aufgabe ein neues Issue entdeckt wird, das **direkt danach** bearbeitet werden soll: Wenn während einer laufenden Aufgabe ein neues Issue entdeckt wird, das **direkt danach** bearbeitet werden soll:
```powershell ```powershell
# Issue anlegen und als nächstes Ticket einsortieren (niedrigster Order-Wert) # Issue anlegen mit status:todo (wird als nächstes gefunden, niedrigste Nummer)
& ".github/skills/gh-tickets/create-next-ticket.ps1" -Title "CRM: Bug in Kundensuche" -Labels "migration,crm" -Body "Beschreibung..." & ".github/skills/gh-tickets/create-next-ticket.ps1" -Title "fix(server): Bug in Auth" -Labels "infrastructure" -Body "Beschreibung..."
# Issue ins Backlog legen
& ".github/skills/gh-tickets/create-next-ticket.ps1" -Title "feat: Neues Feature" -Labels "feature" -Status Backlog
``` ```
Das Skript erledigt automatisch alle 5 Schritte: Issue anlegen, Board hinzufügen, Order auf niedrigsten Wert setzen. Das Skript erledigt automatisch: Issue anlegen + Status-Label setzen.
**Trigger-Phrasen:** „als nächstes anlegen", „nächstes Ticket erstellen", „direkt danach bearbeiten" **Trigger-Phrasen:** „als nächstes anlegen", „nächstes Ticket erstellen", „direkt danach bearbeiten"
--- ---
## 4. Sortierung: Priorität + Order (zweistufig) ## 4. Sortierung: Priorität + Issue-Nummer (zweistufig)
Die Abarbeitungsreihenfolge wird **zweistufig** bestimmt: Die Abarbeitungsreihenfolge wird **zweistufig** bestimmt:
1. **Primär: Label `priority:high`** → Issues mit diesem Label kommen **immer zuerst**, unabhängig vom Order-Wert 1. **Primär: Label `priority:high`** → Issues mit diesem Label kommen **immer zuerst**
2. **Sekundär: Label `priority:low`** → Issues mit diesem Label kommen **immer zuletzt**, unabhängig vom Order-Wert 2. **Sekundär: Label `priority:low`** → Issues mit diesem Label kommen **immer zuletzt**
3. **Tertiär: Order-Feld** → Innerhalb derselben Prioritätsstufe sortiert der Order-Wert aufsteigend (kleinere Zahl = früher dran) 3. **Tertiär: Issue-Nummer aufsteigend** → Niedrigere Nummer = früher erstellt = höhere Priorität
Beispiel: Ein Issue mit `priority:high` und Order 300 kommt **vor** einem Issue ohne Priorität mit Order 5. Ein normales Issue mit Order 999 kommt **vor** einem `priority:low`-Issue mit Order 1. Beispiel: Ein Issue #50 mit `priority:high` kommt **vor** Issue #10 ohne Priorität.

View file

@ -1,20 +1,21 @@
<# <#
.SYNOPSIS .SYNOPSIS
Erstellt ein neues GitHub-Issue und fügt es zum Project Board hinzu. Erstellt ein neues Issue auf Forgejo und setzt das Status-Label.
.DESCRIPTION .DESCRIPTION
Legt ein Issue an, fügt es zum Project Board hinzu, setzt den Board-Status Legt ein Issue an und setzt ein Status-Label (status:todo oder status:backlog).
und den Order-Wert. Die Reihenfolge ergibt sich aus der Issue-Nummer (aufsteigend = ältere zuerst).
- Status "Todo" (Standard): Order = niedrigster Todo-Wert minus 1 wird als nächstes Ticket eingereiht. - Status "Todo" (Standard): Label status:todo wird als nächstes Ticket gefunden.
- Status "Backlog": Order = höchster nicht-Done-Wert plus 10 wird ans Ende gestellt. - Status "Backlog": Label status:backlog wird erst nach Todo-Items bearbeitet.
.PARAMETER Title .PARAMETER Title
Titel des neuen Issues. Titel des neuen Issues.
.PARAMETER Body .PARAMETER Body
Body-Text des Issues (Markdown). Optional, Default: leer. Body-Text des Issues (Markdown). Optional, Default: leer.
.PARAMETER Labels .PARAMETER Labels
Komma-separierte Labels (z.B. "migration,crm,enhancement"). Muss mindestens Komma-separierte Labels (z.B. "infrastructure,enhancement"). Muss mindestens
ein Type-Label enthalten: migration, tech-decision oder infrastructure. ein Type-Label enthalten: migration, tech-decision, infrastructure, block-planning,
feature, refactoring, planning oder test.
.PARAMETER Status .PARAMETER Status
Board-Status des neuen Tickets: "Todo" (Standard) oder "Backlog". Status des neuen Tickets: "Todo" (Standard) oder "Backlog".
#> #>
param( param(
[Parameter(Mandatory)][string]$Title, [Parameter(Mandatory)][string]$Title,
@ -24,13 +25,15 @@ param(
[string]$Status = "Todo" [string]$Status = "Todo"
) )
$repo = "jreinemann-euris/bollwerk" $forgejoBase = "https://git.bollwerk.online/api/v1"
$projectId = "PVT_kwHOCFqiJ84BXk9U" $repo = "bollwerkadmin/bollwerk"
$orderFieldId = "PVTF_lAHOCFqiJ84BXk9UzhSw4jo" $tokenFile = Join-Path $PSScriptRoot "forgejo-token.txt"
$statusFieldId = "PVTSSF_lAHOCFqiJ84BXk9UzhSw4es" $token = (Get-Content $tokenFile -Raw -ErrorAction Stop).Trim()
$statusOptionMap = @{ $headers = @{ Authorization = "token $token"; "Content-Type" = "application/json" }
"Todo" = "f75ad846"
"Backlog" = "4ce6ee37" $statusLabelMap = @{
"Todo" = "status:todo"
"Backlog" = "status:backlog"
} }
# --- 1. Type-Label validieren --- # --- 1. Type-Label validieren ---
@ -42,65 +45,27 @@ if (-not $hasType) {
exit 1 exit 1
} }
# --- 2. Issue anlegen --- # --- 2. Alle Label-Namen zu IDs auflösen ---
$createArgs = @("issue", "create", "--repo", $repo, "--title", $Title, "--label", $Labels) $allLabels = (Invoke-WebRequest -Uri "$forgejoBase/repos/$repo/labels?limit=50" -Headers $headers -UseBasicParsing).Content | ConvertFrom-Json
if ($Body) { $statusLabelName = $statusLabelMap[$Status]
$createArgs += "--body" $allRequiredLabels = $labelList + $statusLabelName
$createArgs += $Body
} $labelIds = @()
$issueUrl = & gh @createArgs 2>&1 foreach ($name in $allRequiredLabels) {
if ($LASTEXITCODE -ne 0) { $found = $allLabels | Where-Object { $_.name -eq $name } | Select-Object -First 1
Write-Error "Fehler beim Anlegen des Issues: $issueUrl" if ($found) { $labelIds += $found.id }
exit 1 else { Write-Warning "Label '$name' nicht gefunden wird übersprungen." }
} }
$issueNumber = ($issueUrl -split '/')[-1] # --- 3. Issue anlegen ---
$issueBody = @{ title = $Title; labels = $labelIds }
if ($Body) { $issueBody["body"] = $Body }
$issueJson = $issueBody | ConvertTo-Json
# --- 3. Zum Board hinzufügen --- $issueResp = Invoke-WebRequest -Uri "$forgejoBase/repos/$repo/issues" -Method Post -Headers $headers -Body $issueJson -UseBasicParsing
$addResult = gh project item-add 2 --owner jreinemann-euris --url $issueUrl --format json 2>&1 $issue = $issueResp.Content | ConvertFrom-Json
if ($LASTEXITCODE -ne 0) { $issueNumber = $issue.number
Write-Error "Fehler beim Hinzufügen zum Board: $addResult" $issueUrl = $issue.html_url
exit 1
}
$itemId = ($addResult | ConvertFrom-Json).id
# --- 4. Order-Wert ermitteln --- Write-Host "#$issueNumber $Title (Status: $Status)"
$raw = gh project item-list 2 --owner jreinemann-euris --format json --limit 200 | ConvertFrom-Json Write-Host "URL: $issueUrl"
$otherItems = $raw.items | Where-Object { $_.status -ne "Done" -and $_.id -ne $itemId }
if ($Status -eq "Todo") {
# Als nächstes Ticket: niedrigster bestehender Wert minus 1
$refOrder = $otherItems |
ForEach-Object { [double]$_.order } |
Where-Object { $_ -gt 0 } |
Measure-Object -Minimum |
Select-Object -ExpandProperty Minimum
if ($null -eq $refOrder) { $refOrder = 10 }
$newOrder = $refOrder - 1
} else {
# Backlog: ans Ende stellen (höchster bestehender Wert plus 10)
$refOrder = $otherItems |
ForEach-Object { [double]$_.order } |
Where-Object { $_ -gt 0 } |
Measure-Object -Maximum |
Select-Object -ExpandProperty Maximum
if ($null -eq $refOrder) { $refOrder = 0 }
$newOrder = $refOrder + 10
}
# --- 5. Order setzen ---
gh project item-edit --project-id $projectId --id $itemId --field-id $orderFieldId --number $newOrder 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Error "Fehler beim Setzen des Order-Werts."
exit 1
}
# --- 6. Board-Status setzen ---
$optionId = $statusOptionMap[$Status]
gh project item-edit --project-id $projectId --id $itemId --field-id $statusFieldId --single-select-option-id $optionId 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Error "Fehler beim Setzen des Board-Status."
exit 1
}
Write-Host "#$issueNumber $Title (Status: $Status, Order: $newOrder)"

View file

@ -1,56 +1,58 @@
<# <#
.SYNOPSIS .SYNOPSIS
Ermittelt das nächste offene Ticket aus dem GitHub Project Board. Ermittelt das nächste offene Ticket aus Forgejo (via status:todo Label).
.DESCRIPTION .DESCRIPTION
Sortierung: priority:high (0) normal (1) priority:low (2), dann Order aufsteigend. Sortierung: priority:high (0) normal (1) priority:low (2), dann Issue-Nummer aufsteigend.
Gibt Nummer, Typ-Label, Titel und Order aus. Gibt Nummer, Typ-Label, Titel und Order (= Issue-Nummer) aus.
.PARAMETER IssueNumber .PARAMETER IssueNumber
Optional: Direkt eine Issue-Nummer abfragen statt Board-Suche. Optional: Direkt eine Issue-Nummer abfragen statt automatischer Suche.
#> #>
param([int]$IssueNumber) param([int]$IssueNumber)
$repo = "jreinemann-euris/bollwerk" $forgejoBase = "https://git.bollwerk.online/api/v1"
$repo = "bollwerkadmin/bollwerk"
$tokenFile = Join-Path $PSScriptRoot "forgejo-token.txt"
$token = (Get-Content $tokenFile -Raw -ErrorAction Stop).Trim()
$headers = @{ Authorization = "token $token" }
function Get-IssueType($labelNames) {
if ($labelNames -contains "block-planning") { "[B]" }
elseif ($labelNames -contains "migration") { "[M]" }
elseif ($labelNames -contains "feature") { "[F]" }
elseif ($labelNames -contains "refactoring") { "[F]" }
elseif ($labelNames -contains "tech-decision") { "[T]" }
elseif ($labelNames -contains "infrastructure") { "[I]" }
elseif ($labelNames -contains "planning") { "[P]" }
elseif ($labelNames -contains "test") { "[X]" }
else { "[?]" }
}
if ($IssueNumber -gt 0) { if ($IssueNumber -gt 0) {
# Variante A: Explizite Issue-Nummer # Variante A: Explizite Issue-Nummer → Forgejo REST API
$json = gh issue view $IssueNumber --repo $repo --json number,title,labels | ConvertFrom-Json $url = "$forgejoBase/repos/$repo/issues/$IssueNumber"
$labels = $json.labels | ForEach-Object { $_.name } $issue = (Invoke-WebRequest -Uri $url -Headers $headers -UseBasicParsing).Content | ConvertFrom-Json
$type = if ($labels -contains "block-planning") { "[B]" } $labelNames = $issue.labels | ForEach-Object { $_.name }
elseif ($labels -contains "migration") { "[M]" } $type = Get-IssueType $labelNames
elseif ($labels -contains "feature") { "[F]" } Write-Host "#$($issue.number) $type $($issue.title)"
elseif ($labels -contains "refactoring") { "[F]" }
elseif ($labels -contains "tech-decision") { "[T]" }
elseif ($labels -contains "infrastructure") { "[I]" }
elseif ($labels -contains "planning") { "[P]" }
elseif ($labels -contains "test") { "[X]" }
else { "[?]" }
Write-Host "#$($json.number) $type $($json.title)"
} }
else { else {
# Variante B: Nächstes Ticket per Board-Query (client-seitig auf Todo gefiltert) # Variante B: Nächstes Ticket per Forgejo-Label status:todo
$raw = gh project item-list 2 --owner jreinemann-euris --format json --limit 200 | ConvertFrom-Json $url = "$forgejoBase/repos/$repo/issues?state=open&type=issues&labels=status%3Atodo&limit=50&sort=oldest"
if ($null -eq $raw -or $null -eq $raw.items) { $issues = (Invoke-WebRequest -Uri $url -Headers $headers -UseBasicParsing).Content | ConvertFrom-Json
Write-Host "Keine offenen Tickets gefunden." if ($null -eq $issues -or $issues.Count -eq 0) {
Write-Host "Keine offenen Tickets mit 'status:todo' gefunden."
return return
} }
$todos = $raw.items | Where-Object { $_.status -eq "Todo" } | ForEach-Object { $todos = $issues | ForEach-Object {
$labels = $_.labels $labelNames = $_.labels | ForEach-Object { $_.name }
$prio = if ($labels -contains "priority:high") { 0 } $prio = if ($labelNames -contains "priority:high") { 0 }
elseif ($labels -contains "priority:low") { 2 } elseif ($labelNames -contains "priority:low") { 2 }
else { 1 } else { 1 }
$type = if ($labels -contains "block-planning") { "[B]" } $type = Get-IssueType $labelNames
elseif ($labels -contains "migration") { "[M]" }
elseif ($labels -contains "feature") { "[F]" }
elseif ($labels -contains "refactoring") { "[F]" }
elseif ($labels -contains "tech-decision") { "[T]" }
elseif ($labels -contains "infrastructure") { "[I]" }
elseif ($labels -contains "planning") { "[P]" }
elseif ($labels -contains "test") { "[X]" }
else { "[?]" }
[PSCustomObject]@{ [PSCustomObject]@{
Number = $_.content.number Number = $_.number
Title = $_.content.title Title = $_.title
Order = if ($null -ne $_.order) { $_.order } else { 999999 } Order = $_.number # Issue-Nummer als Tiebreaker (aufsteigend = älteste zuerst)
Prio = $prio Prio = $prio
Type = $type Type = $type
} }

View file

@ -1,61 +1,62 @@
<# <#
.SYNOPSIS .SYNOPSIS
Setzt den Board-Status eines Issues im GitHub Project Board. Setzt den Status eines Issues in Forgejo via Label (status:todo / status:in-progress / status:done / status:backlog).
.DESCRIPTION .DESCRIPTION
Ermittelt die Item-ID des Issues im Board und setzt den Status Entfernt alle status:*-Labels und setzt das neue Status-Label.
auf den angegebenen Wert.
Akzeptierte Werte: "Todo", "InProgress" (oder "In Progress"), "Done", "Backlog". Akzeptierte Werte: "Todo", "InProgress" (oder "In Progress"), "Done", "Backlog".
.PARAMETER IssueNumber .PARAMETER IssueNumber
Die Issue-Nummer (z.B. 68). Die Issue-Nummer (z.B. 68).
.PARAMETER Status .PARAMETER Status
Der Zielstatus: "Todo", "InProgress", "In Progress" oder "Done". Der Zielstatus: "Todo", "InProgress", "In Progress", "Done" oder "Backlog".
#> #>
param( param(
[Parameter(Mandatory)][int]$IssueNumber, [Parameter(Mandatory)][int]$IssueNumber,
[Parameter(Mandatory)][string]$Status [Parameter(Mandatory)][string]$Status
) )
$projectId = "PVT_kwHOCFqiJ84BXk9U" $forgejoBase = "https://git.bollwerk.online/api/v1"
$statusFieldId = "PVTSSF_lAHOCFqiJ84BXk9UzhSw4es" $repo = "bollwerkadmin/bollwerk"
$tokenFile = Join-Path $PSScriptRoot "forgejo-token.txt"
$token = (Get-Content $tokenFile -Raw -ErrorAction Stop).Trim()
$headers = @{ Authorization = "token $token"; "Content-Type" = "application/json" }
# Normalisierung: "In Progress" → "InProgress" (robust gegen Agent-Varianten) # Normalisierung
$normalizedStatus = $Status.Trim() -replace '\s+', '' $normalizedStatus = $Status.Trim() -replace '\s+', ''
# "InProgress" nach Kleinschreibung angleichen an Map-Schlüssel
$statusMap = @{ $labelMap = @{
"Todo" = "f75ad846" "Todo" = "status:todo"
"InProgress" = "47fc9ee4" "InProgress" = "status:in-progress"
"Done" = "98236657" "Done" = "status:done"
"Backlog" = "4ce6ee37" "Backlog" = "status:backlog"
} }
if (-not $statusMap.ContainsKey($normalizedStatus)) { if (-not $labelMap.ContainsKey($normalizedStatus)) {
$valid = $statusMap.Keys -join ", " $valid = $labelMap.Keys -join ", "
Write-Error "Ungültiger Status '$Status'. Gültige Werte: $valid (auch 'In Progress' für InProgress, 'Backlog' für Backlog)." Write-Error "Ungültiger Status '$Status'. Gültige Werte: $valid (auch 'In Progress' für InProgress)."
exit 1 exit 1
} }
$optionId = $statusMap[$normalizedStatus] $newLabel = $labelMap[$normalizedStatus]
# Item-ID im Board finden # Alle Labels des Issues abrufen
$raw = gh project item-list 2 --owner jreinemann-euris --format json --limit 200 2>&1 $issueUrl = "$forgejoBase/repos/$repo/issues/$IssueNumber"
if ($LASTEXITCODE -ne 0) { $issue = (Invoke-WebRequest -Uri $issueUrl -Headers $headers -UseBasicParsing).Content | ConvertFrom-Json
Write-Error "Fehler beim Abrufen des Project Boards: $raw"
exit 1 # Alte status:*-Labels entfernen
$statusLabelIds = $issue.labels | Where-Object { $_.name -like "status:*" } | ForEach-Object { $_.id }
foreach ($id in $statusLabelIds) {
Invoke-WebRequest -Uri "$forgejoBase/repos/$repo/issues/$IssueNumber/labels/$id" -Method Delete -Headers $headers -UseBasicParsing | Out-Null
} }
$items = $raw | ConvertFrom-Json
$item = $items.items | Where-Object { $null -ne $_.content -and $_.content.number -eq $IssueNumber } | Select-Object -First 1
if (-not $item) { # Neues Status-Label-ID ermitteln
Write-Error "Issue #$IssueNumber nicht im Project Board gefunden. Bitte zuerst mit 'gh project item-add' hinzufügen." $allLabels = (Invoke-WebRequest -Uri "$forgejoBase/repos/$repo/labels?limit=50" -Headers $headers -UseBasicParsing).Content | ConvertFrom-Json
$targetLabel = $allLabels | Where-Object { $_.name -eq $newLabel } | Select-Object -First 1
if (-not $targetLabel) {
Write-Error "Label '$newLabel' nicht auf Forgejo gefunden. Bitte zuerst anlegen."
exit 1 exit 1
} }
$itemId = $item.id # Neues Status-Label setzen
$body = "{`"labels`":[" + $targetLabel.id + "]}"
Invoke-WebRequest -Uri "$forgejoBase/repos/$repo/issues/$IssueNumber/labels" -Method Post -Headers $headers -Body $body -UseBasicParsing | Out-Null
$editOutput = gh project item-edit --project-id $projectId --id $itemId --field-id $statusFieldId --single-select-option-id $optionId 2>&1 Write-Host "Issue #$IssueNumber$normalizedStatus"
if ($LASTEXITCODE -eq 0) {
Write-Host "Issue #$IssueNumber$normalizedStatus"
}
else {
Write-Error "Fehler beim Setzen des Status für Issue #${IssueNumber}: $editOutput"
exit 1
}

View file

@ -1,11 +1,11 @@
--- ---
name: ship name: ship
description: "CoPuPi-Workflow: Commit, Mac-Build-Test, Push, GitHub-Pipeline beobachten. Verwende diesen Skill für alles rund um das Ausliefern von Code git push, Pipeline-Status, CI-Fehler beheben, Version hochzählen. Trigger-Phrasen: 'ship', 'push', 'pipeline', 'CI', 'deployen', 'ausliefern', 'version bump'." description: "CoPuPi-Workflow: Commit, Mac-Build-Test, Push zu Forgejo. Verwende diesen Skill für alles rund um das Ausliefern von Code git push, Version hochzählen. Trigger-Phrasen: 'ship', 'push', 'pipeline', 'CI', 'deployen', 'ausliefern', 'version bump'."
--- ---
# Skill: Ship (CoPuPi-Workflow) # Skill: Ship (CoPuPi-Workflow)
Dieser Skill kapselt Tools und Konventionen für den Ship-Workflow den Weg vom lokalen Commit bis zur grünen CI-Pipeline. Dieser Skill kapselt Tools und Konventionen für den Ship-Workflow den Weg vom lokalen Commit bis zum Push nach Forgejo (`git.bollwerk.online`).
Der vollständige Workflow wird über `.github/prompts/ship.prompt.md` gesteuert. Der vollständige Workflow wird über `.github/prompts/ship.prompt.md` gesteuert.
@ -15,7 +15,7 @@ Der vollständige Workflow wird über `.github/prompts/ship.prompt.md` gesteuert
### `watch-pipeline.ps1` ### `watch-pipeline.ps1`
Beobachtet den neuesten GitHub Actions Run bis zum Abschluss. Gibt Status, Dauer und Warnungen aus. Beobachtet den neuesten Forgejo Actions Run bis zum Abschluss. Gibt Status, Dauer und Warnungen aus.
**Aufruf:** **Aufruf:**

View file

@ -216,6 +216,6 @@ Die SQLite-Datenbank wird unter `/opt/bollwerk/data/` auf dem Host gemountet und
- **1 GB RAM:** JVM-Heap auf 384 MB begrenzt. Kein Spielraum für weitere Dienste. - **1 GB RAM:** JVM-Heap auf 384 MB begrenzt. Kein Spielraum für weitere Dienste.
- **Kein HTTPS:** Server läuft aktuell nur auf HTTP Port 8080. Für HTTPS → Caddy als Reverse Proxy einrichten. - **Kein HTTPS:** Server läuft aktuell nur auf HTTP Port 8080. Für HTTPS → Caddy als Reverse Proxy einrichten.
- **Kein CI/CD:** Deployment ist manuell (JAR bauen → scp → docker compose up). Ggf. GitHub Actions Pipeline ergänzen. - **Kein CI/CD:** Deployment ist manuell (JAR bauen → scp → docker compose up). Ggf. Forgejo Actions Pipeline ergänzen.
- **Dockerfile lokal:** Das Dockerfile auf dem VPS (`/opt/bollwerk/Dockerfile`) ist ein schlankes Runtime-Only-Image. Das Multi-Stage-Dockerfile im Repo-Root ist für lokale Builds gedacht. - **Dockerfile lokal:** Das Dockerfile auf dem VPS (`/opt/bollwerk/Dockerfile`) ist ein schlankes Runtime-Only-Image. Das Multi-Stage-Dockerfile im Repo-Root ist für lokale Builds gedacht.
- **SSH-Escape-Problem:** Beim Schreiben von Dateien via SSH-Heredoc werden JSON-Quotes zerstört. Dateien immer lokal erstellen und per `scp` hochladen. - **SSH-Escape-Problem:** Beim Schreiben von Dateien via SSH-Heredoc werden JSON-Quotes zerstört. Dateien immer lokal erstellen und per `scp` hochladen.