@homericintelligence/athena-opencode 0.4.4 → 0.5.1

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 (136) hide show
  1. package/README.md +30 -17
  2. package/package.json +2 -2
  3. package/plugin.js +20 -5
  4. package/skills/TECHNICAL_ENGLISH.md +78 -0
  5. package/skills/_cli.py +26 -18
  6. package/skills/_support/docs/dependency-resolution.md +192 -0
  7. package/skills/_support/docs/policies/development.md +117 -0
  8. package/skills/_support/docs/policies/evidence-integrity.md +40 -0
  9. package/skills/_support/docs/principles/README.md +542 -0
  10. package/skills/_support/docs/principles/details/p001-kiss.md +114 -0
  11. package/skills/_support/docs/principles/details/p002-yagni.md +109 -0
  12. package/skills/_support/docs/principles/details/p003-dry.md +126 -0
  13. package/skills/_support/docs/principles/details/p004-solid.md +165 -0
  14. package/skills/_support/docs/principles/details/p005-modularity.md +123 -0
  15. package/skills/_support/docs/principles/details/p006-principle-of-least-astonishment.md +115 -0
  16. package/skills/_support/docs/principles/details/p007-subtraction-over-addition.md +113 -0
  17. package/skills/_support/docs/principles/details/p008-understand-before-subtracting.md +115 -0
  18. package/skills/_support/docs/principles/details/p009-general-mechanisms-over-special-cases.md +108 -0
  19. package/skills/_support/docs/principles/details/p010-scope-fidelity.md +117 -0
  20. package/skills/_support/docs/principles/details/p011-minimal-coherent-change.md +128 -0
  21. package/skills/_support/docs/principles/details/p012-evidence-before-modification.md +115 -0
  22. package/skills/_support/docs/principles/details/p013-avoid-hasty-abstractions.md +112 -0
  23. package/skills/_support/docs/principles/details/p014-preserve-unrequested-behavior.md +110 -0
  24. package/skills/_support/docs/principles/details/p015-architecture-conformance.md +124 -0
  25. package/skills/_support/docs/principles/details/p016-separation-of-concerns.md +125 -0
  26. package/skills/_support/docs/principles/details/p017-high-cohesion-low-coupling.md +130 -0
  27. package/skills/_support/docs/principles/details/p018-information-hiding.md +128 -0
  28. package/skills/_support/docs/principles/details/p019-explicit-contracts.md +134 -0
  29. package/skills/_support/docs/principles/details/p020-executable-architecture.md +130 -0
  30. package/skills/_support/docs/principles/details/p021-evolutionary-and-reversible-design.md +126 -0
  31. package/skills/_support/docs/principles/details/p022-test-behavior-not-implementation.md +129 -0
  32. package/skills/_support/docs/principles/details/p023-parameterized-table-driven-testing.md +129 -0
  33. package/skills/_support/docs/principles/details/p024-boundary-value-testing.md +128 -0
  34. package/skills/_support/docs/principles/details/p025-property-based-testing-for-invariants.md +133 -0
  35. package/skills/_support/docs/principles/details/p026-regression-before-repair.md +134 -0
  36. package/skills/_support/docs/principles/details/p027-deterministic-and-hermetic-tests.md +128 -0
  37. package/skills/_support/docs/principles/details/p028-test-failure-paths.md +133 -0
  38. package/skills/_support/docs/principles/details/p029-generalize-error-policy-preserve-specific-cause.md +131 -0
  39. package/skills/_support/docs/principles/details/p030-nearest-responsible-error-boundary.md +133 -0
  40. package/skills/_support/docs/principles/details/p031-propagate-rather-than-swallow.md +131 -0
  41. package/skills/_support/docs/principles/details/p032-handle-once-preserve-causality.md +137 -0
  42. package/skills/_support/docs/principles/details/p033-state-safe-failure-semantics.md +140 -0
  43. package/skills/_support/docs/principles/details/p034-fail-fast.md +132 -0
  44. package/skills/_support/docs/principles/details/p035-fail-secure-fail-closed.md +137 -0
  45. package/skills/_support/docs/principles/details/p036-graceful-degradation.md +137 -0
  46. package/skills/_support/docs/principles/details/p037-idempotency-before-retry.md +139 -0
  47. package/skills/_support/docs/principles/details/p038-bounded-retry.md +145 -0
  48. package/skills/_support/docs/principles/details/p039-bounded-waiting.md +151 -0
  49. package/skills/_support/docs/principles/details/p040-bounded-resources.md +138 -0
  50. package/skills/_support/docs/principles/details/p041-backpressure-and-load-shedding.md +141 -0
  51. package/skills/_support/docs/principles/details/p042-fault-isolation-bulkheads.md +140 -0
  52. package/skills/_support/docs/principles/details/p043-circuit-breakers.md +148 -0
  53. package/skills/_support/docs/principles/details/p044-atomicity-where-possible.md +141 -0
  54. package/skills/_support/docs/principles/details/p045-compensation-where-atomicity-is-impossible.md +163 -0
  55. package/skills/_support/docs/principles/details/p046-resumability.md +138 -0
  56. package/skills/_support/docs/principles/details/p047-observability-is-part-of-correctness.md +134 -0
  57. package/skills/_support/docs/principles/details/p048-secure-by-design.md +132 -0
  58. package/skills/_support/docs/principles/details/p049-secure-by-default.md +126 -0
  59. package/skills/_support/docs/principles/details/p050-least-privilege.md +123 -0
  60. package/skills/_support/docs/principles/details/p051-complete-mediation.md +124 -0
  61. package/skills/_support/docs/principles/details/p052-separation-of-duties.md +129 -0
  62. package/skills/_support/docs/principles/details/p053-validate-at-trust-boundaries.md +126 -0
  63. package/skills/_support/docs/principles/details/p054-defense-in-depth.md +135 -0
  64. package/skills/_support/docs/principles/details/p055-minimize-attack-surface.md +133 -0
  65. package/skills/_support/docs/principles/details/p056-secrets-stay-out-of-code-and-context.md +137 -0
  66. package/skills/_support/docs/principles/details/p057-supply-chain-integrity.md +135 -0
  67. package/skills/_support/docs/principles/details/p058-bounded-agent-authority.md +137 -0
  68. package/skills/_support/docs/principles/details/p059-data-is-not-instruction.md +130 -0
  69. package/skills/_support/docs/principles/details/p060-constrain-sub-agents.md +136 -0
  70. package/skills/_support/docs/principles/details/p061-separate-decision-from-high-impact-execution.md +131 -0
  71. package/skills/_support/docs/principles/details/p062-human-approval-for-irreversible-or-high-risk-actions.md +129 -0
  72. package/skills/_support/docs/principles/details/p063-requirement-to-code-traceability.md +116 -0
  73. package/skills/_support/docs/principles/details/p064-requirement-to-test-traceability.md +116 -0
  74. package/skills/_support/docs/principles/details/p065-verify-before-claiming-completion.md +129 -0
  75. package/skills/_support/docs/principles/details/p066-preserve-existing-work.md +114 -0
  76. package/skills/_support/docs/principles/details/p067-no-test-cheating.md +126 -0
  77. package/skills/_support/docs/principles/details/p068-no-validation-bypass.md +119 -0
  78. package/skills/_support/docs/principles/details/p069-independent-review-for-high-risk-changes.md +129 -0
  79. package/skills/_support/docs/principles/details/p070-code-health-must-not-regress.md +116 -0
  80. package/skills/_support/docs/principles/details/p071-consistency-over-personal-preference.md +117 -0
  81. package/skills/_support/docs/principles/details/p072-technical-evidence-over-preference.md +122 -0
  82. package/skills/_support/docs/principles/details/p073-optimize-only-with-evidence.md +123 -0
  83. package/skills/_support/docs/principles/details/p074-prefer-existing-mechanisms.md +121 -0
  84. package/skills/_support/docs/principles/details/p075-make-invalid-states-hard-to-represent.md +128 -0
  85. package/skills/_support/docs/principles/details/p076-parse-then-validate-then-operate.md +133 -0
  86. package/skills/_support/docs/principles/details/p077-separate-policy-from-mechanism.md +112 -0
  87. package/skills/_support/docs/principles/details/p078-single-source-of-truth.md +131 -0
  88. package/skills/_support/docs/principles/details/p079-explicit-ownership-and-lifetimes.md +124 -0
  89. package/skills/_support/docs/principles/details/p080-make-concurrency-deliberate.md +122 -0
  90. package/skills/_support/docs/principles/details/p081-forward-progress-with-safety.md +125 -0
  91. package/skills/_support/docs/principles/details/p082-design-for-cancellation.md +133 -0
  92. package/skills/_support/docs/principles/details/p083-irreversible-actions-last.md +124 -0
  93. package/skills/_support/docs/principles/details/p084-prefer-local-reasoning.md +131 -0
  94. package/skills/_support/docs/principles/details/p085-explicit-is-better-than-implicit.md +123 -0
  95. package/skills/_support/docs/principles/details/p086-readability-counts.md +113 -0
  96. package/skills/_support/docs/principles/details/p087-comments-explain-why-code-explains-what.md +120 -0
  97. package/skills/_support/docs/principles/details/p088-delete-dead-code.md +125 -0
  98. package/skills/_support/docs/principles/details/p089-delete-obsolete-configuration-and-dependencies.md +125 -0
  99. package/skills/_support/docs/principles/details/p090-prefer-negative-code.md +134 -0
  100. package/skills/_support/docs/principles/details/p091-test-driven-development.md +149 -0
  101. package/skills/_support/docs/review/README.md +59 -0
  102. package/skills/_support/docs/review/behavior-first-testing.md +73 -0
  103. package/skills/_support/docs/review/common.md +365 -0
  104. package/skills/_support/docs/review/design-docs.md +74 -0
  105. package/skills/_support/docs/review/issue-planning.md +220 -0
  106. package/skills/_support/docs/review/language-routing.md +141 -0
  107. package/skills/_support/docs/review/repository-scorecard.md +170 -0
  108. package/skills/advise/SKILL.md +102 -40
  109. package/skills/advise/scripts/list_retrievable_skills.py +3 -3
  110. package/skills/brainstorm/SKILL.md +167 -76
  111. package/skills/change-review/SKILL.md +99 -43
  112. package/skills/change-review/references/scope-resolution.md +70 -41
  113. package/skills/change-review/scripts/resolve_scope.py +119 -73
  114. package/skills/finalize-plan/SKILL.md +208 -100
  115. package/skills/git-worktrees/SKILL.md +108 -59
  116. package/skills/git-worktrees/scripts/prepare_worktree.py +16 -10
  117. package/skills/issue-review/SKILL.md +143 -47
  118. package/skills/learn/SKILL.md +284 -162
  119. package/skills/myrmidon-swarm/SKILL.md +136 -61
  120. package/skills/plan-issue/SKILL.md +129 -47
  121. package/skills/pr-review/SKILL.md +170 -71
  122. package/skills/pr-review/references/criteria.md +61 -14
  123. package/skills/pr-review/references/delivery.md +159 -80
  124. package/skills/pr-review/references/evidence.md +268 -139
  125. package/skills/pr-review/references/prevalidated.md +91 -48
  126. package/skills/pr-review/scripts/collect_evidence.py +211 -140
  127. package/skills/pr-review/scripts/diff_context.py +6 -4
  128. package/skills/pr-review/scripts/materialize_snapshot.py +112 -120
  129. package/skills/pr-review/scripts/pr_identity.py +27 -15
  130. package/skills/pr-review/scripts/resolve_pr.py +53 -31
  131. package/skills/repo-review/SKILL.md +209 -87
  132. package/skills/systematic-debugging/SKILL.md +195 -131
  133. package/skills/systematic-debugging/scripts/repository_evidence.py +9 -4
  134. package/skills/test-driven-development/SKILL.md +134 -53
  135. package/skills/tidy/SKILL.md +97 -33
  136. package/skills/tidy/scripts/run_tidy.py +3 -3
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Collect GitHub PR metadata and immutable review evidence."""
2
+ """Collect GitHub pull-request metadata and immutable review evidence."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
@@ -42,8 +42,8 @@ from skills._cli import (
42
42
  )
