@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
- """Compute the two required pull-request diff lenses."""
2
+ """Calculate the two Git ranges for a pull-request review."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
@@ -32,7 +32,9 @@ def git(*arguments: str) -> str:
32
32
  check=False,
33
33
  )
34
34
  if result.returncode != 0:
35
- raise RuntimeError(result.stderr.strip() or f"git {' '.join(arguments)} failed")
35
+ raise RuntimeError(
36
+ result.stderr.strip() or f"The git {' '.join(arguments)} command failed."
37
+ )
36
38
  return result.stdout.strip()
37
39
 
38
40
 
@@ -42,8 +44,8 @@ def main(argv: Sequence[str] | None = None) -> int:
42
44
  parser.add_argument("head_ref", metavar="HEAD_OID")
43
45
  arguments = parser.parse_args(argv)
44
46
  try:
45
- base_ref = require_commit_oid(arguments.base_ref, "base OID")
46
- head_ref = require_commit_oid(arguments.head_ref, "head OID")
47
+ base_ref = require_commit_oid(arguments.base_ref, "base object identifier")
48
+ head_ref = require_commit_oid(arguments.head_ref, "head object identifier")
47
49
  require_complete_git_history()
48
50
  git("rev-parse", "--verify", f"{base_ref}^{{commit}}")
49
51
  git("rev-parse", "--verify", f"{head_ref}^{{commit}}")
@@ -27,11 +27,15 @@ from skills._cli import (
27
27
 
28
28
  SNAPSHOT_COMMAND_TIMEOUT_SECONDS = 30.0
29
29
  BOUNDED_MATERIALIZE_TIMEOUT_SECONDS = 600.0
30
+ MATERIALIZE_ERROR = "The helper cannot materialize the immutable pull-request snapshot."
31
+ QUOTA_ERROR = "The host cannot enforce the immutable pull-request snapshot size limit."
32
+ REMOVE_ERROR = "The helper cannot remove the immutable pull-request snapshot."
33
+ UNMANAGED_ROOT_ERROR = "The helper does not permit snapshot operations outside the managed temporary directory."
30
34
 
31
35
 
32
36
  @dataclass(frozen=True)
33
37
  class MaterializedSnapshot:
34
- """A detached source tree bound to one reviewed GitHub pull request."""
38
+ """This record binds a detached source tree to one reviewed GitHub pull request."""
35
39
 
36
40
  root: Path
37
41
  source_path: Path
@@ -78,11 +82,9 @@ def _git(
78
82
  ["git", *git_read_arguments(), *arguments], **command_options
79
83
  )
80
84
  except subprocess.SubprocessError as error:
81
- raise RuntimeError(
82
- "cannot materialize the immutable pull-request snapshot"
83
- ) from error
85
+ raise RuntimeError(MATERIALIZE_ERROR) from error
84
86
  if result.returncode not in accepted_codes:
85
- raise RuntimeError("cannot materialize the immutable pull-request snapshot")
87
+ raise RuntimeError(MATERIALIZE_ERROR)
86
88
  return result.stdout.strip() if isinstance(result.stdout, str) else ""
87
89
 
88
90
 
@@ -97,32 +99,31 @@ def _hdiutil(*arguments: str) -> None:
97
99
  check=False,
98
100
  )
99
101
  except (OSError, RuntimeError, subprocess.SubprocessError) as error:
100
- raise RuntimeError(
101
- "host cannot enforce the immutable pull-request snapshot size limit"
102
- ) from error
102
+ raise RuntimeError(QUOTA_ERROR) from error
103
103
  if result.returncode != 0:
104
- raise RuntimeError(
105
- "host cannot enforce the immutable pull-request snapshot size limit"
106
- )
104
+ raise RuntimeError(QUOTA_ERROR)
107
105
 
108
106
 
109
107
  def _mount_tmpfs(source: Path, maximum_bytes: int) -> bool:
110
108
  """Mount a Linux tmpfs whose total capacity is the snapshot quota.
111
109
 
