github-security-report 0.14.2__tar.gz → 0.16.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. {github_security_report-0.14.2 → github_security_report-0.16.0}/PKG-INFO +209 -46
  2. {github_security_report-0.14.2 → github_security_report-0.16.0}/README.md +206 -43
  3. {github_security_report-0.14.2 → github_security_report-0.16.0}/pyproject.toml +4 -4
  4. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/_version.py +2 -2
  5. github_security_report-0.16.0/src/github_security_report/categories/__init__.py +92 -0
  6. github_security_report-0.16.0/src/github_security_report/categories/keys.py +76 -0
  7. github_security_report-0.16.0/src/github_security_report/categories/signals.py +113 -0
  8. github_security_report-0.16.0/src/github_security_report/categories/tables.py +244 -0
  9. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/cli/app.py +6 -1
  10. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/cli/modes.py +4 -0
  11. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/cli/outputs.py +55 -12
  12. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/cli/publish.py +4 -0
  13. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/cli/serialise.py +4 -0
  14. github_security_report-0.16.0/src/github_security_report/client/codeql_parsers.py +119 -0
  15. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/org_reads.py +5 -0
  16. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/parsers.py +7 -0
  17. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/queries.py +28 -3
  18. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/reads.py +95 -1
  19. github_security_report-0.16.0/src/github_security_report/client/reviews.py +76 -0
  20. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/writes.py +20 -0
  21. github_security_report-0.16.0/src/github_security_report/codeql/__init__.py +38 -0
  22. github_security_report-0.16.0/src/github_security_report/codeql/facts.py +189 -0
  23. github_security_report-0.16.0/src/github_security_report/codeql/tables.py +246 -0
  24. github_security_report-0.16.0/src/github_security_report/collect/codeql.py +66 -0
  25. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/collect/extras.py +25 -15
  26. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/collect/protocols.py +15 -0
  27. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/config/loader.py +12 -0
  28. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/config/models.py +27 -1
  29. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/config/schema.py +63 -36
  30. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/layout.py +14 -28
  31. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/models.py +28 -71
  32. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/ordering.py +15 -18
  33. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/posture/__init__.py +2 -0
  34. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/posture/enablement.py +29 -4
  35. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/posture/facts.py +2 -0
  36. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/pulls/__init__.py +4 -0
  37. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/pulls/columns.py +10 -3
  38. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/pulls/counting.py +24 -4
  39. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/pulls/presentation.py +21 -1
  40. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/pulls/table.py +4 -0
  41. github_security_report-0.16.0/src/github_security_report/ranking.py +86 -0
  42. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/remediate.py +10 -6
  43. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/render/html.py +29 -16
  44. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/render/markdown.py +34 -22
  45. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/render/slack.py +45 -30
  46. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/render/terminal.py +23 -40
  47. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/report/__init__.py +11 -1
  48. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/report/aggregate.py +60 -2
  49. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/report/display.py +47 -1
  50. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/report/tables.py +63 -5
  51. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/summary.py +50 -3
  52. github_security_report-0.14.2/src/github_security_report/categories.py +0 -382
  53. {github_security_report-0.14.2 → github_security_report-0.16.0}/.gitignore +0 -0
  54. {github_security_report-0.14.2 → github_security_report-0.16.0}/LICENSE +0 -0
  55. {github_security_report-0.14.2 → github_security_report-0.16.0}/LICENSES/Apache-2.0.txt +0 -0
  56. {github_security_report-0.14.2 → github_security_report-0.16.0}/scripts/README.md +0 -0
  57. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/__init__.py +0 -0
  58. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/authors.py +0 -0
  59. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/classify.py +0 -0
  60. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/cli/__init__.py +0 -0
  61. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/cli/__main__.py +0 -0
  62. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/cli/boundary.py +0 -0
  63. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/cli/options.py +0 -0
  64. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/__init__.py +0 -0
  65. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/alerts.py +0 -0
  66. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/batch_errors.py +0 -0
  67. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/copilot.py +0 -0
  68. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/endpoints.py +0 -0
  69. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/errors.py +0 -0
  70. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/client/transport.py +0 -0
  71. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/collect/__init__.py +0 -0
  72. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/collect/context.py +0 -0
  73. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/collect/org.py +0 -0
  74. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/collect/repo.py +0 -0
  75. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/config/__init__.py +0 -0
  76. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/config/order.py +0 -0
  77. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/gating.py +0 -0
  78. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/gitctx.py +0 -0
  79. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/issues.py +0 -0
  80. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/posture/releases.py +0 -0
  81. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/py.typed +0 -0
  82. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/render/__init__.py +0 -0
  83. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/render/slack_limits.py +0 -0
  84. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/report/signals.py +0 -0
  85. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/rulesets.py +0 -0
  86. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/runner.py +0 -0
  87. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/scope.py +0 -0
  88. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/secret_patterns.py +0 -0
  89. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/severity.py +0 -0
  90. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/templates/index.html.j2 +0 -0
  91. {github_security_report-0.14.2 → github_security_report-0.16.0}/src/github_security_report/templates/report.html.j2 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: github-security-report
