一键导入
sentry
Triage open Sentry issues across zng-app, zng-admin, zng-biller. Buckets by ACT/WATCH/REVIEW/CLOSE with delta tracking from a local snapshot.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Triage open Sentry issues across zng-app, zng-admin, zng-biller. Buckets by ACT/WATCH/REVIEW/CLOSE with delta tracking from a local snapshot.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Triggers on /clockify-reconciliator <project> only. Adds descriptions to description-less Clockify entries for a configured project, splitting large blocks into 1-3h chunks using git commits from configured repos.
Triggers on /work-recap only. Dispatches to a named recap variant (e.g. zirtue weekly, zirtue daily). Each variant lives in a subfolder under this skill.
Triggers on /schedule-once only. Schedules a SINGLE future run of a Claude prompt (or a raw shell command) on THIS machine via Windows Task Scheduler, as a self-deleting one-time task. The local, one-shot counterpart to the cloud /schedule and the recurring /cron-run. PC must be on and logged in at fire time; Claude Code itself need not be open. Also supports list and cancel.
Triggers on /create-pr only. Drafts a human-light PR for the current branch, scales the body to the diff, suggests visuals, previews locally, and creates it on approval.
Triggers on /autopilot only. Dev is AFK and wants maximum autonomous progress with heavy but purposeful token use. Never block - delegate aggressively to subagents to keep main context lean, resolve real judgment calls via a BOUNDED /iterate-it (capped per run), auto-answer any nested skill's question instead of hanging, log decisions, park true hard-stops, and grind the task to a verified finish.
Triggers on /batch-todos only. Dedupes ai_todos, classifies survivors as EASY (auto-execute) or HARD (dev picks), shows dry-run confirmation, batches all EASY todos, then surfaces the HARD queue.
| name | sentry |
| description | Triage open Sentry issues across zng-app, zng-admin, zng-biller. Buckets by ACT/WATCH/REVIEW/CLOSE with delta tracking from a local snapshot. |
Triage open Sentry issues across all 3 ZNG projects.
/sentry — full triage run (described below)/sentry --issue <id> — drill into one issueRun:
& "$env:USERPROFILE\.claude\scripts\Get-SentryIssue.ps1" <id> -Org zirtue-nk
Run the PowerShell script below verbatim using the PowerShell tool. Do not regenerate it.
Config:
$env:USERPROFILE\.claude\.sentry_snapshot.json$env:SENTRY_AUTH_TOKEN (User scope)zirtue-nkzng-app, zng-admin, zng-biller$ErrorActionPreference = 'Stop'
$token = [Environment]::GetEnvironmentVariable('SENTRY_AUTH_TOKEN', 'User')
if (-not $token) { $token = $env:SENTRY_AUTH_TOKEN }
if (-not $token) { Write-Error 'SENTRY_AUTH_TOKEN not set'; exit 1 }
$headers = @{ Authorization = "Bearer $token" }
$base = 'https://sentry.io/api/0'
$org = 'zirtue-nk'
$targets = @('zng-app', 'zng-admin', 'zng-biller')
# --- Slug discovery ---
$allProjects = Invoke-RestMethod -Uri "$base/projects/" -Headers $headers
$matched = @($allProjects | Where-Object { $targets -contains $_.slug })
if ($matched.Count -eq 0) {
$found = ($allProjects | ForEach-Object { $_.slug }) -join ', '
Write-Error "No projects matched $($targets -join ', '). Found: $found"
exit 1
}
$slugs = $matched | ForEach-Object { $_.slug }
Write-Host "Fetching: $($slugs -join ', ')"
# --- Load snapshot ---
$snapshotPath = "$env:USERPROFILE\.claude\.sentry_snapshot.json"
$snapshot = @{}
$snapshotAge = $null
if (Test-Path $snapshotPath) {
$mtime = (Get-Item $snapshotPath).LastWriteTime
$snapshotAge = [int](((Get-Date) - $mtime).TotalHours)
$raw = Get-Content $snapshotPath -Raw | ConvertFrom-Json
$raw.PSObject.Properties | ForEach-Object { $snapshot[$_.Name] = [int]$_.Value }
}
if ($snapshotAge -gt 36) {
Write-Host "SNAPSHOT STALE (${snapshotAge}h) - delta values unreliable" -ForegroundColor Yellow
Write-Host ''
}
# --- Fetch issues ---
$now = Get-Date
$issues = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($slug in $slugs) {
try {
$page = Invoke-RestMethod -Uri "$base/projects/$org/$slug/issues/?query=is:unresolved&sort=date&limit=100" -Headers $headers
} catch {
Write-Host "WARN: $slug - $($_.Exception.Message)" -ForegroundColor Yellow
continue
}
foreach ($iss in $page) {
$evts = [int]$iss.count
$delta = if ($snapshot.ContainsKey($iss.id)) { $evts - $snapshot[$iss.id] } else { $null }
$fs = [datetime]$iss.firstSeen
$ls = [datetime]$iss.lastSeen
$issues.Add([PSCustomObject]@{
id = $iss.id
title = $iss.title
events = $evts
delta = $delta
users = [int]$iss.userCount
firstSeen = $fs
lastSeen = $ls
project = $slug
url = $iss.permalink
isNew = ($fs -gt $now.AddHours(-24))
isRegr = ($fs -gt $now.AddDays(-7) -and $fs -le $now.AddHours(-24))
})
}
}
# --- Bucket ---
$threshold30d = $now.AddDays(-30)
$closeList = [System.Collections.Generic.List[PSCustomObject]]::new()
$actList = [System.Collections.Generic.List[PSCustomObject]]::new()
$watchList = [System.Collections.Generic.List[PSCustomObject]]::new()
$reviewList = [System.Collections.Generic.List[PSCustomObject]]::new()
$closeIds = @{}
foreach ($iss in $issues) {
if ($iss.events -lt 10 -and $iss.lastSeen -lt $threshold30d -and $iss.users -lt 3) {
$closeList.Add($iss)
$closeIds[$iss.id] = $true
}
}
$remaining = $issues | Where-Object { -not $closeIds.ContainsKey($_.id) }
foreach ($grp in ($remaining | Group-Object project)) {
$sorted = @($grp.Group | Sort-Object events)
if ($sorted.Count -lt 5) {
$sorted | ForEach-Object { $reviewList.Add($_) }
continue
}
$median = $sorted[[int]($sorted.Count / 2)].events
$q3 = $sorted[[int]($sorted.Count * 0.75)].events
foreach ($iss in $sorted) {
if ($iss.events -ge $q3) { $actList.Add($iss) }
elseif ($iss.events -ge $median) { $watchList.Add($iss) }
else { $reviewList.Add($iss) }
}
}
# --- Output ---
function Write-Bucket ($Name, $Items, $Color) {
if (-not $Items -or @($Items).Count -eq 0) { return }
$sorted = @($Items | Sort-Object events -Descending)
Write-Host ''
Write-Host "=== $Name ($($sorted.Count)) ===" -ForegroundColor $Color
foreach ($iss in $sorted) {
$d = if ($null -ne $iss.delta) { if ($iss.delta -ge 0) { "+$($iss.delta)" } else { "$($iss.delta)" } } else { 'new' }
$flags = if ($iss.isNew) { ' [NEW]' } elseif ($iss.isRegr) { ' [REGRESSION]' } else { '' }
$proj = $iss.project -replace '^zng-', ''
$fs = $iss.firstSeen.ToString('MM/dd')
$ls = $iss.lastSeen.ToString('MM/dd')
Write-Host (' {0,5} {1,6} u:{2,-3} [{3}] {4}->{5}{6} {7}' -f $iss.events, $d, $iss.users, $proj, $fs, $ls, $flags, $iss.title)
Write-Host " $($iss.url)" -ForegroundColor DarkGray
}
}
Write-Bucket 'ACT' $actList 'Red'
Write-Bucket 'WATCH' $watchList 'Yellow'
Write-Bucket 'REVIEW' $reviewList 'Cyan'
Write-Bucket 'CLOSE' $closeList 'DarkGray'
Write-Host ''
Write-Host "Total: $($issues.Count) issues across $($slugs.Count) projects" -ForegroundColor DarkGray
# --- Atomic snapshot save ---
$newSnap = @{}
foreach ($iss in $issues) { $newSnap[$iss.id] = $iss.events }
$tmpPath = "$snapshotPath.tmp"
$newSnap | ConvertTo-Json | Out-File -FilePath $tmpPath -Encoding utf8
Move-Item -Path $tmpPath -Destination $snapshotPath -Force
One-line summary: ACT: X | WATCH: X | REVIEW: X | CLOSE: X.
Then analyze the results and present options via AskUserQuestion:
Example option labels: