Skip to content

feat(merge-retrospective): compute Gate-Preventable Repair Rate (GPRR) from retrospective issues - #731

Merged
tvna merged 3 commits into
mainfrom
claude/gitapex-pr-726-g0fv9f
Aug 4, 2026
Merged

feat(merge-retrospective): compute Gate-Preventable Repair Rate (GPRR) from retrospective issues#731
tvna merged 3 commits into
mainfrom
claude/gitapex-pr-726-g0fv9f

Conversation

@tvna

@tvna tvna commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Summary

Adds .github/scripts/compute_gprr.py, which parses every label:retrospective issue's Status: tag(s), buckets by ISO week, and reports the missing-deterministic-gate share of classified repairs and of merged PRs per week -- turning the narrative Status classification skills/merge-retrospective/SKILL.md already mandates into an inspectable numeric time series, per issue #726.

Facts

  • scan_retrospective_gate_drift.py is refactored to expose list_labelled_issue_records (full issue records) and a renamed public fetch_json_page; list_labelled_issues becomes a thin wrapper over it. compute_gprr.py reuses both instead of a second hand-rolled GitHub client, per the issue's own constraint. All 51 pre-existing/added tests for that file pass; measured coverage 97.6% (floor: 90%).
  • compute_gprr.py is new, stdlib-only, 130 statements, 32 tests, 99.2% measured coverage.
  • Full repo test suite (after the review-fix commit below): uv run pytest -- 2487 passed. uv run ruff check . -- clean. uv run mypy (the tests + pythonpath-linked roots group, which covers .github/scripts) -- clean, 113 source files. gate_evals_scripts_coverage.py -- all 47 in-scope files pass the 90% floor, including the three changed/added scripts.
  • Ran the script live against this repository (GITHUB_TOKEN already present in this session) rather than only against fixtures: python3 .github/scripts/compute_gprr.py --owner tvna --repo gitapex returned, as of 2026-08-04:
    Gate-Preventable Repair Rate (GPRR), all-time: 207 of 278 classified 'retrospective' repairs were missing-deterministic-gate (74.5% of classified repairs, 93.7% of 221 merged PRs).
    151 carried-forward gate mention(s) reported (excluded from the GPRR ratio above).
    
    Weekly breakdown:
      2026-W28: missing-deterministic-gate=0 of 0 classified (n/a), 13 merged PR(s) (0.0%), carried-forward=0
      2026-W29: missing-deterministic-gate=0 of 0 classified (n/a), 52 merged PR(s) (0.0%), carried-forward=0
      2026-W30: missing-deterministic-gate=17 of 27 classified (63.0%), 68 merged PR(s) (25.0%), carried-forward=3
      2026-W31: missing-deterministic-gate=152 of 198 classified (76.8%), 73 merged PR(s) (208.2%), carried-forward=102
      2026-W32: missing-deterministic-gate=38 of 53 classified (71.7%), 15 merged PR(s) (253.3%), carried-forward=46
    
    This is the live-proof run CLAUDE.md section 1 requires (real artifacts, real GitHub API, not only fixture assertions), and it independently corroborates the issue's own chore(retrospective): merge retrospective for PR #710 #714 reference point (146/149 uncited) at the same order of magnitude via a different metric (~75% of classified repairs vs. that check's ~98% uncited).
  • A follow-up commit documents (design doc + inline) why gate_share_of_merged_prs legitimately exceeds 100% in some weeks (2026-W31, 2026-W32 above) -- a single retrospective issue can enumerate several repairs, and merge-retrospective files one issue per merged PR, not one repair per PR.
  • A code-review pass surfaced a real architecture issue: the first version had compute_gprr.py import scan_retrospective_gate_drift.py directly for the low-level HTTP client, breaking this repo's .github/scripts/*.py independence convention. Fixed by extracting the generic paginated-fetch-with-retry client (fetch_json_page, GitHubApiError, default_opener) into a new shared .github/scripts/_github_http.py; both scripts depend on it, neither on the other, for that layer (compute_gprr.py still imports scan_retrospective_gate_drift for list_labelled_issue_records specifically, which is issue-specific business logic the issue's own constraint named as the thing to reuse). Same pass also found and fixed: the new GPRR workflow step was silently skipped whenever the citation-drift step above it failed on threshold (added if: ${{ !cancelled() }}, since GPRR is meant to report regardless of that check's own outcome); an HTTP-200-with-unparseable-body response would have raised an uncaught json.JSONDecodeError instead of the documented GitHubApiError/exit-1 path (fixed in _github_http.fetch_json_page, now covered by tests/test_github_http.py); and positional _REPAIR_SLUGS[0]/[1]/[2] indexing that could silently mis-map if the tuple's order ever changed (replaced with named slug constants). Re-ran the full suite after: 2487 passed, 47 in-scope files still meet the 90% coverage floor (new _github_http.py: 97.8%), and the live run against this repository reproduced the identical numbers reported above, confirming the refactor is behavior-preserving.

Assumptions

  • Storage (issue's own Acceptance Criteria Map flagged this "unknown, pending a storage decision"): resolved by not persisting anything. The script recomputes the full weekly series from label:retrospective issues' own created_at every run, so a run a week from now naturally shows one more week-bucket -- the growth the issue's proof method asks for -- with no new write scope, no commit-back step, and no contents: write. Speculative: if step-summary-only visibility (bounded by Actions' own log retention) proves insufficient in practice, a persisted series is a follow-on, not built here.
  • The merged-PR denominator is a rough proxy ("repairs classified per merged PR that week"), not a strict fraction bounded to 100% -- see Facts above.
  • This is deliberately informational, not a second threshold gate: it never fails the run on the GPRR value itself, only on a genuine GitHub API error (the issue frames this as replacing "only a threshold pass/fail gate" with a numeric trend, not adding a second one).

Risk / blast radius

  • Read-only. Adds pull-requests: read to retrospective-gate-drift.yml's existing contents: read / issues: read permissions (needed for the merged-PR query); no new secret, no write scope, no commit-back step.
  • The new step has no continue-on-error, matching its sibling step, but nothing in it can trip CI red except a genuine GitHub API error -- the metric itself never fails the run.
  • Scoped to one workflow file, one new script, and a non-behavior-changing refactor of an existing one (verified: pre-existing list_labelled_issues tests pass unchanged, and a malformed-item input was constructed by hand to confirm the refactor still fails loudly with the same KeyError, not a new silent fail-open).

Rollback

Revert this PR's merge commit (or drop the new "Compute Gate-Preventable Repair Rate (GPRR)" step from retrospective-gate-drift.yml and the pull-requests: read permission lines) to fully disable; scan_retrospective_gate_drift.py's own citation-drift check is untouched in behavior and keeps running either way.

Verification

Acceptance Criteria Map from issue #726, restated:

Criterion Proof method Result
GPRR computed from existing retrospective issues with zero new instrumentation Unit test against a fixture set of retrospective issue bodies with known Status tags asserts correct weekly counts PASS -- tests/test_compute_gprr.py's build_weekly_series/parse_status_tags tests; reuses scan_retrospective_gate_drift.py's fetch_json_page/list_labelled_issue_records, no second GitHub client added
Metric visible over time, not just a single snapshot A second run after a week of new retrospective issues shows the series growing by one point, not overwriting prior history Satisfied structurally rather than via persistence: the script recomputes the full historical series every run (see Assumptions), so growth is automatic and there is no snapshot to overwrite
missing-deterministic-gate share reported as the headline number A manual run against this audit's own reference point (#714: 146/149 uncited) reproduces a consistent percentage PASS -- live run above leads with the all-time missing-deterministic-gate share (74.5%), same order of magnitude as #714's own reference point via a related but distinct metric

Checklist

Skill audit evidence

  • checker-script-adversarial-review: RAN -- self-adversarial pass against compute_gprr.py, _github_http.py, and the scan_retrospective_gate_drift.py refactor: constructed a malformed GitHub API response (an issue item missing "number") and confirmed list_labelled_issues still raises KeyError uncaught, identical to pre-refactor behavior (no new fail-open path); confirmed a zero denominator reports "n/a" rather than a misleading "0.0%"; confirmed the Status: line regex is anchored to the start/end of its own line so a hostile string merely mentioned in a repair's free-prose clause cannot forge a fake field, per SKILL.md's own stated injection concern (tests/test_compute_gprr.py::test_parse_status_tags_ignores_mid_sentence_mention); a separate, independent /code-review pass (8 finder agents) additionally found and this PR fixed: the cross-import independence-convention break, the workflow step being skipped after a threshold failure, an uncaught json.JSONDecodeError on an HTTP-200-with-bad-body response (now a tested GitHubApiError, tests/test_github_http.py), and positional slug-tuple indexing -- see Facts for the full list; ran the full test/lint/mypy/coverage suite after each fix (see Facts).
  • deterministic-gate-quality: RAN -- read against skills/evaluating-deterministic-gate-quality/references/dimensions.md; dimension 15 ("fail-closed default on incomplete or malformed input") specifically re-verified by hand against the refactored list_labelled_issue_records/list_labelled_issues, per the dimension's own instruction not to credit it from happy-path fixtures alone.
  • design-doc-adversarial-review: RAN -- docs/superpowers/specs/2026-08-04-gprr-design.md's Decision 2 (stateless recompute instead of a persisted/committed time series) was checked directly against the issue's own "storage decision -- unknown" residual risk, Decision 4's merged-PR ratio was checked against the live run above (which is what surfaced and then documented the >100% case), and Decision 1 was itself revised after review to the shared _github_http.py module shape described in Facts.

Related Issue

Closes #726

claude added 3 commits August 4, 2026 11:45
…) from retrospective issues