3
- Version: 0.14.2
3
+ Version: 0.16.0
4
4
  Summary: Security and quality reporting across GitHub organisations
5
5
  Project-URL: Homepage, https://github.com/lfreleng-actions/github-security-report-action
6
6
  Project-URL: Repository, https://github.com/lfreleng-actions/github-security-report-action
@@ -36,8 +36,8 @@ Requires-Dist: pytest-asyncio==1.4.0; extra == 'dev'
36
36
  Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
37
37
  Requires-Dist: pytest==9.1.1; extra == 'dev'
38
38
  Requires-Dist: respx==0.23.1; extra == 'dev'
39
- Requires-Dist: ruff==0.16.5; extra == 'dev'
40
- Requires-Dist: syrupy==6.0.0; extra == 'dev'
39
+ Requires-Dist: ruff==0.16.8; extra == 'dev'
40
+ Requires-Dist: syrupy==6.1.1; extra == 'dev'
41
41
  Requires-Dist: types-jsonschema==4.26.0.20260518; extra == 'dev'
42
42
  Requires-Dist: types-pyyaml==6.0.12.20250915; extra == 'dev'
43
43
  Description-Content-Type: text/markdown
@@ -101,6 +101,33 @@ if you want to probe everything regardless.
101
101
  Further sections report **configuration posture** and **freshness** as plain
102
102
  tables (org mode):
103
103
 
104
+ - **CodeQL scan health** — two tables beneath **CodeQL: Results/Findings** (the
105
+ CodeQL alerts table), whose "Clean" says nothing about whether CodeQL is
106
+ still running:
107
+ - **Stale Configurations** lists every CodeQL configuration whose last scan
108
+ trails the default branch's newest commit by more than
109
+ `codeql_stale_days` (default 30) — the condition GitHub's tool status page
110
+ flags as *"Code Scanning results may be out of date"*. Each row names the
111
+ setup type (**Default**, GitHub-managed; or **Advanced**, a workflow in the
112
+ repository), the language, the last *successful* scan, and the cause:
113
+ analyses failing (a run that errors still uploads, but its results do not
114
+ count as a scan), default setup switched off, a workflow removed or
115
+ disabled, or an advanced workflow
116
+ **superseded** by default setup (GitHub rejects advanced CodeQL uploads
117
+ while default setup is on). An *orphaned* configuration will never scan
118
+ again; delete it from the tool status page once the live setup covers its
119
+ language.
120
+ - **Language Coverage** lists repositories where GitHub detects a
121
+ CodeQL-supported language that no current configuration scans — typically
122
+ an advanced workflow whose language matrix omits one the repository
123
+ contains, such as `actions` for its own workflows. On default setup GitHub
124
+ reports only the languages it is set to scan, so a detected language
125
+ deliberately left out of default setup is not visible to this check.
126
+
127
+ Both consider only repositories where CodeQL has run at least once; the rest
128
+ already appear in the Results/Findings not-enabled list. Finding a stale
129
+ configuration means reading each repository's whole CodeQL analysis history,
130
+ since GitHub cannot filter it by configuration.
104
131
  - **Dependabot** — three tables: repositories with vulnerability **alerts not
105
132
  enabled**, repositories with **security updates not enabled**, and ecosystems