112
- Returns True only when the mount is actually enforced; the caller must
113
- verify ``source.is_mount()`` is not relied on elsewhere. On any failure
114
- (no ``mount`` binary, no privilege, mount rejection) the caller uses the
115
- bounded user/mount-namespace fallback or fails closed.
110
+ Return True only when the mount enforces the quota. Do not use
111
+ ``source.is_mount()`` elsewhere to infer this result. If the host cannot
112
+ find or run ``mount``, return False. If the host rejects the mount, return
113
+ False. The caller then uses the bounded user and mount namespace or fails
114
+ closed.
116
115
  """
117
116
  if sys.platform != "linux" or shutil.which("mount") is None:
118
117
  return False
119
118
  maximum_kibibytes = maximum_bytes // 1024
120
119
  if maximum_kibibytes < 1:
121
- raise RuntimeError("immutable pull-request snapshot has no usable disk space")
120
+ raise RuntimeError(
121
+ "The immutable pull-request snapshot has no usable disk space."
122
+ )
122
123
  try:
123
124
  source.mkdir()
124
125
  except FileExistsError:
125
- # Directory already exists from a prior materialization; reuse it.
126
+ # If a prior materialization created the directory, use it again.
126
127
  pass
127
128
  except OSError:
128
129
  return False
@@ -150,15 +151,17 @@ def _mount_tmpfs(source: Path, maximum_bytes: int) -> bool:
150
151
  def _create_quota_volume(root: Path, maximum_bytes: int) -> Path | None:
151
152
  """Return a bounded source directory, or None for the Linux bounded fallback.
152
153
 