43
43
 
44
44
  # Keep this query below GitHub's GraphQL complexity budget. Strict callers bind
45
- # changed paths to local immutable Git objects; legacy callers retain the REST
46
- # file-list fallback for backwards compatibility only.
45
+ # changed paths to local immutable Git objects. Earlier callers use the REST
46
+ # file-list fallback only for backward compatibility.
47
47
  FIELDS = (
48
48
  "number,title,body,state,isDraft,author,baseRefName,headRefName,"
49
49
  "baseRefOid,headRefOid,reviews,statusCheckRollup,closingIssuesReferences,url"
@@ -90,7 +90,7 @@ class CheckEvidenceCoverageGap(RuntimeError):
90
90
 
91
91
  @dataclass
92
92
  class LinkedRequirementBudget:
93
- """One cumulative provider budget shared by both strict evidence reads."""
93
+ """This budget controls both strict evidence reads from the provider."""
94
94
 
95
95
  pages: int = 0
96
96
  comments: int = 0
@@ -105,7 +105,7 @@ class LinkedRequirementBudget:
105
105
  """Reserve one bounded provider request before issuing it."""
106
106
  if self.requests >= MAX_LINKED_REQUIREMENT_REQUESTS:
107
107
  raise LinkedRequirementsCoverageGap(
108
- "linked issue requirements exceed the safe aggregate request limit"
108
+ "The linked issue requirements exceed the safe aggregate request limit."
109
109
  )
110
110
  self.requests += 1
111
111
 
@@ -113,7 +113,7 @@ class LinkedRequirementBudget:
113
113
  """Reserve one aggregate comment page and its provider request."""
114
114
  if self.pages >= MAX_LINKED_REQUIREMENT_PAGES:
115
115
  raise LinkedRequirementsCoverageGap(
116
- "linked issue requirements exceed the safe aggregate page limit"
116
+ "The linked issue requirements exceed the safe aggregate page limit."
117
117
  )
118
118
  self.reserve_request()
119
119
 
@@ -121,7 +121,7 @@ class LinkedRequirementBudget:
121
121
  """Account for one provider response without crossing the byte budget."""
122
122
  if count > self.remaining_bytes():
123
123
  raise LinkedRequirementsCoverageGap(
124
- "linked issue requirements exceed the safe aggregate byte limit"
124
+ "The linked issue requirements exceed the safe aggregate byte limit."
125
125
  )
126
126
  self.bytes_read += count
127
127
 
@@ -129,7 +129,7 @@ class LinkedRequirementBudget:
129
129
  """Account for one successful provider page and its item count."""
130
130
  if self.comments + count > MAX_LINKED_REQUIREMENT_COMMENTS:
131
131
  raise LinkedRequirementsCoverageGap(
132
- "linked issue requirements exceed the safe aggregate comment limit"
132
+ "The linked issue requirements exceed the safe aggregate comment limit."
133
133
  )
134
134
  self.pages += 1
135
135
  self.comments += count
@@ -137,7 +137,7 @@ class LinkedRequirementBudget:
137
137
 
138
138
  @dataclass
139
139
  class ProviderStream:
140
- """One bounded asynchronous stdout or stderr capture."""
140
+ """This record contains one bounded asynchronous output stream."""
141
141
 
142
142
  maximum_bytes: int
143
143
  output: bytearray = field(default_factory=bytearray)
@@ -148,7 +148,7 @@ class ProviderStream:
148
148
 
149
149
  @dataclass
150
150
  class ChangedPathStream:
151
- """Incrementally validate a bounded NUL-delimited Git path manifest."""
151
+ """Incrementally validate a bounded null-byte-delimited Git path manifest."""
152
152
 
153
153
  maximum_bytes: int
154
154
  maximum_paths: int
@@ -164,7 +164,7 @@ class ChangedPathStream:
164
164
 
165
165
  @dataclass(frozen=True)
166
166
  class ImmutableIdentity:
167
- """The review artifact and exact revisions that evidence is bound to."""
167
+ """This record binds evidence to a review artifact and its exact revisions."""
168
168
 
169
169
  repository: str
170
170
  number: int
@@ -187,7 +187,7 @@ class ImmutableIdentity:
187
187
 
188
188
  @dataclass(frozen=True)
189
189
  class ExpectedReviewTarget:
190
- """The immutable GitHub artifact target resolved before strict collection."""
190
+ """This record identifies the immutable GitHub target for strict collection."""
191
191
 
192
192
  host: str
193
193
  repository: str
@@ -201,7 +201,7 @@ class ExpectedReviewTarget:
201
201
 
202
202
  @dataclass(frozen=True)
203
203
  class ChangedPathManifest:
204
- """A canonical, immutable changed-path manifest derived from Git objects."""
204
+ """This record describes a canonical changed-path manifest from Git objects."""
205
205
 
206
206
  paths: tuple[str, ...]
207
207
  sha256: str
@@ -217,7 +217,7 @@ class ChangedPathManifest:
217
217
 
218
218
  @dataclass(frozen=True)
219
219
  class ReviewScope:
220
- """Canonical mutable review-context fields bound to an evidence collection."""
220
+ """This record binds mutable review-context fields to an evidence collection."""
221
221
 
222
222
  fields: dict[str, Any]
223
223
  sha256: str
@@ -229,7 +229,7 @@ class ReviewScope:
229
229
 
230
230
  @dataclass(frozen=True)
231
231
  class LinkedRequirements:
232
- """Canonical content binding for every linked issue used as requirements."""
232
+ """This record binds the canonical content of each linked issue used as requirements."""
233
233
 
234
234
  items: tuple[LinkedRequirement, ...]
235
235
  sha256: str
@@ -245,7 +245,7 @@ class LinkedRequirements:
245
245
 
246
246
  @dataclass(frozen=True)
247
247
  class LinkedRequirement:
248
- """One linked issue's stable identity and content-only requirements digest."""
248
+ """This record contains one linked issue identity and its requirements digest."""
249
249
 
250
250
  id: str
251
251
  repository: str
@@ -265,9 +265,9 @@ class LinkedRequirement:
265
265
 
266
266
 
267
267
  def metadata_error(metadata: object, *, require_immutable_identity: bool) -> str | None:
268
- """Return a diagnostic when GitHub returns partial PR metadata."""
268
+ """Return a diagnostic when GitHub returns partial pull-request metadata."""
269
269
  if not isinstance(metadata, dict):
270
- return "PR metadata must be a JSON object"
270
+ return "The pull-request metadata must be a JSON object."
271
271
  required_types = {
272
272
  "number": int,
273
273
  "title": str,
@@ -287,11 +287,13 @@ def metadata_error(metadata: object, *, require_immutable_identity: bool) -> str
287
287
  if not isinstance(author, dict) or not isinstance(author.get("login"), str):
288
288
  invalid.append("author.login")
289
289
  if invalid:
290
- return "GitHub returned incomplete or invalid PR metadata fields: " + ", ".join(
291
- sorted(set(invalid))
290
+ return (
291
+ "GitHub returned pull-request metadata fields that are incomplete or not valid: "
292
+ + ", ".join(f"'{field}'" for field in sorted(set(invalid)))
293
+ + "."
292
294
  )
293
295
  if metadata["state"] != "OPEN":
294
- return f"pull request {metadata['number']} is not open"
296
+ return f"Pull request {metadata['number']} is not open."
295
297
  identity_fields = ("baseRefOid", "headRefOid")
296
298
  identity_values = [metadata.get(field) for field in identity_fields]
297
299
  has_identity = any(value is not None for value in identity_values)
@@ -303,8 +305,10 @@ def metadata_error(metadata: object, *, require_immutable_identity: bool) -> str
303
305
  ]
304
306
  if invalid_identity:
305
307
  return (
306
- "GitHub returned incomplete or invalid immutable PR identity fields: "
307
- + ", ".join(invalid_identity)
308
+ "GitHub returned immutable pull-request identity fields that are "
309
+ "incomplete or not valid: "
310
+ + ", ".join(f"'{field}'" for field in invalid_identity)
311
+ + "."
308
312
  )
309
313
  if require_immutable_identity:
310
314
  body = metadata.get("body")
@@ -320,8 +324,9 @@ def metadata_error(metadata: object, *, require_immutable_identity: bool) -> str
320
324
  scope_invalid.append("closingIssuesReferences")
321
325
  if scope_invalid:
322
326
  return (
323
- "GitHub returned incomplete or invalid review-scope fields: "
324
- + ", ".join(scope_invalid)
327
+ "GitHub returned review-scope fields that are incomplete or not valid: "
328
+ + ", ".join(f"'{field}'" for field in scope_invalid)
329
+ + "."
325
330
  )
326
331
  return None
327
332
 
@@ -335,11 +340,11 @@ def immutable_identity(
335
340
  if base_oid is None and head_oid is None and not require_immutable_identity:
336
341
  return None
337
342
  if not isinstance(base_oid, str) or not isinstance(head_oid, str):
338
- raise TypeError("GitHub returned incomplete immutable pull-request identity")
343
+ raise TypeError("GitHub returned incomplete immutable pull-request identity.")
339
344
  number = metadata.get("number")
340
345
  url = metadata.get("url")
341
346
  if not isinstance(number, int) or not isinstance(url, str):
342
- raise TypeError("GitHub returned incomplete pull-request identity")
347
+ raise TypeError("GitHub returned incomplete pull-request identity.")
343
348
  return ImmutableIdentity(
344
349
  repository=repository,
345
350
  number=number,
@@ -354,15 +359,19 @@ def expected_identity(
354
359
  ) -> tuple[str, str] | None:
355
360
  """Validate the optional immutable identity supplied by resolve_pr.py."""
356
361
  if (base_oid is None) != (head_oid is None):
357
- parser.error(
358
- "--expected-base-oid and --expected-head-oid must be supplied together"
359
- )
362
+ parser.error("Specify --expected-base-oid and --expected-head-oid together.")
360
363
  if base_oid is None or head_oid is None:
361
364
  return None
362
365
  if COMMIT_OID.fullmatch(base_oid) is None:
363
- parser.error("--expected-base-oid must be a lowercase 40-hex Git commit OID")
366
+ parser.error(
367
+ "--expected-base-oid must be a Git commit object identifier that contains "
368
+ "40 lowercase hexadecimal characters."
369
+ )
364
370
  if COMMIT_OID.fullmatch(head_oid) is None:
365
- parser.error("--expected-head-oid must be a lowercase 40-hex Git commit OID")
371
+ parser.error(
372
+ "--expected-head-oid must be a Git commit object identifier that contains "
373
+ "40 lowercase hexadecimal characters."
374
+ )
366
375
  return base_oid, head_oid
367
376
 
368
377
 
@@ -374,7 +383,7 @@ def expected_target(
374
383
  number: int | None,
375
384
  url: str | None,
376
385
  ) -> ExpectedReviewTarget | None:
377
- """Require the resolved GitHub target whenever immutable OIDs are supplied."""
386
+ """Require the resolved GitHub target when immutable object identifiers are supplied."""
378
387
  if identity is None:
379
388
  if (
380
389
  host is not None
@@ -383,21 +392,22 @@ def expected_target(
383
392
  or url is not None
384
393
  ):
385
394
  parser.error(
386
- "--expected-host, --expected-repository, --expected-pr-number, and "
387
- "--expected-pr-url require immutable expected OIDs"
395
+ "If you specify an expected target, also specify --expected-base-oid "
396
+ "and --expected-head-oid."
388
397
  )
389
398
  return None
390
399
  if host is None or repository is None or number is None or url is None:
391
400
  parser.error(
401
+ "With --expected-base-oid and --expected-head-oid, specify "
392
402
  "--expected-host, --expected-repository, --expected-pr-number, and "
393
- "--expected-pr-url are required with immutable expected OIDs"
403
+ "--expected-pr-url."
394
404
  )
395
405
  assert host is not None
396
406
  assert repository is not None
397
407
  assert number is not None
398
408
  assert url is not None
399
409
  if host != "github.com":
400
- parser.error("--expected-host must be github.com")
410
+ parser.error("'--expected-host' must be 'github.com'.")
401
411
  try:
402
412
  canonical_repository = require_github_repository(
403
413
  repository, "--expected-repository"
@@ -405,7 +415,7 @@ def expected_target(
405
415
  except RuntimeError as error:
406
416
  parser.error(str(error))
407
417
  if number < 1:
408
- parser.error("--expected-pr-number must be a positive pull-request number")
418
+ parser.error("--expected-pr-number must be a positive pull-request number.")
409
419
  try:
410
420
  canonical_url = require_canonical_pull_request_url(
411
421
  url, canonical_repository, number, "--expected-pr-url"
@@ -423,14 +433,15 @@ def expected_target(
423
433
  def ensure_expected_identity(
424
434
  identity: ImmutableIdentity | None, expected: tuple[str, str] | None
425
435
  ) -> None:
426
- """Fail closed when collected revisions differ from the resolved PR."""
436
+ """Fail closed when collected revisions differ from the resolved pull request."""
427
437
  if expected is None:
428
438
  return
429
439
  if identity is None:
430
- raise RuntimeError("GitHub returned no immutable pull-request identity")
440
+ raise RuntimeError("GitHub did not return an immutable pull-request identity.")
431
441
  if (identity.base_oid, identity.head_oid) != expected:
432
442
  raise RuntimeError(
433
- "immutable pull-request identity does not match the expected base/head OIDs"
443
+ "The immutable pull-request identity does not match the expected base "
444
+ "object identifier and head object identifier."
434
445
  )
435
446
 
436
447
 
@@ -441,20 +452,22 @@ def ensure_expected_target(
441
452
  if target is None:
442
453
  return
443
454
  if identity is None:
444
- raise RuntimeError("GitHub returned no immutable pull-request identity")
455
+ raise RuntimeError("GitHub did not return an immutable pull-request identity.")
445
456
  if (
446
457
  identity.repository.casefold() != target.repository.casefold()
447
458
  or identity.number != target.number
448
459
  or identity.url != target.url
449
460
  ):
450
- raise RuntimeError("pull-request identity does not match the expected target")
461
+ raise RuntimeError(
462
+ "The pull-request identity does not match the expected target."
463
+ )
451
464
 
452
465
 
453
466
  def review_scope(metadata: dict[str, Any]) -> ReviewScope:
454
- """Bind mutable issue/scope fields so they cannot drift during review."""
467
+ """Bind mutable issue fields and scope fields so they cannot change during review."""
455
468
  closing_issues = metadata.get("closingIssuesReferences")
456
469
  if not isinstance(closing_issues, list):
457
- raise TypeError("GitHub returned incomplete review-scope fields")
470
+ raise TypeError("GitHub returned incomplete review-scope fields.")
458
471
  try:
459
472
  canonical_issues = sorted(
460
473
  json.dumps(
@@ -485,7 +498,9 @@ def review_scope(metadata: dict[str, Any]) -> ReviewScope:
485
498
  sort_keys=True,
486
499
  )
487
500
  except (KeyError, TypeError, ValueError, json.JSONDecodeError) as error:
488
- raise RuntimeError("GitHub returned invalid review-scope fields") from error
501
+ raise RuntimeError(
502
+ "GitHub returned review-scope fields that are not valid."
503
+ ) from error
489
504
  return ReviewScope(
490
505
  fields=fields,
491
506
  sha256=sha256(canonical_scope.encode("utf-8")).hexdigest(),
@@ -495,7 +510,7 @@ def review_scope(metadata: dict[str, Any]) -> ReviewScope:
495
510
  def linked_issue_reference(issue: object) -> tuple[str, str, int, str]:
496
511
  """Return one validated canonical linked-issue identity."""
497
512
  if not isinstance(issue, dict):
498
- raise TypeError("GitHub returned an invalid linked issue reference")
513
+ raise TypeError("GitHub returned a linked issue reference that is not valid.")
499
514
  issue_id = issue.get("id")
500
515
  repository_data = issue.get("repository")
501
516
  number = issue.get("number")
@@ -507,7 +522,7 @@ def linked_issue_reference(issue: object) -> tuple[str, str, int, str]:
507
522
  or isinstance(number, bool)
508
523
  or not isinstance(number, int)
509
524
  ):
510
- raise RuntimeError("GitHub returned an incomplete linked issue reference")
525
+ raise RuntimeError("GitHub returned an incomplete linked issue reference.")
511
526
  owner_data = repository_data.get("owner")
512
527
  name = repository_data.get("name")
513
528
  owner = owner_data.get("login") if isinstance(owner_data, dict) else None
@@ -519,18 +534,18 @@ def linked_issue_reference(issue: object) -> tuple[str, str, int, str]:
519
534
  or number < 1
520
535
  or not isinstance(url, str)
521
536
  ):
522
- raise RuntimeError("GitHub returned an incomplete linked issue reference")
537
+ raise RuntimeError("GitHub returned an incomplete linked issue reference.")
523
538
  try:
524
539
  repository = require_github_repository(
525
540
  f"{owner}/{name}", "GitHub linked issue repository"
526
541
  )
527
542
  except RuntimeError as error:
528
543
  raise RuntimeError(
529
- "GitHub returned an invalid linked issue repository"
544
+ "GitHub returned a linked issue repository that is not valid."
530
545
  ) from error
531
546
  canonical_url = f"https://github.com/{repository}/issues/{number}"
532
547
  if url != canonical_url:
533
- raise RuntimeError("GitHub returned an invalid linked issue URL")
548
+ raise RuntimeError("GitHub returned a linked issue URL that is not valid.")
534
549
  return issue_id, repository, number, canonical_url
535
550
 
536
551
 
@@ -545,7 +560,7 @@ def canonical_json(value: object, label: str) -> str:
545
560
  sort_keys=True,
546
561
  )
547
562
  except (TypeError, ValueError) as error:
548
- raise RuntimeError(f"GitHub returned invalid {label}") from error
563
+ raise RuntimeError(f"The data for {label} is not valid.") from error
549
564
 
550
565
 
551
566
  def drain_provider_stream(stream: IO[bytes], capture: ProviderStream) -> None:
@@ -571,7 +586,7 @@ def drain_provider_stream(stream: IO[bytes], capture: ProviderStream) -> None:
571
586
  try:
572
587
  stream.close()
573
588
  except OSError:
574
- # Another cleanup path may already have closed this best-effort pipe.
589
+ # A different cleanup path can close this pipe first. Ignore the error.
575
590
  pass
576
591
  capture.completed.set()
577
592
 
@@ -579,15 +594,15 @@ def drain_provider_stream(stream: IO[bytes], capture: ProviderStream) -> None:
579
594
  def validate_changed_path(entry: bytes) -> None:
580
595
  """Reject an empty or unsafe Git-relative path entry."""
581
596
  if not entry:
582
- raise RuntimeError("Git returned an empty changed path")
597
+ raise RuntimeError("Git returned an empty changed path.")
583
598
  if entry.startswith(b"/") or any(
584
599
  component in {b".", b".."} for component in entry.split(b"/")
585
600
  ):
586
- raise RuntimeError("Git returned an unsafe changed path")
601
+ raise RuntimeError("Git returned an unsafe changed path.")
587
602
 
588
603
 
589
604
  def drain_changed_path_stream(stream: IO[bytes], capture: ChangedPathStream) -> None:
590
- """Incrementally collect one bounded, NUL-delimited Git path manifest."""
605
+ """Incrementally collect one bounded, null-byte-delimited Git path manifest."""
591
606
  try:
592
607
  while True:
593
608
  read_size = min(
@@ -595,7 +610,7 @@ def drain_changed_path_stream(stream: IO[bytes], capture: ChangedPathStream) ->
595
610
  )
596
611
  if read_size <= 0:
597
612
  capture.limit_error = (
598
- "changed-path manifest exceeds the safe byte limit"
613
+ "The changed-path manifest exceeds the safe byte limit."
599
614
  )
600
615
  capture.overflowed.set()
601
616
  return
@@ -603,12 +618,13 @@ def drain_changed_path_stream(stream: IO[bytes], capture: ChangedPathStream) ->
603
618
  if not chunk:
604
619
  if capture.trailing:
605
620
  raise RuntimeError(
606
- "Git returned a malformed NUL-delimited changed-path manifest"
621
+ "Git returned a null-byte-delimited changed-path manifest that "
622
+ "is not valid."
607
623
  )
608
624
  return
609
625
  if capture.bytes_read + len(chunk) > capture.maximum_bytes:
610
626
  capture.limit_error = (
611
- "changed-path manifest exceeds the safe byte limit"
627
+ "The changed-path manifest exceeds the safe byte limit."
612
628
  )
613
629
  capture.overflowed.set()
614
630
  return
@@ -618,10 +634,10 @@ def drain_changed_path_stream(stream: IO[bytes], capture: ChangedPathStream) ->
618
634
  for entry in entries:
619
635
  validate_changed_path(entry)
620
636
  if entry in capture.seen_paths:
621
- raise RuntimeError("Git returned duplicate changed paths")
637
+ raise RuntimeError("Git returned duplicate changed paths.")
622
638
  if len(capture.paths) >= capture.maximum_paths:
623
639
  capture.limit_error = (
624
- "changed-path manifest exceeds the safe path limit"
640
+ "The changed-path manifest exceeds the safe path limit."
625
641
  )
626
642
  capture.overflowed.set()
627
643
  return
@@ -633,7 +649,7 @@ def drain_changed_path_stream(stream: IO[bytes], capture: ChangedPathStream) ->
633
649
  try:
634
650
  stream.close()
635
651
  except (OSError, ValueError):
636
- # A sibling cleanup path can close the pipe before this reader exits.
652
+ # A different cleanup path can close this pipe first. Ignore the error.
637
653
  pass
638
654
  capture.completed.set()
639
655
 
@@ -650,7 +666,7 @@ def reap_provider(
650
666
  try:
651
667
  stream.close()
652
668
  except (OSError, ValueError):
653
- # Reaping is best effort after either reader may have closed the pipe.
669
+ # A reader can close this pipe first. Ignore the error.
654
670
  pass
655
671
  process.wait()
656
672
  for reader in readers:
@@ -704,14 +720,18 @@ def bounded_gh_output(
704
720
  limit_error: str,
705
721
  timeout_seconds: float | None = None,
706
722
  stderr_maximum_bytes: int | None = None,
707
- stderr_limit_error: str = "linked issue response exceeds the safe stderr limit",
708
- deadline_error: str = "linked issue provider exceeded the safe provider deadline",
709
- output_error: str = "cannot read linked issue provider output",
710
- unavailable_output_error: str = "GitHub did not provide linked issue provider output",
711
- operating_system_error: str = "cannot collect linked issue comments",
723
+ stderr_limit_error: str = (
724
+ "The linked issue response exceeds the safe standard-error limit."
725
+ ),
726
+ deadline_error: str = "The linked issue provider exceeded the safe deadline.",
727
+ output_error: str = "The tool cannot read linked issue provider output.",
728
+ unavailable_output_error: str = (
729
+ "The GitHub CLI process did not provide linked issue output."
730
+ ),
731
+ operating_system_error: str = "The tool cannot collect linked issue comments",
712
732
  coverage_gap: type[RuntimeError] = LinkedRequirementsCoverageGap,
713
733
  ) -> bytes:
714
- """Run one deadline-bound GitHub request with bounded stdout and stderr."""
734
+ """Run one deadline-bound GitHub request with bounded output streams."""
715
735
  command = ["gh", *arguments]
716
736
  effective_timeout = (
717
737
  LINKED_ISSUE_COMMENT_REQUEST_TIMEOUT_SECONDS
@@ -732,7 +752,8 @@ def bounded_gh_output(
732
752
  )
733
753
  except FileNotFoundError as error:
734
754
  raise RuntimeError(
735
- f"required command unavailable: {error.filename or command[0]}"
755
+ "The required command is not available: "
756
+ f"'{error.filename or command[0]}'."
736
757
  ) from error
737
758
  stdout = process.stdout
738
759
  stderr = process.stderr
@@ -777,10 +798,16 @@ def bounded_gh_output(
777
798
  message = (
778
799
  bytes(stderr_capture.output).decode("utf-8", errors="replace").strip()
779
800
  )
780
- raise RuntimeError(message or f"gh {' '.join(arguments)} failed")
801
+ raise RuntimeError(
802
+ message
803
+ or f"The GitHub CLI command failed. Command: gh {' '.join(arguments)}"
804
+ )
781
805
  return bytes(stdout_capture.output)
782
806
  except OSError as error:
783
- raise RuntimeError(f"{operating_system_error}: {error}") from error
807
+ raise RuntimeError(
808
+ f"{operating_system_error}. "
809
+ f"The operation returned this diagnostic.\n{error}"
810
+ ) from error
784
811
 
785
812
 
786
813
  def paginated_issue_comments(
@@ -795,11 +822,11 @@ def paginated_issue_comments(
795
822
  aggregate_remaining_bytes = collection_budget.remaining_bytes()
796
823
  if remaining_bytes <= 0:
797
824
  raise LinkedRequirementsCoverageGap(
798
- "linked issue comments exceed the safe byte limit"
825
+ "The linked issue comments exceed the safe byte limit."
799
826
  )
800
827
  if aggregate_remaining_bytes <= 0:
801
828
  raise LinkedRequirementsCoverageGap(
802
- "linked issue requirements exceed the safe aggregate byte limit"
829
+ "The linked issue requirements exceed the safe aggregate byte limit."
803
830
  )
804
831
  collection_budget.reserve_comment_page()
805
832
  response = bounded_gh_output(
@@ -819,7 +846,7 @@ def paginated_issue_comments(
819
846
  remaining_bytes,
820
847
  aggregate_remaining_bytes,
821
848
  ),
822
- limit_error="linked issue comments exceed the safe byte limit",
849
+ limit_error="The linked issue comments exceed the safe byte limit.",
823
850
  )
824
851
  bytes_read += len(response)
825
852
  collection_budget.record_bytes(len(response))
@@ -827,27 +854,33 @@ def paginated_issue_comments(
827
854
  page_comments = json.loads(response)
828
855
  except json.JSONDecodeError as error:
829
856
  raise RuntimeError(
830
- "GitHub returned invalid linked issue comment pages"
857
+ "GitHub returned linked issue comment pages that are not valid."
831
858
  ) from error
832
859
  if not isinstance(page_comments, list):
833
- raise TypeError("GitHub returned invalid linked issue comment pages")
860
+ raise TypeError(
861
+ "GitHub returned linked issue comment pages that are not valid."
862
+ )
834
863
  if not all(isinstance(comment, dict) for comment in page_comments):
835
- raise RuntimeError("GitHub returned an invalid linked issue comment")
864
+ raise RuntimeError(
865
+ "GitHub returned a linked issue comment that is not valid."
866
+ )
836
867
  if page > MAX_LINKED_ISSUE_COMMENT_PAGES:
837
868
  if page_comments:
838
869
  raise LinkedRequirementsCoverageGap(
839
- "linked issue comments exceed the safe page limit"
870
+ "The linked issue comments exceed the safe page limit."
840
871
  )
841
872
  return comments
842
873
  if len(comments) + len(page_comments) > MAX_LINKED_ISSUE_COMMENTS:
843
874
  raise LinkedRequirementsCoverageGap(
844
- "linked issue comments exceed the safe comment limit"
875
+ "The linked issue comments exceed the safe comment limit."
845
876
  )
846
877
  collection_budget.record_comment_page(len(page_comments))
847
878
  comments.extend(page_comments)
848
879
  if len(page_comments) < LINKED_ISSUE_COMMENT_PAGE_SIZE:
849
880
  return comments
850
- raise AssertionError("bounded linked issue comment pagination did not terminate")
881
+ raise AssertionError(
882
+ "The bounded linked issue comment pagination did not terminate."
883
+ )
851
884
 
852
885
 
853
886
  def structured_error(error: str, details: str) -> None:
@@ -862,7 +895,7 @@ def linked_issue_metadata(
862
895
  aggregate_remaining_bytes = budget.remaining_bytes()
863
896
  if aggregate_remaining_bytes <= 0:
864
897
  raise LinkedRequirementsCoverageGap(
865
- "linked issue requirements exceed the safe aggregate byte limit"
898
+ "The linked issue requirements exceed the safe aggregate byte limit."
866
899
  )
867
900
  budget.reserve_request()
868
901
  response = bounded_gh_output(
@@ -878,15 +911,17 @@ def linked_issue_metadata(
878
911
  maximum_bytes=min(
879
912
  MAX_LINKED_REQUIREMENT_METADATA_BYTES, aggregate_remaining_bytes
880
913
  ),
881
- limit_error="linked issue metadata exceeds the safe metadata byte limit",
914
+ limit_error="The linked issue metadata exceeds the safe metadata byte limit.",
882
915
  )
883
916
  budget.record_bytes(len(response))
884
917
  try:
885
918
  issue_data = json.loads(response)
886
919
  except json.JSONDecodeError as error:
887
- raise RuntimeError("GitHub returned an invalid linked issue") from error
920
+ raise RuntimeError(
921
+ "GitHub returned a linked issue that is not valid."
922
+ ) from error
888
923
  if not isinstance(issue_data, dict):
889
- raise TypeError("GitHub returned an invalid linked issue")
924
+ raise TypeError("GitHub returned a linked issue that is not valid.")
890
925
  return issue_data
891
926
 
892
927
 
@@ -896,10 +931,10 @@ def linked_requirements(
896
931
  """Bind every linked issue's requirement content and complete comment history."""
897
932
  references = metadata.get("closingIssuesReferences")
898
933
  if not isinstance(references, list):
899
- raise TypeError("GitHub returned incomplete linked issue references")
934
+ raise TypeError("GitHub returned incomplete linked issue references.")
900
935
  identities = sorted(linked_issue_reference(issue) for issue in references)
901
936
  if len(identities) != len(set(identities)):
902
- raise RuntimeError("GitHub returned duplicate linked issue references")
937
+ raise RuntimeError("GitHub returned duplicate linked issue references.")
903
938
  collection_budget = budget if budget is not None else LinkedRequirementBudget()
904
939
  items: list[LinkedRequirement] = []
905
940
  for expected_id, repository, number, expected_url in identities:
@@ -914,7 +949,7 @@ def linked_requirements(
914
949
  or (body is not None and not isinstance(body, str))
915
950
  or not isinstance(issue_data.get("state"), str)
916
951
  ):
917
- raise RuntimeError("GitHub returned incomplete linked issue requirements")
952
+ raise RuntimeError("GitHub returned incomplete linked issue requirements.")
918
953
  comments = sorted(
919
954
  canonical_json(comment, "linked issue comment")
920
955
  for comment in paginated_issue_comments(
@@ -962,14 +997,17 @@ def bounded_git_path_manifest(
962
997
  )
963
998
  except FileNotFoundError as error:
964
999
  raise RuntimeError(
965
- f"required command unavailable: {error.filename or command[0]}"
1000
+ "The required command is not available: "
1001
+ f"'{error.filename or command[0]}'."
966
1002
  ) from error
967
1003
  stdout = process.stdout
968
1004
  stderr = process.stderr
969
1005
  if stdout is None or stderr is None:
970
1006
  process.kill()
971
1007
  process.wait()
972
- raise RuntimeError("Git did not provide changed-path output")
1008
+ raise RuntimeError(
1009
+ "The Git process did not provide the required output streams."
1010
+ )
973
1011
  stdout_capture = ChangedPathStream(
974
1012
  MAX_CHANGED_PATH_MANIFEST_BYTES, MAX_CHANGED_PATHS
975
1013
  )
@@ -993,11 +1031,15 @@ def bounded_git_path_manifest(
993
1031
  process,
994
1032
  stdout_capture,
995
1033
  stderr_capture,
996
- "changed-path manifest exceeds the safe byte limit",
1034
+ "The changed-path manifest exceeds the safe byte limit.",
997
1035
  timeout_seconds=CHANGED_PATH_REQUEST_TIMEOUT_SECONDS,
998
- stderr_limit_error="changed-path response exceeds the safe stderr limit",
999
- deadline_error="changed-path provider exceeded the safe provider deadline",
1000
- output_error="cannot read immutable changed-path output",
1036
+ stderr_limit_error=(
1037
+ "The changed-path response exceeds the safe standard-error limit."
1038
+ ),
1039
+ deadline_error=(
1040
+ "The changed-path provider exceeded the safe deadline."
1041
+ ),
1042
+ output_error="The tool cannot read immutable changed-path output.",
1001
1043
  coverage_gap=ChangedPathCoverageGap,
1002
1044
  )
1003
1045
  except BaseException:
@@ -1009,16 +1051,19 @@ def bounded_git_path_manifest(
1009
1051
  message = (
1010
1052
  bytes(stderr_capture.output).decode("utf-8", errors="replace").strip()
1011
1053
  )
1012
- raise RuntimeError(message or f"git {' '.join(arguments)} failed")
1054
+ raise RuntimeError(
1055
+ message or f"The Git command failed. Command: git {' '.join(arguments)}"
1056
+ )
1013
1057
  return stdout_capture.paths
1014
1058
  except OSError as error:
1015
1059
  raise RuntimeError(
1016
- f"cannot collect immutable changed paths: {error}"
1060
+ "The tool cannot collect immutable changed paths. "
1061
+ f"The operation returned this diagnostic.\n{error}"
1017
1062
  ) from error
1018
1063
 
1019
1064
 
1020
1065
  def git_bytes(*arguments: str, cwd: Path | None = None) -> bytes:
1021
- """Run a read-only Git query and return its byte-exact stdout."""
1066
+ """Run a read-only Git query and return its byte-exact standard output."""
1022
1067
  result: Any = run_command(
1023
1068
  ["git", *git_read_arguments(), *arguments],
1024
1069
  capture_output=True,
@@ -1032,17 +1077,19 @@ def git_bytes(*arguments: str, cwd: Path | None = None) -> bytes:
1032
1077
  message = stderr.decode("utf-8", errors="replace").strip()
1033
1078
  else:
1034
1079
  message = str(stderr).strip()
1035
- raise RuntimeError(message or f"git {' '.join(arguments)} failed")
1080
+ raise RuntimeError(
1081
+ message or f"The Git command failed. Command: git {' '.join(arguments)}"
1082
+ )
1036
1083
  stdout = result.stdout
1037
1084
  if not isinstance(stdout, bytes):
1038
- raise TypeError("git returned non-byte output for immutable path evidence")
1085
+ raise TypeError("Git returned non-byte output for immutable path evidence.")
1039
1086
  return stdout
1040
1087
 
1041
1088
 
1042
1089
  def immutable_range_paths(
1043
1090
  base_oid: str, head_oid: str, *, cwd: Path | None = None
1044
1091
  ) -> list[bytes]:
1045
- """Return validated NUL-safe paths from one immutable Git diff range."""
1092
+ """Return validated null-byte-safe paths from one immutable Git diff range."""
1046
1093
  return bounded_git_path_manifest(
1047
1094
  (
1048
1095
  "-c",
@@ -1081,7 +1128,9 @@ def immutable_changed_paths(
1081
1128
  try:
1082
1129
  paths = tuple(entry.decode("utf-8") for entry in canonical_entries)
1083
1130
  except UnicodeDecodeError as error:
1084
- raise RuntimeError("Git returned a non-UTF-8 changed path") from error
1131
+ raise RuntimeError(
1132
+ "Git returned a changed path that is not valid 'UTF-8'."
1133
+ ) from error
1085
1134
  canonical_bytes = b"".join(entry + b"\0" for entry in canonical_entries)
1086
1135
  return ChangedPathManifest(paths=paths, sha256=sha256(canonical_bytes).hexdigest())
1087
1136
 
@@ -1101,13 +1150,15 @@ def strict_changed_paths(
1101
1150
  expected: tuple[str, str],
1102
1151
  target: ExpectedReviewTarget,
1103
1152
  ) -> tuple[ChangedPathManifest, MaterializedSnapshot | None]:
1104
- """Derive strict paths locally or from a verified host-owned snapshot."""
1153
+ """Derive strict paths locally or from a verified host-owned materialized snapshot."""
1105
1154
  base_oid, head_oid = expected
1106
1155
  if local_immutable_objects_available(base_oid, head_oid):
1107
1156
  return immutable_changed_paths(base_oid, head_oid), None
1108
1157
  base_ref = metadata.get("baseRefName")
1109
1158
  if not isinstance(base_ref, str):
1110
- raise TypeError("GitHub returned an invalid pull-request base ref")
1159
+ raise TypeError(
1160
+ "GitHub returned a pull-request base reference that is not valid."
1161
+ )
1111
1162
  snapshot = materialize_snapshot(
1112
1163
  repository=target.repository,
1113
1164
  number=target.number,
@@ -1129,7 +1180,10 @@ def gh(*arguments: str, accepted_codes: tuple[int, ...] = (0,)) -> str:
1129
1180
  ["gh", *arguments], capture_output=True, text=True, check=False
1130
1181
  )
1131
1182
  if result.returncode not in accepted_codes:
1132
- raise RuntimeError(result.stderr.strip() or f"gh {' '.join(arguments)} failed")
1183
+ raise RuntimeError(
1184
+ result.stderr.strip()
1185
+ or f"The GitHub CLI command failed. Command: gh {' '.join(arguments)}"
1186
+ )
1133
1187
  return result.stdout
1134
1188
 
1135
1189
 
@@ -1142,12 +1196,12 @@ def head_bound_check_runs(repository: str, head_oid: str) -> list[dict[str, Any]
1142
1196
  for page_number in range(1, MAX_CHECK_RUN_PAGES + 2):
1143
1197
  if page_number > MAX_CHECK_RUN_PAGES:
1144
1198
  raise CheckEvidenceCoverageGap(
1145
- "GitHub check runs exceed the safe page limit"
1199
+ "GitHub check runs exceed the safe page limit."
1146
1200
  )
1147
1201
  remaining_bytes = MAX_CHECK_RUN_BYTES - bytes_read
1148
1202
  if remaining_bytes <= 0:
1149
1203
  raise CheckEvidenceCoverageGap(
1150
- "GitHub check runs exceed the safe aggregate byte limit"
1204
+ "GitHub check runs exceed the safe aggregate byte limit."
1151
1205
  )
1152
1206
  try:
1153
1207
  response = bounded_gh_output(
@@ -1165,31 +1219,39 @@ def head_bound_check_runs(repository: str, head_oid: str) -> list[dict[str, Any]
1165
1219
  ),
1166
1220
  ),
1167
1221
  maximum_bytes=min(MAX_CHECK_RUN_PAGE_BYTES, remaining_bytes),
1168
- limit_error="GitHub check-run response exceeds the safe byte limit",
1222
+ limit_error="The GitHub check-run response exceeds the safe byte limit.",
1169
1223
  timeout_seconds=CHECK_RUN_REQUEST_TIMEOUT_SECONDS,
1170
1224
  stderr_maximum_bytes=MAX_CHECK_RUN_STDERR_BYTES,
1171
- stderr_limit_error="GitHub check-run response exceeds the safe stderr limit",
1172
- deadline_error="GitHub check-run provider exceeded the safe provider deadline",
1173
- output_error="cannot read GitHub check-run provider output",
1174
- unavailable_output_error="GitHub did not provide check-run provider output",
1175
- operating_system_error="cannot collect GitHub check runs",
1225
+ stderr_limit_error=(
1226
+ "The GitHub check-run response exceeds the safe standard-error limit."
1227
+ ),
1228
+ deadline_error=(
1229
+ "The GitHub check-run provider exceeded the safe deadline."
1230
+ ),
1231
+ output_error="The tool cannot read GitHub check-run provider output.",
1232
+ unavailable_output_error=(
1233
+ "The GitHub CLI process did not provide check-run output."
1234
+ ),
1235
+ operating_system_error="The tool cannot collect GitHub check runs",
1176
1236
  coverage_gap=CheckEvidenceCoverageGap,
1177
1237
  )
1178
1238
  except RuntimeError as error:
1179
1239
  if isinstance(error, CheckEvidenceCoverageGap):
1180
1240
  raise
1181
1241
  raise CheckEvidenceCoverageGap(
1182
- "GitHub did not return readable head-bound check evidence"
1242
+ "GitHub did not return readable check evidence for the head object identifier."
1183
1243
  ) from error
1184
1244
  bytes_read += len(response)
1185
1245
  try:
1186
1246
  page = json.loads(response)
1187
1247
  except json.JSONDecodeError as error:
1188
1248
  raise CheckEvidenceCoverageGap(
1189
- "GitHub did not return readable head-bound check evidence"
1249
+ "GitHub did not return readable check evidence for the head object identifier."
1190
1250
  ) from error
1191
1251
  if not isinstance(page, dict):
1192
- raise CheckEvidenceCoverageGap("GitHub returned a malformed check-run page")
1252
+ raise CheckEvidenceCoverageGap(
1253
+ "GitHub returned a check-run page that is not valid."
1254
+ )
1193
1255
  page_total = page.get("total_count")
1194
1256
  page_runs = page.get("check_runs")
1195
1257
  if (
@@ -1199,21 +1261,23 @@ def head_bound_check_runs(repository: str, head_oid: str) -> list[dict[str, Any]
1199
1261
  or not isinstance(page_runs, list)
1200
1262
  ):
1201
1263
  raise CheckEvidenceCoverageGap(
1202
- "GitHub returned incomplete check-run evidence"
1264
+ "GitHub returned incomplete check-run evidence."
1203
1265
  )
1204
1266
  if total_count is None:
1205
1267
  total_count = page_total
1206
1268
  if total_count > MAX_CHECK_RUNS:
1207
1269
  raise CheckEvidenceCoverageGap(
1208
- "GitHub check runs exceed the safe run limit"
1270
+ "GitHub check runs exceed the safe run limit."
1209
1271
  )
1210
1272
  elif page_total != total_count:
1211
1273
  raise CheckEvidenceCoverageGap(
1212
- "GitHub returned inconsistent check-run totals"
1274
+ "GitHub returned inconsistent check-run totals."
1213
1275
  )
1214
1276
  for run in page_runs:
1215
1277
  if not isinstance(run, dict):
1216
- raise CheckEvidenceCoverageGap("GitHub returned a malformed check run")
1278
+ raise CheckEvidenceCoverageGap(
1279
+ "GitHub returned a check run that is not valid."
1280
+ )
1217
1281
  run_id = run.get("id")
1218
1282
  run_head_oid = run.get("head_sha")
1219
1283
  if (
@@ -1229,32 +1293,35 @@ def head_bound_check_runs(repository: str, head_oid: str) -> list[dict[str, Any]
1229
1293
  or COMMIT_OID.fullmatch(run_head_oid) is None
1230
1294
  ):
1231
1295
  raise CheckEvidenceCoverageGap(
1232
- "GitHub returned incomplete check-run evidence"
1296
+ "GitHub returned incomplete check-run evidence."
1233
1297
  )
1234
1298
  if run_head_oid != head_oid:
1235
1299
  raise CheckEvidenceCoverageGap(
1236
- "GitHub returned a check run bound to a different head OID"
1300
+ "GitHub returned a check run that is bound to a different head "
1301
+ "object identifier."
1237
1302
  )
1238
1303
  run_ids.add(run_id)
1239
1304
  runs.append(run)
1240
1305
  if len(runs) == total_count:
1241
1306
  return runs
1242
1307
  if not page_runs:
1243
- raise CheckEvidenceCoverageGap("GitHub returned partial check-run evidence")
1244
- raise AssertionError("bounded check-run pagination did not terminate")
1308
+ raise CheckEvidenceCoverageGap(
1309
+ "GitHub returned partial check-run evidence."
1310
+ )
1311
+ raise AssertionError("The bounded check-run pagination did not terminate.")
1245
1312
 
1246
1313
 
1247
1314
  def pr_metadata(
1248
1315
  pull_request: str, target: ExpectedReviewTarget | None
1249
1316
  ) -> dict[str, Any]:
1250
- """Read one PR through the retained target when strict evidence is required."""
1317
+ """Read one pull request through the retained target when strict evidence is required."""
1251
1318
  command = ["pr", "view", pull_request]
1252
1319
  if target is not None:
1253
1320
  command.extend(("--repo", target.repository_argument()))
1254
1321
  command.extend(("--json", FIELDS))
1255
1322
  metadata = json.loads(gh(*command))
1256
1323
  if not isinstance(metadata, dict):
1257
- raise TypeError("GitHub returned an invalid pull-request object")
1324
+ raise TypeError("GitHub returned a pull-request object that is not valid.")
1258
1325
  return metadata
1259
1326
 
1260
1327
 
@@ -1263,33 +1330,33 @@ def main(argv: Sequence[str] | None = None) -> int:
1263
1330
  parser.add_argument(
1264
1331
  "--expected-base-oid",
1265
1332
  metavar="BASE_OID",
1266
- help="immutable base revision returned by resolve_pr.py",
1333
+ help="Use the immutable base revision from 'resolve_pr.py'.",
1267
1334
  )
1268
1335
  parser.add_argument(
1269
1336
  "--expected-head-oid",
1270
1337
  metavar="HEAD_OID",
1271
- help="immutable head revision returned by resolve_pr.py",
1338
+ help="Use the immutable head revision from 'resolve_pr.py'.",
1272
1339
  )
1273
1340
  parser.add_argument(
1274
1341
  "--expected-host",
1275
1342
  metavar="HOST",
1276
- help="canonical GitHub host returned by resolve_pr.py",
1343
+ help="Use the canonical GitHub host from 'resolve_pr.py'.",
1277
1344
  )
1278
1345
  parser.add_argument(
1279
1346
  "--expected-repository",
1280
1347
  metavar="OWNER/REPOSITORY",
1281
- help="canonical GitHub repository returned by resolve_pr.py",
1348
+ help="Use the canonical GitHub repository from 'resolve_pr.py'.",
1282
1349
  )
1283
1350
  parser.add_argument(
1284
1351
  "--expected-pr-number",
1285
1352
  metavar="NUMBER",
1286
1353
  type=int,
1287
- help="canonical pull-request number returned by resolve_pr.py",
1354
+ help="Use the canonical pull-request number from 'resolve_pr.py'.",
1288
1355
  )
1289
1356
  parser.add_argument(
1290
1357
  "--expected-pr-url",
1291
1358
  metavar="URL",
1292
- help="canonical pull-request URL returned by resolve_pr.py",
1359
+ help="Use the canonical pull-request URL from 'resolve_pr.py'.",
1293
1360
  )
1294
1361
  parser.add_argument("pull_request", metavar="PR_NUMBER_OR_URL")
1295
1362
  arguments = parser.parse_args(argv)
@@ -1312,11 +1379,12 @@ def main(argv: Sequence[str] | None = None) -> int:
1312
1379
  if target is not None:
1313
1380
  if requested != target.number:
1314
1381
  raise RuntimeError(
1315
- "requested pull request does not match the expected target number"
1382
+ "The requested pull request does not match the expected target "
1383
+ "number."
1316
1384
  )
1317
1385
  if pull_request.startswith("https://") and pull_request != target.url:
1318
1386
  raise RuntimeError(
1319
- "requested pull request does not match the expected target URL"
1387
+ "The requested pull request does not match the expected target URL."
1320
1388
  )
1321
1389
  metadata = pr_metadata(pull_request, target)
1322
1390
  metadata_problem = metadata_error(
@@ -1337,15 +1405,18 @@ def main(argv: Sequence[str] | None = None) -> int:
1337
1405
  or not isinstance(number, int)
1338
1406
  or not isinstance(url, str)
1339
1407
  ):
1340
- raise TypeError("GitHub returned incomplete repository or PR identity")
1408
+ raise TypeError(
1409
+ "GitHub returned an incomplete repository or pull-request identity."
1410
+ )
1341
1411
  pull_repository = repository_from_pr_url(url, number)
1342
1412
  if pull_repository.casefold() != repository.casefold():
1343
1413
  raise RuntimeError(
1344
- f"pull request {url} does not belong to current repository {repository}"
1414
+ f"Pull request '{url}' is not in the current repository '{repository}'."
1345
1415
  )
1346
1416
  if number != requested:
1347
1417
  raise RuntimeError(
1348
- "GitHub returned a pull request different from the requested identifier"
1418
+ "GitHub returned a pull request that is different from the requested "
1419
+ "identifier."
1349
1420
  )
1350
1421
  identity = immutable_identity(
1351
1422
  metadata,
@@ -1410,7 +1481,7 @@ def main(argv: Sequence[str] | None = None) -> int:
1410
1481
  )
1411
1482
  )
1412
1483
  if not isinstance(checks, list):
1413
- raise RuntimeError("GitHub returned invalid check evidence")
1484
+ raise RuntimeError("GitHub returned check evidence that is not valid.")
1414
1485
  final_metadata = pr_metadata(pull_request, target)
1415
1486
  final_problem = metadata_error(
1416
1487
  final_metadata, require_immutable_identity=require_immutable_identity
@@ -1425,13 +1496,13 @@ def main(argv: Sequence[str] | None = None) -> int:
1425
1496
  )
1426
1497
  if final_identity != identity:
1427
1498
  raise RuntimeError(
1428
- "immutable pull-request identity changed while collecting evidence"
1499
+ "The immutable pull-request identity changed during evidence collection."
1429
1500
  )
1430
1501
  ensure_expected_identity(final_identity, expected)
1431
1502
  ensure_expected_target(final_identity, target)
1432
1503
  final_scope = review_scope(final_metadata) if expected is not None else None
1433
1504
  if final_scope != reviewed_scope:
1434
- raise RuntimeError("review scope changed while collecting evidence")
1505
+ raise RuntimeError("The review scope changed during evidence collection.")
1435
1506
  final_linked_requirements = (
1436
1507
  linked_requirements(final_metadata, linked_requirement_budget)
1437
1508
  if expected is not None
@@ -1439,7 +1510,7 @@ def main(argv: Sequence[str] | None = None) -> int:
1439
1510
  )
1440
1511
  if final_linked_requirements != reviewed_linked_requirements:
1441
1512
  raise RuntimeError(
1442
- "linked issue requirements changed while collecting evidence"
1513
+ "The linked issue requirements changed during evidence collection."
1443
1514
  )
1444
1515
  except ChangedPathCoverageGap as error:
1445
1516
  structured_error("changed path coverage gap", str(error))