106
133
  with no update `cooldown` configured (mandatory; any value passes).
@@ -118,6 +145,20 @@ tables (org mode):
118
145
  exposes no org-wide or GraphQL equivalent) and, like every other category,
119
146
  always collected; hide it with the `private_vulnerability_reporting` render
120
147
  toggle.
148
+ - **Auto-merge** — repositories where the **Allow auto-merge** setting is off,
149
+ so a pull request cannot be queued to merge itself once its requirements are
150
+ met. The setting only offers the option: an auto-merging pull request still
151
+ waits for the required checks, reviews and branch protections the repository
152
+ already enforces, so enabling it relaxes nothing. What it removes is the
153
+ interval between a change becoming mergeable and somebody noticing — the
154
+ window a reviewed dependency update sits in while the vulnerability it fixes
155
+ stays unpatched. Read from the batched GraphQL prefetch, so it costs no extra
156
+ request; hide it with the `auto_merge` render toggle.
157
+
158
+ The four enablement categories count the repositories with and without the
159
+ feature (plus any whose state could not be read), and by default name whichever
160
+ known list is shorter — see
161
+ [Enabled or disabled repository lists](#enabled-or-disabled-repository-lists).
121
162
 
122
163
  ## Operating modes
123
164
 
@@ -182,6 +223,7 @@ organisation and **Repository access** set to *All repositories*, then grant:
182
223
  | Secret scanning alerts | Open secret-scanning alerts, across every GitHub pattern category |
183
224
  | Issues | Open issues and their labels (GitHub Issues table) |
184
225
  | Administration | Dependabot enablement + security-updates status, and effective branch rules |
226
+ | Actions | Workflow state behind a stale CodeQL configuration |
185
227
 
186
228
  **Organization permissions:**
187
229
 
@@ -302,6 +344,7 @@ environment-variable name, never embedded.
302
344
  "include_test": false,
303
345
  "repo_min_age_days": 28,
304
346
  "release_max_age_days": 60,
347
+ "codeql_stale_days": 30,
305
348
  "graph_batch": 10,
306
349
  "order": { "style": "auto" }
307
350
  },
@@ -326,6 +369,17 @@ individual output. Set a value to `0` to remove the limit entirely and show
326
369
  every offender. Each can also be set at the CLI with `--top-n`,
327
370
  `--top-n-report`, `--top-n-cli`, and `--top-n-slack`.
328
371
 
372
+ `report.codeql_stale_days` (default `30`, minimum `1`) is the CodeQL
373
+ stale-configuration threshold: a configuration is stale once its last scan
374
+ trails the default branch's newest commit by more than that many days. It is
375
+ measured against the branch head rather than the clock, so a repository nobody
376
+ has pushed to is not flagged just for being quiet. GitHub does not publish the
377
+ threshold behind its own "may be out of date" warning; across the
378
+ `lfreleng-actions` estate healthy configurations trailed their head by at most
379
+ five days and abandoned ones by more than eighty, so the default sits well
380
+ clear of both. The same threshold decides which configurations count as current
381
+ for Language Coverage.
382
+
329
383
  The Releases / Tagging section has two independent freshness levers:
330
384
 
331
385
  - `report.repo_min_age_days` (default `28`, `0` = include all) is a grace
@@ -355,6 +409,23 @@ The per-org `exclude` list removes repositories from analysis entirely; they are
355
409
  reported as **excluded** (distinct from "not enabled"), so an intentional
356
410
  exclusion is visible rather than silently dropped.
357
411
 
412
+ Every category reports those exclusions beneath its counts, so an
413
+ organisation-wide list repeats under each one. `report.excluded_display`
414
+ governs that line on every surface:
415
+
416
+ | Value | Excluded line |
417
+ | ----- | ------------- |
418
+ | `always-show` (default) | Shown under every category |
419
+ | `always-hide` | Never shown |
420
+ | `conditional-hide` | Shown only for a category whose exclusions differ from the organisation's `exclude` list, and then in full |
421
+
422
+ Hiding the line changes nothing else: every count is unaffected, and the report
423
+ header's repository count already leaves excluded repositories out. So a clean
424
+ category reads `All Clean` once no Excluded line qualifies it. Today every
425
+ category reports the organisation's own list, so `conditional-hide` currently
426
+ hides the same lines `always-hide` does. It differs once a category excludes
427
+ repositories of its own. The `report.json` artifact always lists the exclusions.
428
+
358
429
  Archived and test repositories are excluded from analysis by default. Opt them
