feat(reports): count archived repos on ADO (#74) + cross-platform scan summary#96
Conversation
ADO archived handling was inert: listReposForProject never detected archived/disabled repos, and a variable-shadowing bug in getRepoAnalyse silently lost both the empty and archived counts. Azure DevOps has no "archived" repo state and the pinned SDK drops isDisabled, so detect disabled repos via a raw REST call (graceful on error) and count/skip them. Also close the equivalent reporting gap in Bitbucket DC, which skipped archived repos without counting them. Add a cross-platform scan summary (Scanned / Analyzed / Archived / Empty / Excluded / Skipped) persisted to Results/config/analysis_summary.json and rendered in the ResultsAll web page and the global PDF report. Mirrors the existing skipped-repos persistence/render pattern; missing file degrades to nil so older result sets still render. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address the Gitar review findings on the scan-summary PR: - fetchDisabledRepoIDs bounded its request with context.Background() and the shared HTTP client, which has no request timeout — a stalled Azure endpoint could hang the whole scan. Wrap the call in a 30s context deadline so it degrades gracefully as intended. - The Azure summary derived Scanned from len(importantBranches), so repos dropped inside the analysis loop (no usable default branch, or single-branch selection) fell into no category and understated the true total. Add a Skipped bucket (discovery-phase drops) to ScanSummary; Scanned now reconciles as Analyzed + Archived + Empty + Excluded + Skipped, and the reports fold the discovery-phase skips together with the analysis-phase skips. Also consolidate the per-platform summary persistence behind utils.NewScanSummary + utils.PersistScanSummary (removing duplicated struct literals and the repeated error-log string), and add tests covering the disabled-repo detection, listReposForProject filtering, fetchAllRepos archived/excluded counting, the reconciliation math, and the render helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
| func buildScanSummaryView(summary *utils.ScanSummary, skippedCount int) *ScanSummaryView { | ||
| if summary == nil { | ||
| return nil | ||
| } | ||
| analyzed := summary.Analyzed - skippedCount | ||
| if analyzed < 0 { | ||
| analyzed = 0 | ||
| } | ||
| return &ScanSummaryView{ | ||
| Scanned: summary.Scanned, | ||
| Analyzed: analyzed, | ||
| Archived: summary.Archived, | ||
| Empty: summary.Empty, | ||
| Excluded: summary.Excluded, | ||
| Skipped: summary.Skipped + skippedCount, |
There was a problem hiding this comment.
💡 Edge Case: Scan summary row fails to reconcile when Analyzed floors at 0
The stated invariant for the scan summary is Scanned = Analyzed + Archived + Empty + Excluded + Skipped. In both buildScanSummaryView (ResultsAll.go:955-971) and renderScanSummarySection (pkg/utils/globalreport.go:337-355) the displayed Analyzed is computed as summary.Analyzed - skippedCount and floored at 0, while displayed Skipped is summary.Skipped + skippedCount and Scanned is passed through unchanged.
When skippedCount > summary.Analyzed (more analysis-phase failures than projected analyzed repos), Analyzed is clamped to 0 but the full skippedCount is still added into the Skipped column and Scanned is unchanged. The row then over-sums by skippedCount - summary.Analyzed and no longer reconciles — contradicting the invariant the reconciliation test (scan_summary_test.go:36-38) asserts for the normal case.
This is a low-probability edge (skippedCount normally cannot exceed the projected analyzed count) and only affects display totals, hence minor. Consider clamping the amount folded into Skipped to what was actually subtracted from Analyzed so the row always balances.
Only fold the portion of skippedCount that was actually subtracted from Analyzed into the displayed Skipped, keeping the row balanced when analysis failures exceed projected analyzed repos.:
used := skippedCount
if used > summary.Analyzed {
used = summary.Analyzed
}
analyzed := summary.Analyzed - used
// ...
Skipped: summary.Skipped + used,
Was this helpful? React with 👍 / 👎
Code Review 👍 Approved with suggestions 2 resolved / 3 findingsImplements repository accounting for ADO and Bitbucket DC alongside a new cross-platform scan summary feature. Address the reconciliation invariant when Analyzed counts floor at zero to ensure consistent scan breakdown reporting. 💡 Edge Case: Scan summary row fails to reconcile when Analyzed floors at 0📄 ResultsAll.go:955-969 📄 pkg/utils/globalreport.go:337-351 The stated invariant for the scan summary is When This is a low-probability edge (skippedCount normally cannot exceed the projected analyzed count) and only affects display totals, hence minor. Consider clamping the amount folded into Skipped to what was actually subtracted from Analyzed so the row always balances. Only fold the portion of skippedCount that was actually subtracted from Analyzed into the displayed Skipped, keeping the row balanced when analysis failures exceed projected analyzed repos.✅ 2 resolved✅ Bug: Disabled-repo REST call can hang the scan indefinitely
✅ Edge Case: Azure Analyzed count omits repos skipped mid-loop, breaking reconciliation
🤖 Prompt for agentsOptionsAuto-apply is off → Gitar will not commit updates to this branch. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |


Closes #74.
ADO archived repos (#74)
The archived-repo handling in
getazurewas inert:listReposForProjectnever detected archived/disabled repos (archivedCountwas declared but never incremented).getRepoAnalyse(a loop-scoped:=re-declaringemptyRepos) meant both the empty and archived counts were silently lost and always returned 0;emptyOrArchivedCountwas misnamed dead code.Azure DevOps has no "archived" repo state — the equivalent is a disabled repo (no default branch, uncloneable) — and the pinned SDK (
azure-devops-go-api v1.0.0-b5) dropsisDisabledfromGitRepository. So disabled repos are now detected via a raw REST call (GET …/git/repositories?api-version=6.0, PAT basic auth, sharedutils.HTTPClient) that degrades gracefully (logs a warning, treats none as disabled) on permission/network errors so a scan never fails. Disabled repos are counted and skipped, and the empty/archived accounting is corrected.Other platforms
GitHub, Bitbucket Cloud, and GitLab already detected + counted archived correctly. Bitbucket DC skipped archived repos without counting them — added an archived/excluded counter to
fetchAllReposto close that reporting gap.Cross-platform Scan Summary
Per-run repository breakdown — Scanned / Analyzed / Archived / Empty / Excluded / Skipped — surfaced in the ResultsAll web page and the global PDF report, for all platforms.
pkg/utils/summary.gomirrors the existingskipped.gopersistence pattern:ScanSummary→Results/config/analysis_summary.json, withScannedderived so the breakdown always reconciles (Scanned = Analyzed + Archived + Empty + Excluded + Skipped).nil, so older result sets still render./api/scan-summaryendpoint added alongside the existing API handlers.Analyzedis adjusted down by analysis-phase failures, which surface asSkipped.Tests
pkg/utils/summary_test.go: save/load round-trip,Scannedderivation, missing/invalid file, PDF section render (nil/zero/populated).pkg/devops/getazure/getazure_disabled_test.go:isDisabledparse + graceful non-200/bad-JSON fallback.pkg/devops/getbitbucketdc:fetchAllReposarchived/excluded counts.scan_summary_test.go:buildScanSummaryViewlogic + template renders/omits.All builds (
./...,-tags webui,-tags resultsall) and the full test suite pass. A live ADO scan against a real disabled repo was not run (needs org credentials); the REST parse, counting, and rendering are covered by unit tests.🤖 Generated with Claude Code