Adds .github/scripts/compute_gprr.py: parses each label:retrospective
issue's Status: tag(s), buckets by ISO week, and reports the
missing-deterministic-gate share of classified repairs and of merged
PRs per week, as a stateless recomputation (no persisted/committed
snapshot -- every run reproduces the full trend from source).

Refactors scan_retrospective_gate_drift.py to expose
list_labelled_issue_records (full issue records) and a renamed public
fetch_json_page, so compute_gprr.py reuses the existing paginated
GitHub API client instead of a second hand-rolled one, per the issue's
own constraint. list_labelled_issues becomes a thin wrapper; its
existing behavior and tests are unchanged.

Wires the new script into retrospective-gate-drift.yml as an
additional step (same cadence, piped to $GITHUB_STEP_SUMMARY, no
continue-on-error), adding pull-requests: read for the merged-PR
query.

Design: docs/superpowers/specs/2026-08-04-gprr-design.md

Refs #726.
Verified live against this repository (2026-08-04): a busy week's
retrospective issues can classify more gate-preventable repairs than
PRs merged that same week, since merge-retrospective files one issue
per merged PR but a single issue can enumerate several repairs, and a
week's classified repairs can trace back to a PR merged earlier.
Recorded so a future reader does not mistake a >100% share for a bug.