359
430
  back in with `report.include_archived` / `report.include_test` in the config, or
360
431
  for a single run with `--include-archived` / `--include-test`.
@@ -384,16 +455,17 @@ both true.
384
455
 
385
456
  The example above hides Zizmor on every surface, and keeps Releases / Tagging
386
457
  out of the terminal and Slack while still publishing it to the Markdown and HTML
387
- Pages output. The valid category keys are: `codeql`, `scorecard`, `zizmor`,
388
- `aislop`, `dependabot_alerts`, `secret_scanning`, `dependabot_alerts_enabled`,
389
- `dependabot_updates_enabled`, `dependabot_cooldown`, `releases`,
390
- `mutable_releases`, `private_vulnerability_reporting`, `github_issues`. Like the
391
- other `report`
392
- settings, `categories` can be set
393
- globally and overridden per organisation (overrides merge key-by-key, so
394
- flipping one output leaves the rest untouched). The machine-readable
395
- `report.json` artifact always contains the complete dataset, regardless of these
396
- toggles.
458
+ Pages output. The valid category keys are: `codeql`,
459
+ `codeql_stale_configurations`, `codeql_language_coverage`, `scorecard`,
460
+ `zizmor`, `aislop`, `dependabot_alerts`, `secret_scanning`,
461
+ `dependabot_alerts_enabled`, `dependabot_updates_enabled`,
462
+ `dependabot_cooldown`, `releases`, `mutable_releases`,
463
+ `private_vulnerability_reporting`, `auto_merge`, `github_issues`,
464
+ `pull_requests`, `pull_requests_assigned`. Like the other `report` settings,
465
+ `categories` can be set globally and overridden per organisation (overrides
466
+ merge key-by-key, so flipping one output leaves the rest untouched). The
467
+ machine-readable `report.json` artifact always contains the complete dataset,
468
+ regardless of these toggles.
397
469
 
398
470
  When several organisations share one Slack channel they render into a single
399
471
  combined digest, so the per-org Slack toggles are unioned for that channel: a
@@ -454,6 +526,52 @@ Slack-style ceiling, but they still apply their own row limits — only the
454
526
  GitHub Pages report whenever `pages_url` is set and short enough to render as a
455
527
  link.
456
528
 
529
+ ### Enabled or disabled repository lists
530
+
531
+ The four boolean feature categories — `dependabot_alerts_enabled`,
532
+ `dependabot_updates_enabled`, `private_vulnerability_reporting` and `auto_merge`
533
+ — sort each repository into one of three buckets: enabled, not enabled, or
534
+ **unknown** when the feature's state could not be read. The unknown bucket is
535
+ counted but never named, and never treated as either side, so the enabled and
536
+ not-enabled counts need not sum to the repositories analysed. Beneath the counts,
537
+ one of the two known sides is named; `report.repo_list` chooses which, on every
538
+ surface:
539
+
540
+ | Value | Names |
541
+ | ----- | ----- |
542
+ | `auto` (default) | Whichever list is shorter |
543
+ | `enabled` | The repositories with the feature on |
544
+ | `disabled` | The repositories with the feature off |
545
+
546
+ `auto` keeps a footer short whichever way an organisation leans: a feature
547
+ nearly every repository has lists its few holdouts, and one almost none have
548
+ lists its few adopters. Two cases resolve towards the actionable side: a tie
549
+ names the repositories without the feature, and so does a category where
550
+ *nothing* is enabled, since naming an empty list would print nothing where a
551
+ reader wants the repositories to fix. Both sides are always counted, whichever is
552
+ named.
553
+
554
+ A category can override the global value:
555
+
556
+ ```json
557
+ {
558
+ "report": {
559
+ "repo_list": "auto",
560
+ "categories": {
561
+ "dependabot_alerts_enabled": { "repo_list": "disabled" }
562
+ }
563
+ },
564
+ "organizations": [{ "name": "lfreleng-actions" }]
565
+ }
566
+ ```
567
+
568
+ Here Dependabot alerts always name the repositories to fix, and the other three
569
+ feature categories name whichever side is shorter. `repo_list` applies only to
570
+ those four categories; setting it on any other is a configuration error, since a
571
+ table with qualitative columns has no enabled list to name. These categories
572
+ render as a name list rather than a one-column table on every surface. The
573
+ `report.json` artifact is unaffected.
574
+
457
575
  ### Per-category row ordering