153
- darwin: attach a sparse HFS+ volume whose total capacity is the quota.
154
- linux: mount a tmpfs whose total capacity is the quota when the host is
155
- privileged; otherwise return None so the caller materializes the
156
- snapshot inside a bounded user/mount namespace.
157
- other: fail closed — the host cannot enforce the snapshot size limit.
154
+ On macOS, attach a sparse HFS+ volume whose total capacity is the quota.
155
+ On Linux, mount a tmpfs when the host has the required authority. Otherwise,
156
+ return None so the caller can materialize the snapshot inside a bounded user
157
+ and mount namespace. On other systems, fail closed because the host cannot
158
+ enforce the snapshot size limit.
158
159
  """
159
160
  maximum_kibibytes = maximum_bytes // 1024
160
161
  if maximum_kibibytes < 1:
161
- raise RuntimeError("immutable pull-request snapshot has no usable disk space")
162
+ raise RuntimeError(
163
+ "The immutable pull-request snapshot has no usable disk space."
164
+ )
162
165
  if sys.platform == "darwin":
163
166
  image = root / "snapshot.sparseimage"
164
167
  source = root / "source"
@@ -173,7 +176,7 @@ def _create_quota_volume(root: Path, maximum_bytes: int) -> Path | None:
173
176
  "-fs",
174
177
  "Case-sensitive HFS+",
175
178
  "-volname",
176
- "Athena PR Review",
179
+ "Athena pull-request review",
177
180
  "-nospotlight",
178
181
  str(image),
179
182
  )
@@ -186,9 +189,7 @@ def _create_quota_volume(root: Path, maximum_bytes: int) -> Path | None:
186
189
  str(image),
187
190
  )
188
191
  if not source.is_mount():
189
- raise RuntimeError(
190
- "host cannot enforce the immutable pull-request snapshot size limit"
191
- )
192
+ raise RuntimeError(QUOTA_ERROR)
192
193
  return source
193
194
  if sys.platform.startswith("linux"):
194
195
  source = root / "source"
@@ -197,13 +198,11 @@ def _create_quota_volume(root: Path, maximum_bytes: int) -> Path | None:
197
198
  try:
198
199
  source.rmdir()
199
200
  except OSError:
200
- # The mount may have partially created the directory; a failed
201
- # rmdir is not fatal - the caller treats None as a fallback.
201
+ # The mount can create the directory before it fails.
202
+ # If rmdir fails, the caller uses None to select the other method.
202
203
  pass
203
204
  return None
204
- raise RuntimeError(
205
- "host cannot enforce the immutable pull-request snapshot size limit"
206
- )
205
+ raise RuntimeError(QUOTA_ERROR)
207
206
 
208
207
 
209
208
  def _detach_volume(source: Path) -> None:
@@ -229,41 +228,38 @@ def _detach_volume(source: Path) -> None:
229
228
  check=False,
230
229
  )
231
230
  if result.returncode != 0:
232
- raise RuntimeError("cannot remove the immutable pull-request snapshot")
231
+ raise RuntimeError(REMOVE_ERROR)
233
232
  except (OSError, RuntimeError, subprocess.SubprocessError) as error:
234
- raise RuntimeError(
235
- "cannot remove the immutable pull-request snapshot"
236
- ) from error
233
+ raise RuntimeError(REMOVE_ERROR) from error
237
234
  return
238
- raise RuntimeError("cannot remove the immutable pull-request snapshot")
235
+ raise RuntimeError(REMOVE_ERROR)
239
236
 
240
237
 
241
238
  def _detach_best_effort(source: Path) -> None:
242
- """Detach a quota volume without masking a prior failure."""
239
+ """Detach the quota volume. Do not let a detach failure hide an earlier failure."""
243
240
  try:
244
241
  _detach_volume(source)
245
242
  except RuntimeError:
246
- # Detach is best-effort; a prior failure must not be masked by a
247
- # secondary cleanup error.
243
+ # Do not let this cleanup error hide an earlier failure.
248
244
  pass
249
245
 
250
246
 
251
247
  def _require_base_ref(base_ref: str) -> str:
252
248
  """Validate the GitHub base branch before it becomes a fetch refspec."""
253
249
  if not base_ref or base_ref.startswith("-") or ".." in base_ref:
254
- raise RuntimeError("GitHub returned an invalid pull-request base ref")
250
+ raise RuntimeError("GitHub returned a pull-request base ref that is not valid.")
255
251
  _git("check-ref-format", "--branch", base_ref)
256
252
  return base_ref
257
253
 
258
254
 
259
255
  def _repository_size(path: Path, *, maximum_bytes: int | None = None) -> int:
260
- """Return the size of a repository within 90 percent of free disk space."""
256
+ """Return the repository size and enforce the applicable size limit."""
261
257
  if maximum_bytes is None:
262
258
  try:
263
259
  maximum_bytes = (shutil.disk_usage(path).free * 9) // 10
264
260
  except OSError as error:
265
261
  raise RuntimeError(
266
- "cannot inspect the immutable pull-request snapshot"
262
+ "The helper cannot inspect the immutable pull-request snapshot."
267
263
  ) from error
268
264
  total = 0
269
265
  for entry in path.rglob("*"):
@@ -271,13 +267,13 @@ def _repository_size(path: Path, *, maximum_bytes: int | None = None) -> int:
271
267
  details = entry.lstat()
272
268
  except OSError as error:
273
269
  raise RuntimeError(
274
- "cannot inspect the immutable pull-request snapshot"
270
+ "The helper cannot inspect the immutable pull-request snapshot."
275
271
  ) from error
276
272
  if stat.S_ISREG(details.st_mode):
277
273
  total += details.st_size
278
274
  if total > maximum_bytes:
279
275
  raise RuntimeError(
280
- "immutable pull-request snapshot exceeds the safe size limit"
276
+ "The immutable pull-request snapshot exceeds the safe size limit."
281
277
  )
282
278
  return total
283
279
 
@@ -296,7 +292,7 @@ def _verify_no_promisor_configuration(repository: Path) -> None:
296
292
  )
297
293
  if value:
298
294
  raise RuntimeError(
299
- "immutable pull-request snapshot must not use partial clone configuration"
295
+ "The immutable pull-request snapshot must not use partial-clone configuration."
300
296
  )
301
297
  promisor = _git(
302
298
  "config",
@@ -309,12 +305,12 @@ def _verify_no_promisor_configuration(repository: Path) -> None:
309
305
  )
310
306
  if promisor:
311
307
  raise RuntimeError(
312
- "immutable pull-request snapshot must not use promisor configuration"
308
+ "The immutable pull-request snapshot must not use promisor configuration."
313
309
  )
314
310
 
315
311
 
316
312
  def _require_commit(repository: Path, revision: str, label: str) -> str:
317
- """Verify one fetched ref resolves exactly to its captured commit OID."""
313
+ """Verify that one fetched ref resolves to its captured commit object identifier."""
318
314
  resolved = _git(
319
315
  "rev-parse",
320
316
  "--verify",
@@ -326,7 +322,7 @@ def _require_commit(repository: Path, revision: str, label: str) -> str:
326
322
 
327
323
 
328
324
  def _make_read_only(root: Path) -> None:
329
- """Remove write bits from the completed snapshot without following symlinks."""
325
+ """Remove write bits from the completed snapshot without following symbolic links."""
330
326
  entries = sorted(root.rglob("*"), key=lambda entry: len(entry.parts), reverse=True)
331
327
  for entry in entries:
332
328
  if entry.is_symlink():
@@ -339,7 +335,7 @@ def _make_read_only(root: Path) -> None:
339
335
  entry.chmod(0o555 if mode & stat.S_IXUSR else 0o444)
340
336
  except OSError as error:
341
337
  raise RuntimeError(
342
- "cannot make the immutable pull-request snapshot read-only"
338
+ "The helper cannot make the immutable pull-request snapshot read-only."
343
339
  ) from error
344
340
  root.chmod(0o555)
345
341
 
@@ -356,11 +352,11 @@ def _acquire_into(
356
352
  template: Path,
357
353
  maximum_bytes: int,
358
354
  ) -> tuple[str, str]:
359
- """Fetch only the captured base branch and PR head into a source directory.
355
+ """Fetch only the captured base branch and pull-request head.
360
356
 
361
- The source directory is provided by the caller (a quota volume or a
362
- bounded tmpfs). Returns ``(merge_base, tree_oid)`` after verifying every
363
- immutable binding against the captured OIDs.
357
+ The caller provides a source directory in a quota volume or bounded tmpfs.
358
+ Verify each immutable binding against the captured object identifiers.
359
+ Then, return ``(merge_base, tree_oid)``.
364
360
  """
