diff --git a/.claude/skills/instruction-drift/scripts/Check-Staleness.ps1 b/.claude/skills/instruction-drift/scripts/Check-Staleness.ps1 index b06cafd457..efb2993c00 100644 --- a/.claude/skills/instruction-drift/scripts/Check-Staleness.ps1 +++ b/.claude/skills/instruction-drift/scripts/Check-Staleness.ps1 @@ -127,8 +127,8 @@ function Get-GitHubLatestRelease { } } $release = $json | ConvertFrom-Json - # Truncate release notes to first 2000 chars to keep report manageable - $body = if ($release.body.Length -gt 2000) { $release.body.Substring(0, 2000) + '...' } else { $release.body } + # Truncate release notes to first 5000 chars + $body = if ($release.body.Length -gt 5000) { $release.body.Substring(0, 5000) + '...' } else { $release.body } return @{ status = 'ok' repo = $Repo @@ -149,6 +149,50 @@ function Get-GitHubLatestRelease { } } +function Get-GitHubReleasesSince { + param([string]$Repo, [string]$SinceTag) + + try { + # Fetch recent releases (up to 10) + $json = gh api "repos/$Repo/releases?per_page=10" --jq '[.[] | {tag_name: .tag_name, name: .name, published_at: .published_at, body: .body}]' 2>&1 + if ($LASTEXITCODE -ne 0) { + return @{ + status = 'error' + repo = $Repo + error = "Failed to fetch releases: $json" + } + } + if (-not $json -or $json -eq 'null' -or $json -eq '[]') { + return @{ status = 'ok'; repo = $Repo; releases = @() } + } + $allReleases = $json | ConvertFrom-Json + # Collect releases newer than $SinceTag + $newReleases = @() + foreach ($r in $allReleases) { + if ($r.tag_name -eq $SinceTag) { break } + $body = if ($r.body.Length -gt 5000) { $r.body.Substring(0, 5000) + '...' } else { $r.body } + $newReleases += @{ + tag = $r.tag_name + name = $r.name + published_at = $r.published_at + release_notes = $body + } + } + return @{ + status = 'ok' + repo = $Repo + releases = $newReleases + } + } + catch { + return @{ + status = 'error' + repo = $Repo + error = $_.Exception.Message + } + } +} + function Get-IndexPageLinks { <# .SYNOPSIS @@ -372,6 +416,11 @@ function ConvertFrom-SyncManifest { $currentItem.resolution_expected = $Matches[1].Trim() -eq 'true' } } + elseif ($trimmed -match '^last_reviewed_release:\s*(.+)$') { + if ($currentItem) { + $currentItem.last_reviewed_release = $Matches[1].Trim() + } + } elseif ($trimmed -match '^coverage_gaps:') { if ($currentItem) { $currentItem.coverage_gaps = @() @@ -533,21 +582,45 @@ foreach ($manifestPath in $manifests) { } 'releases' { Write-Host " šŸ“¦ Checking releases for $($source.repo)..." -NoNewline - $result = Get-GitHubLatestRelease -Repo $source.repo - if ($result.status -eq 'ok' -and $result.latest) { - Write-Host " āœ… latest=$($result.latest.tag)" -ForegroundColor Green - } - elseif ($result.status -eq 'ok') { - Write-Host " ā„¹ļø No releases found" -ForegroundColor Yellow + $sinceTag = if ($source.ContainsKey('last_reviewed_release')) { $source.last_reviewed_release } else { $null } + if ($sinceTag) { + $result = Get-GitHubReleasesSince -Repo $source.repo -SinceTag $sinceTag + if ($result.status -eq 'ok' -and $result.releases.Count -gt 0) { + Write-Host " šŸ†• $($result.releases.Count) new release(s) since $sinceTag" -ForegroundColor Yellow + } + elseif ($result.status -eq 'ok') { + Write-Host " āœ… up to date (last reviewed: $sinceTag)" -ForegroundColor Green + } + else { + Write-Host " āŒ Error: $($result.error)" -ForegroundColor Red + } } else { - Write-Host " āŒ Error: $($result.error)" -ForegroundColor Red + # No last_reviewed_release — fall back to latest-only + $singleResult = Get-GitHubLatestRelease -Repo $source.repo + $result = @{ + status = $singleResult.status + repo = $source.repo + releases = if ($singleResult.status -eq 'ok' -and $singleResult.latest) { @($singleResult.latest) } else { @() } + error = $singleResult.error + } + if ($result.status -eq 'ok' -and $result.releases.Count -gt 0) { + Write-Host " āœ… latest=$($result.releases[0].tag) (no last_reviewed_release set — add to manifest)" -ForegroundColor Yellow + } + elseif ($result.status -eq 'ok') { + Write-Host " ā„¹ļø No releases found" -ForegroundColor Yellow + } + else { + Write-Host " āŒ Error: $($result.error)" -ForegroundColor Red + } } - $sourceResults += @{ - type = 'releases' - repo = $source.repo - result = $result + $entry = @{ + type = 'releases' + repo = $source.repo + last_reviewed_release = $sinceTag + result = $result } + $sourceResults += $entry } } } @@ -632,16 +705,20 @@ foreach ($manifestPath in $manifests) { # - closed issues where resolution_expected is true (instruction may reference outdated workarounds) # - untracked pages discovered via index crawling # - untracked recently closed issues +# - new releases since last_reviewed_release $actionableChanges = $results | ForEach-Object { $_.sources } | Where-Object { $_.result.status -eq 'error' -or ($_.type -eq 'issue' -and $_.resolution_expected -and $_.result.status -eq 'ok' -and $_.result.state -eq 'closed') } +$hasNewReleases = ($results | ForEach-Object { $_.sources } | Where-Object { + $_.type -eq 'releases' -and $_.result.status -eq 'ok' -and $_.result.releases.Count -gt 0 -and $_.last_reviewed_release +} | Measure-Object).Count -gt 0 $hasUntrackedPages = ($results | Where-Object { $_.untracked_pages.Count -gt 0 } | Measure-Object).Count -gt 0 $hasUntrackedIssues = ($results | Where-Object { $_.untracked_closed_issues.Count -gt 0 } | Measure-Object).Count -gt 0 $report = @{ checked_at = (Get-Date -Format 'o') manifests = $results - changes_detected = (($actionableChanges | Measure-Object).Count -gt 0) -or $hasUntrackedPages -or $hasUntrackedIssues + changes_detected = (($actionableChanges | Measure-Object).Count -gt 0) -or $hasUntrackedPages -or $hasUntrackedIssues -or $hasNewReleases } Write-Host "`nšŸ“Š Report:" -ForegroundColor Cyan diff --git a/.github/instructions/gh-aw-workflows.sync.yaml b/.github/instructions/gh-aw-workflows.sync.yaml index 7039804576..ec5b163523 100644 --- a/.github/instructions/gh-aw-workflows.sync.yaml +++ b/.github/instructions/gh-aw-workflows.sync.yaml @@ -84,6 +84,7 @@ sources: # GitHub releases — check for new versions - releases: github/gh-aw + last_reviewed_release: v0.69.3 style: | Match existing section structure. Use tables for feature comparisons. diff --git a/.github/workflows/instruction-drift.agent.md b/.github/workflows/instruction-drift.agent.md index e2cfe39a3e..395b3dea73 100644 --- a/.github/workflows/instruction-drift.agent.md +++ b/.github/workflows/instruction-drift.agent.md @@ -121,8 +121,9 @@ Read the staleness report above. For each signal, read the affected skill files ### Rules 1. **Respect `divergence` sections** — NEVER remove: "Security Boundaries", "Safe Pattern: Checkout + Restore", "Common Patterns" 2. **Classify P0-P3:** P0=factually wrong, P1=security, P2=new features, P3=nice-to-have -3. **Only auto-fix P0 and P1.** Note P2 in PR description. Skip P3. -4. **Update sync manifest** — `resolution_expected`, `last_reviewed`, new issues +3. **Auto-fix P0, P1, AND P2.** Only skip P3 (cosmetic/nice-to-have). +4. **For new releases:** Read ALL release notes in the report. For each new feature, check if our skill covers it. Add new features to the anti-patterns table, trigger guide, or security patterns as appropriate. Update the `last_reviewed_release` field in `gh-aw-workflows.sync.yaml` to the latest tag after processing. +5. **Update sync manifest** — `resolution_expected`, `last_reviewed_release`, new issues Commit each change: ```bash