458
576
 
459
577
  Each table ships a sensible default ordering — largest backlog first, stalest
@@ -736,17 +854,17 @@ organisation](#inside-or-outside-the-organisation):
736
854
 
737
855
  ```text
738
856
  Pull Requests
739
- ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━┳━━━━━━┳━━━━━━━━━━┳━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━┓
740
- ┃ Repository ┃ Human ┃ Ext ┃ Auto ┃ Conflict ┃ Fail ┃ Copilot ┃ Draft ┃ Total ┃
741
- ┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━╇━━━━━━╇━━━━━━━━━━╇━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━┩
742
- │ lftools-uv │ 8 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 8 │
743
- │ dependamerge │ 3 │ 0 │ 0 │ 0 │ 0 │ 2 │ 0 │ 3 │
744
- │ gha-workflow-linter │ 2 │ 0 │ 0 │ 0 │ 1 │ 0 │ 1 │ 2 │
745
- │ harden-runner-block- │ 1 │ 0 │ 0 │ 1 │ 0 │ 0 │ 1 │ 1 │
746
- │ action │ │ │ │ │ │ │ │ │
747
- ├─────────────────────┼───────┼─────┼──────┼──────────┼──────┼─────────┼───────┼───────┤
748
- │ Total │ 14 │ 0 │ 0 │ 1 │ 1 │ 2 │ 2 │ 14 │
749
- └─────────────────────┴───────┴─────┴──────┴──────────┴──────┴─────────┴───────┴───────┘
857
+ ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━┳━━━━━━┳━━━━━━━━━━┳━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━┓
858
+ ┃ Repository ┃ Human ┃ Ext ┃ Auto ┃ Conflict ┃ Fail ┃ Review ┃ Copilot ┃ Draft ┃ Total ┃
859
+ ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━╇━━━━━━╇━━━━━━━━━━╇━━━━━━╇━━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━┩
860
+ │ lftools-uv │ 8 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 8 │
861
+ │ dependamerge │ 3 │ 0 │ 0 │ 0 │ 0 │ 1 │ 2 │ 0 │ 3 │
862
+ │ gha-workflow-linter │ 2 │ 0 │ 0 │ 0 │ 1 │ 0 │ 0 │ 1 │ 2 │
863
+ │ harden-runner-block- │ 1 │ 0 │ 0 │ 1 │ 0 │ 0 │ 0 │ 1 │ 1 │
864
+ │ action │ │ │ │ │ │ │ │ │ │
865
+ ├──────────────────────┼───────┼─────┼──────┼──────────┼──────┼────────┼─────────┼───────┼───────┤
866
+ │ Total │ 14 │ 0 │ 0 │ 1 │ 1 │ 1 │ 2 │ 2 │ 14 │
867
+ └──────────────────────┴───────┴─────┴──────┴──────────┴──────┴────────┴─────────┴───────┴───────┘
750
868
  … and 9 more
751
869
  ❌ 13 With open pull requests
752
870
  ✅ 104 No open pull requests
@@ -760,34 +878,73 @@ The columns form **two independent groupings**:
760
878
  | `Ext` | Human pull requests raised from outside the organisation — a **subset of Human**, which is why it sits beside it. |
761
879
  | `Conflict` | Blocked on a merge conflict. |
762
880
  | `Fail` | Latest checks did not pass. The rollup includes optional checks, so this is not by itself proof that the merge is blocked. |
881
+ | `Review` | A reviewer has **requested changes** — a person waiting on the author. |
763
882
  | `Copilot` | Carries at least one **unresolved review thread** opened by GitHub's automated code reviewer. |
764
883
  | `Draft` | Marked as a draft. |
765
884
 
766
- `Conflict`, `Fail`, `Copilot` and `Draft` **overlap** each other and the author
767
- split, so they do not sum to `Total` and are not meant to: one pull request that
768
- is conflicting, failing *and* a draft is counted once in each of those three
769
- columns, and once under `Human` or `Auto`. Only `Human` + `Auto` reconciles with
770
- the collected total. They are ordered worst-first — a conflict needs a human to
771
- rebase, a failing check may only need a re-run, unresolved review feedback needs
772
- a human but does not hold the merge button down, and a draft is not blocked at
773
- all.
885
+ `Conflict`, `Fail`, `Review`, `Copilot` and `Draft` **overlap** each other and
886
+ the author split, so they do not sum to `Total` and are not meant to: one pull
887
+ request that is conflicting, failing *and* a draft is counted once in each of
888
+ those three columns, and once under `Human` or `Auto`. Only `Human` + `Auto`
889
+ reconciles with the collected total. They are ordered worst-first — a conflict
890
+ needs a human to rebase, a failing check may only need a re-run, requested
891
+ changes need the author to act, unresolved automated feedback needs somebody but
892
+ does not hold the merge button down, and a draft is not blocked at all.
893
+
894
+ `Review` and `Copilot` sit together inside the blocker group, and deliberately
895
+ not beside `Human`: that column names who **raised** a pull request, whereas
896
+ these two name who is **waiting on** it. The grouping is what keeps those two
897
+ readings of "human" apart.
898
+
899
+ #### Requested changes
900
+
901
+ `Review` counts pull requests where **a person** has asked for changes and not
902
+ withdrawn it.
903
+
904
+ Two fields answer that, each covering the other's blind spot. `reviewDecision` is
905
+ GitHub's own verdict, computed over every review and accounting for dismissals,
906
+ so it is **exact at any review count** — but it names nobody, and a GitHub App
907
+ holding pull-request write permission can request changes exactly as a person
908
+ can. `latestOpinionatedReviews` names the reviewers, but is a bounded window of
909
+ five (one review per reviewer, so five reviewers rather than five reviews).
910
+
911
+ So the decision **gates** and the window **attributes**. Anything other than
912
+ `CHANGES_REQUESTED` is a definite zero whatever the window holds, and only once
913
+ GitHub says changes are outstanding does the reviewer's identity matter — which
914
+ keeps the cheap exact answer for almost every pull request and pays the window's
915
+ uncertainty on the few where it changes the reading. Automated reviewers are
916
+ recognised by the same rule as the `Auto` column.
917
+
918
+ Only `CHANGES_REQUESTED` gates. **`REVIEW_REQUIRED` is deliberately excluded**:
919
+ it reports that a branch rule demands a review, not that anybody objected, so on
920
+ an organisation that requires review by default it would mark nearly every human
921
+ pull request and distinguish none of them. The rule is written as an allow-list
922
+ of the one blocking state rather than as "anything that is not `APPROVED`", so a
923
+ state GitHub adds later arrives uncounted rather than pre-counted as a blocker.
924
+
925
+ Where a request for changes cannot be attributed — the reviews were unreadable,
926
+ the author is gone, or the window held only automated requests without covering
927
+ every reviewer — the pull request is left **uncounted rather than credited to a
928
+ person**, and the table says so in its description.
774
929
 
775
930
  #### Unresolved Copilot feedback
776
931
 
777
- `Copilot` counts the pull requests **waiting on a human to answer a review**. A
778
- review thread counts when it was opened by GitHub's automated code reviewer (the
779
- `copilot-pull-request-reviewer` bot) and nobody has resolved it. The reviewer is
780
- identified from each thread's **opening** comment, since later replies are
781
- usually the human answering the review.
932
+ `Copilot` counts the pull requests **waiting on somebody to answer an automated
933
+ review**. A review thread counts when it was opened by GitHub's automated code
934
+ reviewer (the `copilot-pull-request-reviewer` bot) and nobody has resolved it.
935
+ The reviewer is identified from each thread's **opening** comment, since later
936
+ replies are usually the human answering the review.
782
937
 
783
938
  An **outdated** thread still counts: GitHub marks a thread outdated when the
784
939
  code beneath it changes, but that does not resolve it, so the feedback remains
785
940
  unanswered.
786
941
 
787
- Any non-zero count is coloured **red**, like `Conflict` and `Fail` — it marks a
788
- pull request that cannot progress until somebody responds. The count also feeds
789
- the row ranking, so a backlog awaiting review outranks an untouched one of the
790
- same size.
942
+ Any non-zero count is coloured **amber**, one step below the red of `Conflict`,
943
+ `Fail` and `Review`. Automated feedback is worth answering, but nobody is
944
+ waiting on it, and there is far more of it — colouring the two alike would let
945
+ the bot's threads mask the rarer case that actually holds a person up. The count
946
+ still feeds the row ranking, so a backlog awaiting review outranks an untouched
947
+ one of the same size.
791
948
 
792
949
  The scan reads the **newest 20 review threads per pull request**, which is the
793
950
  single most expensive part of the prefetch (see the cost figures above). The
@@ -802,8 +959,8 @@ clear — see below.
802
959
  #### Automation backlog thresholds
803
960
 
804
961
  On the terminal the counts are coloured so the table reads at a glance: `Human`
805
- and `Ext` **green**, `Conflict`, `Fail` and `Copilot` **red**, and `Auto`
806
- coloured against two configured thresholds.
962
+ and `Ext` **green**, `Conflict`, `Fail` and `Review` **red**, `Copilot`
963
+ **amber**, and `Auto` coloured against two configured thresholds.
807
964
 
808
965
  Dependabot stops raising pull requests once a repository reaches its
809
966
  `open-pull-requests-limit`, so the repository quietly stops receiving dependency
@@ -1025,7 +1182,7 @@ reports as an App and any unrecognised login carrying the `[bot]` marker — so
1025
1182
  future bot is classified as automation rather than mistaken for an outside
1026
1183
  contributor.
1027
1184
 
1028
- `Fail`, `Conflict` and `Copilot` count only **established** states. GitHub
1185
+ `Fail`, `Conflict`, `Review` and `Copilot` count only **established** states. GitHub
1029
1186
  computes mergeability lazily and answers `UNKNOWN` until it settles, and reports
1030
1187
  no check rollup at all when no checks have run; neither absence is evidence that
1031
1188
  a pull request is ready, so neither is counted either way. `Copilot` follows the
@@ -1039,10 +1196,15 @@ holding any such reading says so in its description: `Copilot` then
1039
1196
  **undercounts and should be read as a lower bound**, exactly as `Ext` does when
1040
1197
  membership cannot be read.
1041
1198
 
1199
+ `Review` carries a weaker version of the same hedge, and earns it far less
1200
+ often: `reviewDecision` is exact, so only a pull request GitHub *does* report
1201
+ changes requested on can leave the question open, and then only when the
1202
+ opinionated-review window fails to attribute it to anybody.
1203
+
1042
1204
  Repositories with no open pull requests are counted in the footer rather than
1043
1205
  listed. Rows rank by total open pull requests, then by those failing,
1044
- conflicting or awaiting Copilot feedback (counted once each, since the columns
1045
- overlap), so two repositories with equal backlogs surface the more
1206
+ conflicting or awaiting review — automated or human (counted once each, since
1207
+ the columns overlap) — so two repositories with equal backlogs surface the more
1046
1208
  stuck one first.
1047
1209
 
1048
1210
  > **Accuracy note.** As with the issues table, `Total` is exact at any size,
@@ -1369,6 +1531,7 @@ enablement endpoint:
1369
1531
  | `dependabot_alerts_enabled` | Dependabot vulnerability alerts |
1370
1532
  | `dependabot_updates_enabled` | Dependabot security updates (plus alerts) |
1371
1533
  | `private_vulnerability_reporting` | Private vulnerability reporting |
1534
+ | `auto_merge` | The repository's "Allow auto-merge" setting |
1372
1535
 
1373
1536
  Qualitative findings (Scorecard, zizmor, open Dependabot alerts, cooldown,
1374
1537
  release freshness/mutability) are reported but not auto-remediated. Remediation