365
361
  _git(
366
362
  "-c",
@@ -405,15 +401,26 @@ def _acquire_into(
405
401
  _git("rev-parse", "--is-shallow-repository", cwd=source, capture_output=True)
406
402
  != "false"
407
403
  ):
408
- raise RuntimeError("immutable pull-request snapshot requires complete history")
409
- if _require_commit(source, "refs/athena/base", "fetched base OID") != base_oid:
410
- raise RuntimeError("fetched base ref does not match the captured base OID")
404
+ raise RuntimeError(
405
+ "The immutable pull-request snapshot requires complete history."
406
+ )
407
+ if (
408
+ _require_commit(source, "refs/athena/base", "fetched base object identifier")
409
+ != base_oid
410
+ ):
411
+ raise RuntimeError(
412
+ "The fetched base ref does not match the captured base object identifier."
413
+ )
411
414
  if (
412
- _require_commit(source, f"refs/athena/pr/{number}/head", "fetched head OID")
415
+ _require_commit(
416
+ source,
417
+ f"refs/athena/pr/{number}/head",
418
+ "fetched head object identifier",
419
+ )
413
420
  != head_oid
414
421
  ):
415
422
  raise RuntimeError(
416
- "fetched pull-request ref does not match the captured head OID"
423
+ "The fetched pull-request ref does not match the captured head object identifier."
417
424
  )
418
425
  merge_bases = _git(
419
426
  "merge-base",
@@ -425,15 +432,15 @@ def _acquire_into(
425
432
  ).splitlines()
426
433
  if len(merge_bases) != 1:
427
434
  raise RuntimeError(
428
- "immutable pull-request snapshot requires one unambiguous merge base"
435
+ "The immutable pull-request snapshot requires one unambiguous merge base."
429
436
  )
430
437
  merge_base = require_commit_oid(merge_bases[0], "immutable merge base")
431
- tree_oid = _require_commit(source, head_oid, "fetched head OID")
438
+ tree_oid = _require_commit(source, head_oid, "fetched head object identifier")
432
439
  tree_oid = _git(
433
440
  "rev-parse", f"{tree_oid}^{{tree}}", cwd=source, capture_output=True
434
441
  )
435
442
  if COMMIT_OID.fullmatch(tree_oid) is None:
436
- raise RuntimeError("Git returned an invalid immutable head tree")
443
+ raise RuntimeError("Git returned an immutable head tree that is not valid.")
437
444
  _git(
438
445
  "-c",
439
446
  f"core.hooksPath={hooks}",
@@ -450,15 +457,16 @@ def _acquire_into(
450
457
 
451
458
 
452
459
  def _bounded_materialize_main(arguments: Sequence[str]) -> int:
453
- """Materialize inside a bounded user/mount namespace (internal entry).
454
-
455
- This runs as the child of ``unshare -rm`` on Linux. It mounts a tmpfs
456
- whose total capacity is the snapshot quota, acquires the snapshot inside
457
- that bound, copies the verified read-only tree to a host-visible path, and
458
- prints the result as JSON. Exit 2 means the quota boundary itself could
459
- not be established; exit 1 means materialization failed.
460
+ """Materialize inside a bounded user and mount namespace.
461
+
462
+ This helper runs as the child of ``unshare -rm`` on Linux. The helper mounts
463
+ a tmpfs whose total capacity is the snapshot quota. It materializes the
464
+ snapshot in the size-limited file system. Then, it copies the verified
465
+ read-only tree to a path that the host can read. Exit 2 means that the helper
466
+ could not establish the quota boundary. Exit 1 means that materialization
467
+ failed.
460
468
  """
461
- parser = argument_parser(description="Bounded snapshot materialization.")
469
+ parser = argument_parser(description="Materialize a bounded pull-request snapshot.")
462
470
  parser.add_argument("--root", required=True, metavar="ROOT")
463
471
  parser.add_argument("--repository-url", required=True, metavar="URL")
464
472
  parser.add_argument("--pr-number", required=True, type=int, metavar="NUMBER")
@@ -470,22 +478,20 @@ def _bounded_materialize_main(arguments: Sequence[str]) -> int:
470
478
  root = Path(parsed.root).resolve()
471
479
  temporary_root = Path(tempfile.gettempdir()).resolve()
472
480
  if root.parent != temporary_root or not root.name.startswith("athena-pr-review-"):
473
- print(
474
- "refusing to materialize outside the managed temporary directory",
475
- file=sys.stderr,
476
- )
481
+ print(UNMANAGED_ROOT_ERROR, file=sys.stderr)
477
482
  return 1
478
483
  try:
479
- canonical_base = require_commit_oid(parsed.base_oid, "captured base OID")
480
- canonical_head = require_commit_oid(parsed.head_oid, "captured head OID")
484
+ canonical_base = require_commit_oid(
485
+ parsed.base_oid, "captured base object identifier"
486
+ )
487
+ canonical_head = require_commit_oid(
488
+ parsed.head_oid, "captured head object identifier"
489
+ )
481
490
  canonical_base_ref = _require_base_ref(parsed.base_ref)
482
491
  bounded = root / "bounded"
483
492
  bounded.mkdir()
484
493
  if not _mount_tmpfs(bounded, parsed.maximum_bytes):
485
- print(
486
- "host cannot enforce the immutable pull-request snapshot size limit",
487
- file=sys.stderr,
488
- )
494
+ print(QUOTA_ERROR, file=sys.stderr)
489
495
  return 2
490
496
  merge_base, tree_oid = _acquire_into(
491
497
  bounded,
@@ -528,20 +534,18 @@ def _bounded_materialize(
528
534
  base_oid: str,
529
535
  head_oid: str,
530
536
  ) -> MaterializedSnapshot:
531
- """Materialize a snapshot inside a bounded Linux user/mount namespace.
532
-
533
- Spawns this helper under ``unshare -rm`` (rootful within the new namespace
534
- but unprivileged on the host), where the child mounts a tmpfs whose total
535
- capacity is the snapshot quota. Every fetch/checkout write is bounded by
536
- that filesystem; the verified tree is copied to a host-visible read-only
537
- path before the namespace exits. Fails closed when ``unshare`` or a tmpfs
538
- mount is unavailable.
537
+ """Materialize a snapshot inside a bounded Linux user and mount namespace.
538
+
539
+ Run this helper under ``unshare -rm``. The child has root authority only in
540
+ the new namespace. The child mounts a tmpfs whose total capacity is the
541
+ snapshot quota. The file system bounds each fetch and checkout write. Before
542
+ the namespace exits, the child copies the verified tree to a read-only path
543
+ that the host can read. If ``unshare`` or a tmpfs mount is not available,
544
+ the helper fails closed.
539
545
  """
540
546
  unshare = shutil.which("unshare")
541
547
  if unshare is None:
542
- raise RuntimeError(
543
- "host cannot enforce the immutable pull-request snapshot size limit"
544
- )
548
+ raise RuntimeError(QUOTA_ERROR)
545
549
  command = [
546
550
  unshare,
547
551
  "-rm",
@@ -573,26 +577,20 @@ def _bounded_materialize(
573
577
  timeout=BOUNDED_MATERIALIZE_TIMEOUT_SECONDS,
574
578
  )
575
579
  except subprocess.TimeoutExpired as error:
576
- raise RuntimeError(
577
- "cannot materialize the immutable pull-request snapshot"
578
- ) from error
580
+ raise RuntimeError(MATERIALIZE_ERROR) from error
579
581
  except (OSError, RuntimeError, subprocess.SubprocessError) as error:
580
- raise RuntimeError(
581
- "host cannot enforce the immutable pull-request snapshot size limit"
582
- ) from error
582
+ raise RuntimeError(QUOTA_ERROR) from error
583
583
  if result.returncode == 2:
584
- raise RuntimeError(
585
- "host cannot enforce the immutable pull-request snapshot size limit"
586
- )
584
+ raise RuntimeError(QUOTA_ERROR)
587
585
  if result.returncode != 0:
588
- raise RuntimeError("cannot materialize the immutable pull-request snapshot")
586
+ raise RuntimeError(MATERIALIZE_ERROR)
589
587
  try:
590
588
  lines = [line for line in result.stdout.splitlines() if line.strip()]
591
589
  if not lines:
592
- raise RuntimeError("cannot materialize the immutable pull-request snapshot")
590
+ raise RuntimeError(MATERIALIZE_ERROR)
593
591
  record = json.loads(lines[-1])
594
592
  if not isinstance(record, dict):
595
- raise TypeError("cannot materialize the immutable pull-request snapshot")
593
+ raise TypeError(MATERIALIZE_ERROR)
596
594
  source_path = Path(str(record["source_path"]))
597
595
  merge_base = require_commit_oid(record["merge_base"], "immutable merge base")
598
596
  tree_oid = require_commit_oid(record["tree_oid"], "immutable head tree")
@@ -603,11 +601,9 @@ def _bounded_materialize(
603
601
  json.JSONDecodeError,
604
602
  RuntimeError,
605
603
  ) as error:
606
- raise RuntimeError(
607
- "cannot materialize the immutable pull-request snapshot"
608
- ) from error
604
+ raise RuntimeError(MATERIALIZE_ERROR) from error
609
605
  if source_path != root / "source" or not source_path.is_dir():
610
- raise RuntimeError("cannot materialize the immutable pull-request snapshot")
606
+ raise RuntimeError(MATERIALIZE_ERROR)
611
607
  return MaterializedSnapshot(
612
608
  root=root, source_path=source_path, merge_base=merge_base, tree_oid=tree_oid
613
609
  )
@@ -616,12 +612,12 @@ def _bounded_materialize(
616
612
  def materialize_snapshot(
617
613
  *, repository: str, number: int, base_ref: str, base_oid: str, head_oid: str
618
614
  ) -> MaterializedSnapshot:
619
- """Fetch only a captured base branch and PR head into a fresh repository."""
615
+ """Materialize a captured base branch and pull-request head in a new repository."""
620
616
  canonical_repository = require_github_repository(repository, "GitHub repository")
621
617
  if isinstance(number, bool) or not isinstance(number, int) or number < 1:
622
- raise RuntimeError("pull-request number must be positive")
623
- canonical_base = require_commit_oid(base_oid, "captured base OID")
624
- canonical_head = require_commit_oid(head_oid, "captured head OID")
618
+ raise RuntimeError("The pull-request number must be positive.")
619
+ canonical_base = require_commit_oid(base_oid, "captured base object identifier")
620
+ canonical_head = require_commit_oid(head_oid, "captured head object identifier")
625
621
  canonical_base_ref = _require_base_ref(base_ref)
626
622
  root = Path(tempfile.mkdtemp(prefix="athena-pr-review-"))
627
623
  template = root / "empty-template"
@@ -632,7 +628,7 @@ def materialize_snapshot(
632
628
  maximum_snapshot_bytes = (shutil.disk_usage(root).free * 9) // 10
633
629
  if maximum_snapshot_bytes < 1:
634
630
  raise RuntimeError(
635
- "immutable pull-request snapshot has no usable disk space"
631
+ "The immutable pull-request snapshot has no usable disk space."
636
632
  )
637
633
  source = _create_quota_volume(root, maximum_snapshot_bytes)
638
634
  if source is None:
@@ -659,9 +655,7 @@ def materialize_snapshot(
659
655
  _make_read_only(root)
660
656
  except (OSError, subprocess.TimeoutExpired):
661
657
  remove_snapshot(root)
662
- raise RuntimeError(
663
- "cannot materialize the immutable pull-request snapshot"
664
- ) from None
658
+ raise RuntimeError(MATERIALIZE_ERROR) from None
665
659
  except BaseException:
666
660
  remove_snapshot(root)
667
661
  raise
@@ -671,15 +665,13 @@ def materialize_snapshot(
671
665
 
672
666
 
673
667
  def remove_snapshot(root: Path) -> None:
674
- """Remove a snapshot that this helper created after host inspection ends."""
668
+ """After host inspection ends, remove a snapshot that this helper materialized."""
675
669
  resolved = root.resolve()
676
670
  temporary_root = Path(tempfile.gettempdir()).resolve()
677
671
  if resolved.parent != temporary_root or not resolved.name.startswith(
678
672
  "athena-pr-review-"
679
673
  ):
680
- raise RuntimeError(
681
- "refusing to remove a snapshot outside the managed temporary directory"
682
- )
674
+ raise RuntimeError(UNMANAGED_ROOT_ERROR)
683
675
  source = resolved / "source"
684
676
  if source.is_mount():
685
677
  _detach_volume(source)
@@ -690,7 +682,7 @@ def remove_snapshot(root: Path) -> None:
690
682
  if candidate.exists() and not candidate.is_symlink():
691
683
  candidate.chmod(0o700)
692
684
  if not callable(function):
693
- raise TypeError("cannot remove the immutable pull-request snapshot")
685
+ raise TypeError(REMOVE_ERROR)
694
686
  function(path)
695
687
 
696
688
  shutil.rmtree(resolved, onexc=make_removable)
@@ -1,4 +1,4 @@
1
- """Validate pull-request identifiers shared by PR review helpers."""
1
+ """Validate pull-request identifiers shared by pull-request review helpers."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -12,45 +12,51 @@ GITHUB_HOST = "github.com"
12
12
 
13
13
 
14
14
  def validate_pr_identifier(value: str) -> None:
15
- """Require a positive PR number or canonical GitHub pull-request URL."""
15
+ """Require a positive pull-request number or a canonical GitHub pull-request URL."""
16
16
  if (value.isascii() and value.isdigit() and int(value) > 0) or PR_URL.fullmatch(
17
17
  value
18
18
  ):
19
19
  return
20
- raise RuntimeError(f"invalid pull-request identifier: {value!r}")
20
+ raise RuntimeError(f"The pull-request identifier is not valid: {value!r}.")
21
21
 
22
22
 
23
23
  def require_commit_oid(value: object, label: str) -> str:
24
- """Return one canonical immutable commit OID or fail closed."""
24
+ """Return one canonical immutable commit object identifier or fail closed."""
25
25
  if not isinstance(value, str) or COMMIT_OID.fullmatch(value) is None:
26
- raise RuntimeError(f"{label} must be a lowercase 40-hex Git commit OID")
26
+ raise RuntimeError(
27
+ f"The value for {label} must be a Git commit object identifier that "
28
+ "contains 40 lowercase hexadecimal characters."
29
+ )
27
30
  return value
28
31
 
29
32
 
30
33
  def require_github_repository(value: object, label: str) -> str:
31
- """Return a canonical owner/repository target or reject unsafe input."""
34
+ """Return a canonical GitHub repository or reject unsafe input."""
32
35
  if not isinstance(value, str) or GITHUB_REPOSITORY.fullmatch(value) is None:
33
- raise RuntimeError(f"{label} must be a canonical GitHub owner/repository")
36
+ raise RuntimeError(
37
+ "The value for "
38
+ f"{label} must use the canonical GitHub OWNER/REPOSITORY format."
39
+ )
34
40
  return value
35
41
 
36
42
 
37
43
  def require_github_host(value: object, label: str) -> str:
38
44
  """Return the supported canonical GitHub hostname or fail closed."""
39
45
  if value != GITHUB_HOST:
40
- raise RuntimeError(f"{label} must be {GITHUB_HOST}")
46
+ raise RuntimeError(f"The value for {label} must be '{GITHUB_HOST}'.")
41
47
  return GITHUB_HOST
42
48
 
43
49
 
44
50
  def canonical_pull_request_url(repository: object, number: object) -> str:
45
- """Return one exact public-GitHub pull-request URL from trusted identity."""
51
+ """Return one exact public GitHub pull-request URL from a trusted identity."""
46
52
  canonical_repository = require_github_repository(repository, "repository")
47
53
  if isinstance(number, bool) or not isinstance(number, int) or number < 1:
48
- raise RuntimeError("pull-request number must be a positive integer")
54
+ raise RuntimeError("The pull-request number must be a positive integer.")
49
55
  return f"https://{GITHUB_HOST}/{canonical_repository}/pull/{number}"
50
56
 
51
57
 
52
58
  def pull_request_number(value: str) -> int:
53
- """Return the positive number encoded by a validated PR identifier."""
59
+ """Return the positive number in a validated pull-request identifier."""
54
60
  validate_pr_identifier(value)
55
61
  if value.isdigit():
56
62
  return int(value)
@@ -64,17 +70,23 @@ def require_canonical_pull_request_url(
64
70
  """Require the exact canonical URL for a retained pull-request identity."""
65
71
  canonical_url = canonical_pull_request_url(repository, number)
66
72
  if value != canonical_url:
67
- raise RuntimeError(f"{label} must be the canonical GitHub pull-request URL")
73
+ raise RuntimeError(
74
+ f"The value for {label} must be the canonical GitHub pull-request URL."
75
+ )
68
76
  return canonical_url
69
77
 
70
78
 
71
79
  def repository_from_pr_url(url: str, number: int) -> str:
72
- """Return owner/repository after validating a canonical PR URL and number."""
80
+ """Return the repository from a canonical pull-request URL and number."""
73
81
  parsed_url = urlparse(url)
74
82
  path_parts = parsed_url.path.strip("/").split("/")
75
83
  if len(path_parts) != 4 or path_parts[2] != "pull" or path_parts[3] != str(number):
76
- raise RuntimeError(f"GitHub returned invalid pull-request URL: {url}")
84
+ raise RuntimeError(
85
+ f"GitHub returned a pull-request URL that is not valid: '{url}'."
86
+ )
77
87
  repository = "/".join(path_parts[:2])
78
88
  if url != canonical_pull_request_url(repository, number):
79
- raise RuntimeError(f"GitHub returned invalid pull-request URL: {url}")
89
+ raise RuntimeError(
90
+ f"GitHub returned a pull-request URL that is not valid: '{url}'."
91
+ )
80
92
  return repository