Migrate all repos into monorepo context folders

Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
@@ -0,0 +1,138 @@
<#
.SYNOPSIS
Findet das aelteste und neueste Ticket pro Person (Tenure-Analyse)
.NOTES
BULK-Ansatz: Nur 2-3 API-Calls statt 70+
Holt ALLE Issues der relevanten Projekte und gruppiert lokal nach Assignee
#>
$JiraUrl = "https://arija.jaas.service.deutschebahn.com"
$ApiBase = "$JiraUrl/rest"
$OutputDir = "project-audit\data\jira-bugs"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Write-Host "============================================================"
Write-Host " Tenure-Analyse (Bulk-Abfrage)"
Write-Host " Strategie: Alle Issues holen, lokal nach Assignee gruppieren"
Write-Host "============================================================"
$Token = $null
$SecretsPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "..\.secrets"
if (Test-Path $SecretsPath) {
Get-Content $SecretsPath | ForEach-Object {
if ($_ -match "^JIRA_TOKEN=(.+)$") {
$val = $Matches[1].Trim()
if ($val -ne "HIER_TOKEN_EINTRAGEN") { $Token = $val }
}
}
}
if (-not $Token) { $Token = Read-Host "Jira PAT" }
$Headers = @{ "Authorization" = "Bearer $Token"; "Content-Type" = "application/json" }
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
function Invoke-JiraBulk {
param([string]$Jql, [int]$MaxTotal = 5000)
$AllIssues = @()
$StartAt = 0
$PageSize = 1000
do {
$Uri = "$ApiBase/api/2/search?jql=$([Uri]::EscapeDataString($Jql))&startAt=$StartAt&maxResults=$PageSize&fields=assignee,created,updated,project"
Write-Host " Lade Issues $StartAt - $($StartAt + $PageSize)..." -NoNewline
try {
$R = Invoke-WebRequest -Uri $Uri -Headers $Headers -UseBasicParsing -ErrorAction Stop
$Data = $R.Content | ConvertFrom-Json
$AllIssues += $Data.issues
$Total = $Data.total
Write-Host " $($AllIssues.Count)/$Total" -ForegroundColor Green
$StartAt += $PageSize
Start-Sleep -Seconds 2
} catch {
$StatusCode = 0
if ($_.Exception.Response) { $StatusCode = [int]$_.Exception.Response.StatusCode }
if ($StatusCode -eq 429) {
Write-Host " [429 - warte 30s]" -ForegroundColor Yellow
Start-Sleep -Seconds 30
} else {
Write-Host " FEHLER: $($_.Exception.Message)" -ForegroundColor Red
break
}
}
} while ($AllIssues.Count -lt $Total -and $AllIssues.Count -lt $MaxTotal)
return $AllIssues
}
# EINE Bulk-Abfrage: Alle Issues aus allen Team-Projekten
$Projects = "O2C404, O2CCIB, O2CZERO, O2COS, O2CDEVOPS, O2CSYS, O2CBS, O2CQST, TTTI"
$Jql = "project in ($Projects) ORDER BY created ASC"
Write-Host ""
Write-Host ">> Bulk-Abfrage: Alle Issues aus $Projects" -ForegroundColor Yellow
$AllIssues = Invoke-JiraBulk -Jql $Jql -MaxTotal 25000
Write-Host ""
Write-Host " Gesamt: $($AllIssues.Count) Issues geladen" -ForegroundColor Cyan
# Lokal nach Assignee gruppieren
Write-Host ""
Write-Host ">> Gruppiere nach Assignee..." -ForegroundColor Yellow
$ByAssignee = @{}
foreach ($Issue in $AllIssues) {
if ($Issue.fields.assignee) {
$Name = $Issue.fields.assignee.displayName
if (-not $ByAssignee[$Name]) {
$ByAssignee[$Name] = @{ Issues = @(); FirstCreated = $null; LastUpdated = $null }
}
$Created = $Issue.fields.created.Substring(0, 10)
$Updated = $Issue.fields.updated.Substring(0, 10)
$ByAssignee[$Name].Issues += $Issue.key
if (-not $ByAssignee[$Name].FirstCreated -or $Created -lt $ByAssignee[$Name].FirstCreated) {
$ByAssignee[$Name].FirstCreated = $Created
$ByAssignee[$Name].FirstTicket = $Issue.key
$ByAssignee[$Name].FirstProject = $Issue.fields.project.key
}
if (-not $ByAssignee[$Name].LastUpdated -or $Updated -gt $ByAssignee[$Name].LastUpdated) {
$ByAssignee[$Name].LastUpdated = $Updated
$ByAssignee[$Name].LastTicket = $Issue.key
}
}
}
Write-Host " $($ByAssignee.Count) verschiedene Assignees gefunden"
# Ergebnisse aufbereiten
$Results = @()
foreach ($Entry in ($ByAssignee.GetEnumerator() | Sort-Object { $_.Value.FirstCreated })) {
$Name = $Entry.Key
$Data = $Entry.Value
$DaysAgo = ((Get-Date) - [DateTime]::Parse($Data.LastUpdated)).Days
$Status = if ($DaysAgo -le 30) { "Aktiv" } elseif ($DaysAgo -le 90) { "Wenig aktiv" } else { "INAKTIV ($DaysAgo Tage)" }
$Results += [PSCustomObject]@{
Person = $Name
FirstTicket = $Data.FirstTicket
FirstDate = $Data.FirstCreated
LastDate = $Data.LastUpdated
LastTicket = $Data.LastTicket
Status = $Status
Project = $Data.FirstProject
IssueCount = $Data.Issues.Count
}
}
# CSV speichern
$Results | Export-Csv "$OutputDir\tenure.csv" -Delimiter ";" -Encoding UTF8 -NoTypeInformation
# Ausgabe
Write-Host ""
Write-Host "=== Sortiert nach Startdatum (Top 40) ==="
$Results | Select-Object -First 40 | ForEach-Object {
$Color = if ($_.Status -eq "Aktiv") { "Green" } elseif ($_.Status -match "INAKTIV") { "Red" } else { "Yellow" }
Write-Host (" {0,-35} {1} bis {2} [{3}] ({4} Issues)" -f $_.Person, $_.FirstDate, $_.LastDate, $_.Status, $_.IssueCount) -ForegroundColor $Color
}
Write-Host ""
Write-Host "FERTIG! $($Results.Count) Personen in $OutputDir\tenure.csv" -ForegroundColor Green
Write-Host "API-Calls: ~$([Math]::Ceiling($AllIssues.Count / 1000) + 1) (statt $($ByAssignee.Count * 2))" -ForegroundColor Cyan