Refs #726.
Extract the generic paginated-fetch-with-retry GitHub client
(fetch_json_page, GitHubApiError, default_opener) into a new shared
.github/scripts/_github_http.py, so compute_gprr.py no longer imports
scan_retrospective_gate_drift.py directly for it -- the first version
broke this repo's .github/scripts/*.py independence convention.
compute_gprr.py still imports scan_retrospective_gate_drift for
list_labelled_issue_records specifically, the issue-specific reuse
issue #726 itself asked for.

Also fixes, found by the same review:
- retrospective-gate-drift.yml: the new GPRR step was silently skipped
  whenever the preceding citation-drift step failed on threshold;
  added if: ${{ !cancelled() }} so the trend still reports on exactly
  the days it matters most.
- _github_http.fetch_json_page: an HTTP 200 response with an
  unparseable body raised an uncaught json.JSONDecodeError instead of
  the documented GitHubApiError/exit-1 path; now caught and re-raised,
  covered by tests/test_github_http.py.
- compute_gprr.py: replaced positional _REPAIR_SLUGS[0]/[1]/[2]
  indexing (silently wrong if the tuple's order ever changed) with
  named slug constants.

Full suite re-verified after: 2487 passed, 47 in-scope files still
meet the 90% coverage floor, live run against this repository
reproduces identical output to before the refactor.

Refs #726.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(merge-retrospective): compute Gate-Preventable Repair Rate (GPRR) from existing retrospective issues

2 participants