@homericintelligence/athena-opencode 0.5.0 → 0.5.2

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 (146) 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 +33 -22
  6. package/skills/_plugin.json +1 -0
  7. package/skills/_support/docs/dependency-resolution.md +203 -0
  8. package/skills/_support/docs/policies/development.md +131 -0
  9. package/skills/_support/docs/policies/evidence-integrity.md +40 -0
  10. package/skills/_support/docs/principles/README.md +565 -0
  11. package/skills/_support/docs/principles/details/p001-kiss.md +114 -0
  12. package/skills/_support/docs/principles/details/p002-yagni.md +109 -0
  13. package/skills/_support/docs/principles/details/p003-dry.md +126 -0
  14. package/skills/_support/docs/principles/details/p004-solid.md +165 -0
  15. package/skills/_support/docs/principles/details/p005-modularity.md +123 -0
  16. package/skills/_support/docs/principles/details/p006-principle-of-least-astonishment.md +115 -0
  17. package/skills/_support/docs/principles/details/p007-subtraction-over-addition.md +113 -0
  18. package/skills/_support/docs/principles/details/p008-understand-before-subtracting.md +115 -0
  19. package/skills/_support/docs/principles/details/p009-general-mechanisms-over-special-cases.md +108 -0
  20. package/skills/_support/docs/principles/details/p010-scope-fidelity.md +117 -0
  21. package/skills/_support/docs/principles/details/p011-minimal-coherent-change.md +128 -0
  22. package/skills/_support/docs/principles/details/p012-evidence-before-modification.md +115 -0
  23. package/skills/_support/docs/principles/details/p013-avoid-hasty-abstractions.md +112 -0
  24. package/skills/_support/docs/principles/details/p014-preserve-unrequested-behavior.md +110 -0
  25. package/skills/_support/docs/principles/details/p015-architecture-conformance.md +124 -0
  26. package/skills/_support/docs/principles/details/p016-separation-of-concerns.md +125 -0
  27. package/skills/_support/docs/principles/details/p017-high-cohesion-low-coupling.md +130 -0
  28. package/skills/_support/docs/principles/details/p018-information-hiding.md +128 -0
  29. package/skills/_support/docs/principles/details/p019-explicit-contracts.md +134 -0
  30. package/skills/_support/docs/principles/details/p020-executable-architecture.md +130 -0
  31. package/skills/_support/docs/principles/details/p021-evolutionary-and-reversible-design.md +126 -0
  32. package/skills/_support/docs/principles/details/p022-test-behavior-not-implementation.md +129 -0
  33. package/skills/_support/docs/principles/details/p023-parameterized-table-driven-testing.md +129 -0
  34. package/skills/_support/docs/principles/details/p024-boundary-value-testing.md +128 -0
  35. package/skills/_support/docs/principles/details/p025-property-based-testing-for-invariants.md +133 -0
  36. package/skills/_support/docs/principles/details/p026-regression-before-repair.md +134 -0
  37. package/skills/_support/docs/principles/details/p027-deterministic-and-hermetic-tests.md +128 -0
  38. package/skills/_support/docs/principles/details/p028-test-failure-paths.md +133 -0
  39. package/skills/_support/docs/principles/details/p029-generalize-error-policy-preserve-specific-cause.md +131 -0
  40. package/skills/_support/docs/principles/details/p030-nearest-responsible-error-boundary.md +133 -0
  41. package/skills/_support/docs/principles/details/p031-propagate-rather-than-swallow.md +131 -0
  42. package/skills/_support/docs/principles/details/p032-handle-once-preserve-causality.md +137 -0
  43. package/skills/_support/docs/principles/details/p033-state-safe-failure-semantics.md +140 -0
  44. package/skills/_support/docs/principles/details/p034-fail-fast.md +132 -0
  45. package/skills/_support/docs/principles/details/p035-fail-secure-fail-closed.md +137 -0
  46. package/skills/_support/docs/principles/details/p036-graceful-degradation.md +137 -0
  47. package/skills/_support/docs/principles/details/p037-idempotency-before-retry.md +139 -0
  48. package/skills/_support/docs/principles/details/p038-bounded-retry.md +145 -0
  49. package/skills/_support/docs/principles/details/p039-bounded-waiting.md +151 -0
  50. package/skills/_support/docs/principles/details/p040-bounded-resources.md +138 -0
  51. package/skills/_support/docs/principles/details/p041-backpressure-and-load-shedding.md +141 -0
  52. package/skills/_support/docs/principles/details/p042-fault-isolation-bulkheads.md +140 -0
  53. package/skills/_support/docs/principles/details/p043-circuit-breakers.md +148 -0
  54. package/skills/_support/docs/principles/details/p044-atomicity-where-possible.md +141 -0
  55. package/skills/_support/docs/principles/details/p045-compensation-where-atomicity-is-impossible.md +163 -0
  56. package/skills/_support/docs/principles/details/p046-resumability.md +138 -0
  57. package/skills/_support/docs/principles/details/p047-observability-is-part-of-correctness.md +134 -0
  58. package/skills/_support/docs/principles/details/p048-secure-by-design.md +132 -0
  59. package/skills/_support/docs/principles/details/p049-secure-by-default.md +126 -0
  60. package/skills/_support/docs/principles/details/p050-least-privilege.md +123 -0
  61. package/skills/_support/docs/principles/details/p051-complete-mediation.md +124 -0
  62. package/skills/_support/docs/principles/details/p052-separation-of-duties.md +129 -0
  63. package/skills/_support/docs/principles/details/p053-validate-at-trust-boundaries.md +126 -0
  64. package/skills/_support/docs/principles/details/p054-defense-in-depth.md +135 -0
  65. package/skills/_support/docs/principles/details/p055-minimize-attack-surface.md +133 -0
  66. package/skills/_support/docs/principles/details/p056-secrets-stay-out-of-code-and-context.md +137 -0
  67. package/skills/_support/docs/principles/details/p057-supply-chain-integrity.md +135 -0
  68. package/skills/_support/docs/principles/details/p058-bounded-agent-authority.md +137 -0
  69. package/skills/_support/docs/principles/details/p059-data-is-not-instruction.md +130 -0
  70. package/skills/_support/docs/principles/details/p060-constrain-sub-agents.md +136 -0
  71. package/skills/_support/docs/principles/details/p061-separate-decision-from-high-impact-execution.md +131 -0
  72. package/skills/_support/docs/principles/details/p062-human-approval-for-irreversible-or-high-risk-actions.md +129 -0
  73. package/skills/_support/docs/principles/details/p063-requirement-to-code-traceability.md +116 -0
  74. package/skills/_support/docs/principles/details/p064-requirement-to-test-traceability.md +116 -0
  75. package/skills/_support/docs/principles/details/p065-verify-before-claiming-completion.md +131 -0
  76. package/skills/_support/docs/principles/details/p066-preserve-existing-work.md +114 -0
  77. package/skills/_support/docs/principles/details/p067-no-test-cheating.md +126 -0
  78. package/skills/_support/docs/principles/details/p068-no-validation-bypass.md +119 -0
  79. package/skills/_support/docs/principles/details/p069-independent-review-for-high-risk-changes.md +129 -0
  80. package/skills/_support/docs/principles/details/p070-code-health-must-not-regress.md +116 -0
  81. package/skills/_support/docs/principles/details/p071-consistency-over-personal-preference.md +117 -0
  82. package/skills/_support/docs/principles/details/p072-technical-evidence-over-preference.md +122 -0
  83. package/skills/_support/docs/principles/details/p073-optimize-only-with-evidence.md +123 -0
  84. package/skills/_support/docs/principles/details/p074-prefer-existing-mechanisms.md +121 -0
  85. package/skills/_support/docs/principles/details/p075-make-invalid-states-hard-to-represent.md +128 -0
  86. package/skills/_support/docs/principles/details/p076-parse-then-validate-then-operate.md +133 -0
  87. package/skills/_support/docs/principles/details/p077-separate-policy-from-mechanism.md +112 -0
  88. package/skills/_support/docs/principles/details/p078-single-source-of-truth.md +131 -0
  89. package/skills/_support/docs/principles/details/p079-explicit-ownership-and-lifetimes.md +124 -0
  90. package/skills/_support/docs/principles/details/p080-make-concurrency-deliberate.md +122 -0
  91. package/skills/_support/docs/principles/details/p081-forward-progress-with-safety.md +125 -0
  92. package/skills/_support/docs/principles/details/p082-design-for-cancellation.md +133 -0
  93. package/skills/_support/docs/principles/details/p083-irreversible-actions-last.md +124 -0
  94. package/skills/_support/docs/principles/details/p084-prefer-local-reasoning.md +131 -0
  95. package/skills/_support/docs/principles/details/p085-explicit-is-better-than-implicit.md +123 -0
  96. package/skills/_support/docs/principles/details/p086-readability-counts.md +113 -0
  97. package/skills/_support/docs/principles/details/p087-comments-explain-why-code-explains-what.md +120 -0
  98. package/skills/_support/docs/principles/details/p088-delete-dead-code.md +125 -0
  99. package/skills/_support/docs/principles/details/p089-delete-obsolete-configuration-and-dependencies.md +125 -0
  100. package/skills/_support/docs/principles/details/p090-prefer-negative-code.md +134 -0
  101. package/skills/_support/docs/principles/details/p091-test-driven-development.md +149 -0
  102. package/skills/_support/docs/review/README.md +63 -0
  103. package/skills/_support/docs/review/behavior-first-testing.md +78 -0
  104. package/skills/_support/docs/review/common.md +400 -0
  105. package/skills/_support/docs/review/design-docs.md +74 -0
  106. package/skills/_support/docs/review/issue-planning.md +247 -0
  107. package/skills/_support/docs/review/language-routing.md +141 -0
  108. package/skills/_support/docs/review/repository-scorecard.md +170 -0
  109. package/skills/advise/SKILL.md +108 -74
  110. package/skills/advise/scripts/list_retrievable_skills.py +20 -8
  111. package/skills/advise/scripts/resolve_knowledge_checkout.py +533 -0
  112. package/skills/brainstorm/SKILL.md +163 -127
  113. package/skills/change-review/SKILL.md +91 -71
  114. package/skills/change-review/references/scope-resolution.md +69 -46
  115. package/skills/change-review/scripts/resolve_scope.py +144 -84
  116. package/skills/finalize-plan/SKILL.md +205 -139
  117. package/skills/git-worktrees/SKILL.md +100 -80
  118. package/skills/git-worktrees/scripts/prepare_worktree.py +34 -15
  119. package/skills/issue-review/SKILL.md +136 -79
  120. package/skills/learn/SKILL.md +359 -192
  121. package/skills/myrmidon-swarm/SKILL.md +126 -86
  122. package/skills/plan-issue/SKILL.md +122 -81
  123. package/skills/pr-review/SKILL.md +184 -116
  124. package/skills/pr-review/references/criteria.md +61 -37
  125. package/skills/pr-review/references/delivery.md +277 -101
  126. package/skills/pr-review/references/evidence.md +346 -149
  127. package/skills/pr-review/references/prevalidated.md +93 -61
  128. package/skills/pr-review/scripts/collect_evidence.py +312 -162
  129. package/skills/pr-review/scripts/deliver_go.py +701 -0
  130. package/skills/pr-review/scripts/diff_context.py +34 -15
  131. package/skills/pr-review/scripts/materialize_snapshot.py +141 -130
  132. package/skills/pr-review/scripts/pr_identity.py +27 -15
  133. package/skills/pr-review/scripts/resolve_pr.py +77 -41
  134. package/skills/realign/SKILL.md +516 -0
  135. package/skills/realign/references/aislop-integration.md +215 -0
  136. package/skills/realign/references/architecture-and-structure.md +271 -0
  137. package/skills/realign/references/control-flow-and-errors.md +344 -0
  138. package/skills/realign/references/tests-dependencies-and-security.md +261 -0
  139. package/skills/realign/scripts/resolve_assessment.py +1525 -0
  140. package/skills/repo-review/SKILL.md +202 -131
  141. package/skills/simplify/SKILL.md +174 -0
  142. package/skills/systematic-debugging/SKILL.md +212 -192
  143. package/skills/systematic-debugging/scripts/repository_evidence.py +26 -8
  144. package/skills/test-driven-development/SKILL.md +126 -96
  145. package/skills/tidy/SKILL.md +104 -59
  146. package/skills/tidy/scripts/run_tidy.py +54 -6
@@ -1,26 +1,43 @@
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
 
6
+ import importlib.util
6
7
  import json
7
8
  import sys
8
9
  from collections.abc import Sequence
9
10
  from pathlib import Path
10
-
11
- if __package__ in {None, ""}:
12
- sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
11
+ from typing import TYPE_CHECKING
13
12
 
14
13
  from pr_identity import require_commit_oid
15
14
 
16
- from skills._cli import (
17
- argument_parser,
18
- git_read_arguments,
19
- git_read_environment,
20
- require_complete_git_history,
21
- require_unambiguous_git_merge_base,
22
- run_command,
23
- )
15
+ if TYPE_CHECKING or __package__ not in {None, ""}:
16
+ from skills._cli import (
17
+ argument_parser,
18
+ git_read_arguments,
19
+ git_read_environment,
20
+ require_complete_git_history,
21
+ require_unambiguous_git_merge_base,
22
+ run_command,
23
+ )
24
+ else:
25
+ _cli_path = Path(__file__).resolve().parents[2] / "_cli.py"
26
+ _cli_spec = importlib.util.spec_from_file_location(
27
+ "athena_installed_cli", _cli_path
28
+ )
29
+ if _cli_spec is None or _cli_spec.loader is None:
30
+ raise RuntimeError(
31
+ f"The installed Athena CLI helper is unavailable: '{_cli_path}'."
32
+ )
33
+ _cli = importlib.util.module_from_spec(_cli_spec)
34
+ _cli_spec.loader.exec_module(_cli)
35
+ argument_parser = _cli.argument_parser
36
+ git_read_arguments = _cli.git_read_arguments
37
+ git_read_environment = _cli.git_read_environment
38
+ require_complete_git_history = _cli.require_complete_git_history
39
+ require_unambiguous_git_merge_base = _cli.require_unambiguous_git_merge_base
40
+ run_command = _cli.run_command
24
41
 
25
42
 
26
43
  def git(*arguments: str) -> str:
@@ -32,7 +49,9 @@ def git(*arguments: str) -> str:
32
49
  check=False,
33
50
  )
34
51
  if result.returncode != 0:
35
- raise RuntimeError(result.stderr.strip() or f"git {' '.join(arguments)} failed")
52
+ raise RuntimeError(
53
+ result.stderr.strip() or f"The git {' '.join(arguments)} command failed."
54
+ )
36
55
  return result.stdout.strip()
37
56
 
38
57
 
@@ -42,8 +61,8 @@ def main(argv: Sequence[str] | None = None) -> int:
42
61
  parser.add_argument("head_ref", metavar="HEAD_OID")
43
62
  arguments = parser.parse_args(argv)
44
63
  try:
45
- base_ref = require_commit_oid(arguments.base_ref, "base OID")
46
- head_ref = require_commit_oid(arguments.head_ref, "head OID")
64
+ base_ref = require_commit_oid(arguments.base_ref, "base object identifier")
65
+ head_ref = require_commit_oid(arguments.head_ref, "head object identifier")
47
66
  require_complete_git_history()
48
67
  git("rev-parse", "--verify", f"{base_ref}^{{commit}}")
49
68
  git("rev-parse", "--verify", f"{head_ref}^{{commit}}")
@@ -3,6 +3,8 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
+ import importlib.util
7
+ import inspect
6
8
  import json
7
9
  import shutil
8
10
  import stat
@@ -12,26 +14,44 @@ import tempfile
12
14
  from collections.abc import Sequence
13
15
  from dataclasses import dataclass
14
16
  from pathlib import Path
15
-
16
- if __package__ in {None, ""}:
17
- sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
17
+ from typing import TYPE_CHECKING
18
18
 
19
19
  from pr_identity import COMMIT_OID, require_commit_oid, require_github_repository
20
20
 
21
- from skills._cli import (
22
- argument_parser,
23
- git_read_arguments,
24
- git_read_environment,
25
- run_command,
26
- )
21
+ if TYPE_CHECKING or __package__ not in {None, ""}:
22
+ from skills._cli import (
23
+ argument_parser,
24
+ git_read_arguments,
25
+ git_read_environment,
26
+ run_command,
27
+ )
28
+ else:
29
+ _cli_path = Path(__file__).resolve().parents[2] / "_cli.py"
30
+ _cli_spec = importlib.util.spec_from_file_location(
31
+ "athena_installed_cli", _cli_path
32
+ )
33
+ if _cli_spec is None or _cli_spec.loader is None:
34
+ raise RuntimeError(
35
+ f"The installed Athena CLI helper is unavailable: '{_cli_path}'."
36
+ )
37
+ _cli = importlib.util.module_from_spec(_cli_spec)
38
+ _cli_spec.loader.exec_module(_cli)
39
+ argument_parser = _cli.argument_parser
40
+ git_read_arguments = _cli.git_read_arguments
41
+ git_read_environment = _cli.git_read_environment
42
+ run_command = _cli.run_command
27
43
 
28
44
  SNAPSHOT_COMMAND_TIMEOUT_SECONDS = 30.0
29
45
  BOUNDED_MATERIALIZE_TIMEOUT_SECONDS = 600.0
46
+ MATERIALIZE_ERROR = "The helper cannot materialize the immutable pull-request snapshot."
47
+ QUOTA_ERROR = "The host cannot enforce the immutable pull-request snapshot size limit."
48
+ REMOVE_ERROR = "The helper cannot remove the immutable pull-request snapshot."
49
+ UNMANAGED_ROOT_ERROR = "The helper does not permit snapshot operations outside the managed temporary directory."
30
50
 
31
51
 
32
52
  @dataclass(frozen=True)
33
53
  class MaterializedSnapshot:
34
- """A detached source tree bound to one reviewed GitHub pull request."""
54
+ """This record binds a detached source tree to one reviewed GitHub pull request."""
35
55
 
36
56
  root: Path
37
57
  source_path: Path
@@ -78,11 +98,9 @@ def _git(
78
98
  ["git", *git_read_arguments(), *arguments], **command_options
79
99
  )
80
100
  except subprocess.SubprocessError as error:
81
- raise RuntimeError(
82
- "cannot materialize the immutable pull-request snapshot"
83
- ) from error
101
+ raise RuntimeError(MATERIALIZE_ERROR) from error
84
102
  if result.returncode not in accepted_codes:
85
- raise RuntimeError("cannot materialize the immutable pull-request snapshot")
103
+ raise RuntimeError(MATERIALIZE_ERROR)
86
104
  return result.stdout.strip() if isinstance(result.stdout, str) else ""
87
105
 
88
106
 
@@ -97,32 +115,31 @@ def _hdiutil(*arguments: str) -> None:
97
115
  check=False,
98
116
  )
99
117
  except (OSError, RuntimeError, subprocess.SubprocessError) as error:
100
- raise RuntimeError(
101
- "host cannot enforce the immutable pull-request snapshot size limit"
102
- ) from error
118
+ raise RuntimeError(QUOTA_ERROR) from error
103
119
  if result.returncode != 0:
104
- raise RuntimeError(
105
- "host cannot enforce the immutable pull-request snapshot size limit"
106
- )
120
+ raise RuntimeError(QUOTA_ERROR)
107
121
 
108
122
 
109
123
  def _mount_tmpfs(source: Path, maximum_bytes: int) -> bool:
110
124
  """Mount a Linux tmpfs whose total capacity is the snapshot quota.
111
125
 
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.
126
+ Return True only when the mount enforces the quota. Do not use
127
+ ``source.is_mount()`` elsewhere to infer this result. If the host cannot
128
+ find or run ``mount``, return False. If the host rejects the mount, return
129
+ False. The caller then uses the bounded user and mount namespace or fails
130
+ closed.
116
131
  """
117
132
  if sys.platform != "linux" or shutil.which("mount") is None:
118
133
  return False
119
134
  maximum_kibibytes = maximum_bytes // 1024
120
135
  if maximum_kibibytes < 1:
121
- raise RuntimeError("immutable pull-request snapshot has no usable disk space")
136
+ raise RuntimeError(
137
+ "The immutable pull-request snapshot has no usable disk space."
138
+ )
122
139
  try:
123
140
  source.mkdir()
124
141
  except FileExistsError:
125
- # Directory already exists from a prior materialization; reuse it.
142
+ # If a prior materialization created the directory, use it again.
126
143
  pass
127
144
  except OSError:
128
145
  return False
@@ -150,15 +167,17 @@ def _mount_tmpfs(source: Path, maximum_bytes: int) -> bool:
150
167
  def _create_quota_volume(root: Path, maximum_bytes: int) -> Path | None:
151
168
  """Return a bounded source directory, or None for the Linux bounded fallback.
152
169
 
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.
170
+ On macOS, attach a sparse HFS+ volume whose total capacity is the quota.
171
+ On Linux, mount a tmpfs when the host has the required authority. Otherwise,
172
+ return None so the caller can materialize the snapshot inside a bounded user
173
+ and mount namespace. On other systems, fail closed because the host cannot
174
+ enforce the snapshot size limit.
158
175
  """
159
176
  maximum_kibibytes = maximum_bytes // 1024
160
177
  if maximum_kibibytes < 1:
161
- raise RuntimeError("immutable pull-request snapshot has no usable disk space")
178
+ raise RuntimeError(
179
+ "The immutable pull-request snapshot has no usable disk space."
180
+ )
162
181
  if sys.platform == "darwin":
163
182
  image = root / "snapshot.sparseimage"
164
183
  source = root / "source"
@@ -173,7 +192,7 @@ def _create_quota_volume(root: Path, maximum_bytes: int) -> Path | None:
173
192
  "-fs",
174
193
  "Case-sensitive HFS+",
175
194
  "-volname",
176
- "Athena PR Review",
195
+ "Athena pull-request review",
177
196
  "-nospotlight",
178
197
  str(image),
179
198
  )
@@ -186,9 +205,7 @@ def _create_quota_volume(root: Path, maximum_bytes: int) -> Path | None:
186
205
  str(image),
187
206
  )
188
207
  if not source.is_mount():
189
- raise RuntimeError(
190
- "host cannot enforce the immutable pull-request snapshot size limit"
191
- )
208
+ raise RuntimeError(QUOTA_ERROR)
192
209
  return source
193
210
  if sys.platform.startswith("linux"):
194
211
  source = root / "source"
@@ -197,13 +214,11 @@ def _create_quota_volume(root: Path, maximum_bytes: int) -> Path | None:
197
214
  try:
198
215
  source.rmdir()
199
216
  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.
217
+ # The mount can create the directory before it fails.
218
+ # If rmdir fails, the caller uses None to select the other method.
202
219
  pass
203
220
  return None
204
- raise RuntimeError(
205
- "host cannot enforce the immutable pull-request snapshot size limit"
206
- )
221
+ raise RuntimeError(QUOTA_ERROR)
207
222
 
208
223
 
209
224
  def _detach_volume(source: Path) -> None:
@@ -229,41 +244,38 @@ def _detach_volume(source: Path) -> None:
229
244
  check=False,
230
245
  )
231
246
  if result.returncode != 0:
232
- raise RuntimeError("cannot remove the immutable pull-request snapshot")
247
+ raise RuntimeError(REMOVE_ERROR)
233
248
  except (OSError, RuntimeError, subprocess.SubprocessError) as error:
234
- raise RuntimeError(
235
- "cannot remove the immutable pull-request snapshot"
236
- ) from error
249
+ raise RuntimeError(REMOVE_ERROR) from error
237
250
  return
238
- raise RuntimeError("cannot remove the immutable pull-request snapshot")
251
+ raise RuntimeError(REMOVE_ERROR)
239
252
 
240
253
 
241
254
  def _detach_best_effort(source: Path) -> None:
242
- """Detach a quota volume without masking a prior failure."""
255
+ """Detach the quota volume. Do not let a detach failure hide an earlier failure."""
243
256
  try:
244
257
  _detach_volume(source)
245
258
  except RuntimeError:
246
- # Detach is best-effort; a prior failure must not be masked by a
247
- # secondary cleanup error.
259
+ # Do not let this cleanup error hide an earlier failure.
248
260
  pass
249
261
 
250
262
 
251
263
  def _require_base_ref(base_ref: str) -> str:
252
264
  """Validate the GitHub base branch before it becomes a fetch refspec."""
253
265
  if not base_ref or base_ref.startswith("-") or ".." in base_ref:
254
- raise RuntimeError("GitHub returned an invalid pull-request base ref")
266
+ raise RuntimeError("GitHub returned a pull-request base ref that is not valid.")
255
267
  _git("check-ref-format", "--branch", base_ref)
256
268
  return base_ref
257
269
 
258
270
 
259
271
  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."""
272
+ """Return the repository size and enforce the applicable size limit."""
261
273
  if maximum_bytes is None:
262
274
  try:
263
275
  maximum_bytes = (shutil.disk_usage(path).free * 9) // 10
264
276
  except OSError as error:
265
277
  raise RuntimeError(
266
- "cannot inspect the immutable pull-request snapshot"
278
+ "The helper cannot inspect the immutable pull-request snapshot."
267
279
  ) from error
268
280
  total = 0
269
281
  for entry in path.rglob("*"):
@@ -271,13 +283,13 @@ def _repository_size(path: Path, *, maximum_bytes: int | None = None) -> int:
271
283
  details = entry.lstat()
272
284
  except OSError as error:
273
285
  raise RuntimeError(
274
- "cannot inspect the immutable pull-request snapshot"
286
+ "The helper cannot inspect the immutable pull-request snapshot."
275
287
  ) from error
276
288
  if stat.S_ISREG(details.st_mode):
277
289
  total += details.st_size
278
290
  if total > maximum_bytes:
279
291
  raise RuntimeError(
280
- "immutable pull-request snapshot exceeds the safe size limit"
292
+ "The immutable pull-request snapshot exceeds the safe size limit."
281
293
  )
282
294
  return total
283
295
 
@@ -296,7 +308,7 @@ def _verify_no_promisor_configuration(repository: Path) -> None:
296
308
  )
297
309
  if value:
298
310
  raise RuntimeError(
299
- "immutable pull-request snapshot must not use partial clone configuration"
311
+ "The immutable pull-request snapshot must not use partial-clone configuration."
300
312
  )
301
313
  promisor = _git(
302
314
  "config",
@@ -309,12 +321,12 @@ def _verify_no_promisor_configuration(repository: Path) -> None:
309
321
  )
310
322
  if promisor:
311
323
  raise RuntimeError(
312
- "immutable pull-request snapshot must not use promisor configuration"
324
+ "The immutable pull-request snapshot must not use promisor configuration."
313
325
  )
314
326
 
315
327
 
316
328
  def _require_commit(repository: Path, revision: str, label: str) -> str:
317
- """Verify one fetched ref resolves exactly to its captured commit OID."""
329
+ """Verify that one fetched ref resolves to its captured commit object identifier."""
318
330
  resolved = _git(
319
331
  "rev-parse",
320
332
  "--verify",
@@ -326,7 +338,7 @@ def _require_commit(repository: Path, revision: str, label: str) -> str:
326
338
 
327
339
 
328
340
  def _make_read_only(root: Path) -> None:
329
- """Remove write bits from the completed snapshot without following symlinks."""
341
+ """Remove write bits from the completed snapshot without following symbolic links."""
330
342
  entries = sorted(root.rglob("*"), key=lambda entry: len(entry.parts), reverse=True)
331
343
  for entry in entries:
332
344
  if entry.is_symlink():
@@ -339,7 +351,7 @@ def _make_read_only(root: Path) -> None:
339
351
  entry.chmod(0o555 if mode & stat.S_IXUSR else 0o444)
340
352
  except OSError as error:
341
353
  raise RuntimeError(
342
- "cannot make the immutable pull-request snapshot read-only"
354
+ "The helper cannot make the immutable pull-request snapshot read-only."
343
355
  ) from error
344
356
  root.chmod(0o555)
345
357
 
@@ -356,11 +368,11 @@ def _acquire_into(
356
368
  template: Path,
357
369
  maximum_bytes: int,
358
370
  ) -> tuple[str, str]:
359
- """Fetch only the captured base branch and PR head into a source directory.
371
+ """Fetch only the captured base branch and pull-request head.
360
372
 
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.
373
+ The caller provides a source directory in a quota volume or bounded tmpfs.
374
+ Verify each immutable binding against the captured object identifiers.
375
+ Then, return ``(merge_base, tree_oid)``.
364
376
  """
365
377
  _git(
366
378
  "-c",
@@ -405,15 +417,26 @@ def _acquire_into(
405
417
  _git("rev-parse", "--is-shallow-repository", cwd=source, capture_output=True)
406
418
  != "false"
407
419
  ):
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")
420
+ raise RuntimeError(
421
+ "The immutable pull-request snapshot requires complete history."
422
+ )
423
+ if (
424
+ _require_commit(source, "refs/athena/base", "fetched base object identifier")
425
+ != base_oid
426
+ ):
427
+ raise RuntimeError(
428
+ "The fetched base ref does not match the captured base object identifier."
429
+ )
411
430
  if (
412
- _require_commit(source, f"refs/athena/pr/{number}/head", "fetched head OID")
431
+ _require_commit(
432
+ source,
433
+ f"refs/athena/pr/{number}/head",
434
+ "fetched head object identifier",
435
+ )
413
436
  != head_oid
414
437
  ):
415
438
  raise RuntimeError(
416
- "fetched pull-request ref does not match the captured head OID"
439
+ "The fetched pull-request ref does not match the captured head object identifier."
417
440
  )
418
441
  merge_bases = _git(
419
442
  "merge-base",
@@ -425,15 +448,15 @@ def _acquire_into(
425
448
  ).splitlines()
426
449
  if len(merge_bases) != 1:
427
450
  raise RuntimeError(
428
- "immutable pull-request snapshot requires one unambiguous merge base"
451
+ "The immutable pull-request snapshot requires one unambiguous merge base."
429
452
  )
430
453
  merge_base = require_commit_oid(merge_bases[0], "immutable merge base")
431
- tree_oid = _require_commit(source, head_oid, "fetched head OID")
454
+ tree_oid = _require_commit(source, head_oid, "fetched head object identifier")
432
455
  tree_oid = _git(
433
456
  "rev-parse", f"{tree_oid}^{{tree}}", cwd=source, capture_output=True
434
457
  )
435
458
  if COMMIT_OID.fullmatch(tree_oid) is None:
436
- raise RuntimeError("Git returned an invalid immutable head tree")
459
+ raise RuntimeError("Git returned an immutable head tree that is not valid.")
437
460
  _git(
438
461
  "-c",
439
462
  f"core.hooksPath={hooks}",
@@ -450,15 +473,16 @@ def _acquire_into(
450
473
 
451
474
 
452
475
  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.
476
+ """Materialize inside a bounded user and mount namespace.
477
+
478
+ This helper runs as the child of ``unshare -rm`` on Linux. The helper mounts
479
+ a tmpfs whose total capacity is the snapshot quota. It materializes the
480
+ snapshot in the size-limited file system. Then, it copies the verified
481
+ read-only tree to a path that the host can read. Exit 2 means that the helper
482
+ could not establish the quota boundary. Exit 1 means that materialization
483
+ failed.
460
484
  """
461
- parser = argument_parser(description="Bounded snapshot materialization.")
485
+ parser = argument_parser(description="Materialize a bounded pull-request snapshot.")
462
486
  parser.add_argument("--root", required=True, metavar="ROOT")
463
487
  parser.add_argument("--repository-url", required=True, metavar="URL")
464
488
  parser.add_argument("--pr-number", required=True, type=int, metavar="NUMBER")
@@ -470,22 +494,20 @@ def _bounded_materialize_main(arguments: Sequence[str]) -> int:
470
494
  root = Path(parsed.root).resolve()
471
495
  temporary_root = Path(tempfile.gettempdir()).resolve()
472
496
  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
- )
497
+ print(UNMANAGED_ROOT_ERROR, file=sys.stderr)
477
498
  return 1
478
499
  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")
500
+ canonical_base = require_commit_oid(
501
+ parsed.base_oid, "captured base object identifier"
502
+ )
503
+ canonical_head = require_commit_oid(
504
+ parsed.head_oid, "captured head object identifier"
505
+ )
481
506
  canonical_base_ref = _require_base_ref(parsed.base_ref)
482
507
  bounded = root / "bounded"
483
508
  bounded.mkdir()
484
509
  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
- )
510
+ print(QUOTA_ERROR, file=sys.stderr)
489
511
  return 2
490
512
  merge_base, tree_oid = _acquire_into(
491
513
  bounded,
@@ -528,20 +550,18 @@ def _bounded_materialize(
528
550
  base_oid: str,
529
551
  head_oid: str,
530
552
  ) -> 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.
553
+ """Materialize a snapshot inside a bounded Linux user and mount namespace.
554
+
555
+ Run this helper under ``unshare -rm``. The child has root authority only in
556
+ the new namespace. The child mounts a tmpfs whose total capacity is the
557
+ snapshot quota. The file system bounds each fetch and checkout write. Before
558
+ the namespace exits, the child copies the verified tree to a read-only path
559
+ that the host can read. If ``unshare`` or a tmpfs mount is not available,
560
+ the helper fails closed.
539
561
  """
540
562
  unshare = shutil.which("unshare")
541
563
  if unshare is None:
542
- raise RuntimeError(
543
- "host cannot enforce the immutable pull-request snapshot size limit"
544
- )
564
+ raise RuntimeError(QUOTA_ERROR)
545
565
  command = [
546
566
  unshare,
547
567
  "-rm",
@@ -573,26 +593,20 @@ def _bounded_materialize(
573
593
  timeout=BOUNDED_MATERIALIZE_TIMEOUT_SECONDS,
574
594
  )
575
595
  except subprocess.TimeoutExpired as error:
576
- raise RuntimeError(
577
- "cannot materialize the immutable pull-request snapshot"
578
- ) from error
596
+ raise RuntimeError(MATERIALIZE_ERROR) from error
579
597
  except (OSError, RuntimeError, subprocess.SubprocessError) as error:
580
- raise RuntimeError(
581
- "host cannot enforce the immutable pull-request snapshot size limit"
582
- ) from error
598
+ raise RuntimeError(QUOTA_ERROR) from error
583
599
  if result.returncode == 2:
584
- raise RuntimeError(
585
- "host cannot enforce the immutable pull-request snapshot size limit"
586
- )
600
+ raise RuntimeError(QUOTA_ERROR)
587
601
  if result.returncode != 0:
588
- raise RuntimeError("cannot materialize the immutable pull-request snapshot")
602
+ raise RuntimeError(MATERIALIZE_ERROR)
589
603
  try:
590
604
  lines = [line for line in result.stdout.splitlines() if line.strip()]
591
605
  if not lines:
592
- raise RuntimeError("cannot materialize the immutable pull-request snapshot")
606
+ raise RuntimeError(MATERIALIZE_ERROR)
593
607
  record = json.loads(lines[-1])
594
608
  if not isinstance(record, dict):
595
- raise TypeError("cannot materialize the immutable pull-request snapshot")
609
+ raise TypeError(MATERIALIZE_ERROR)
596
610
  source_path = Path(str(record["source_path"]))
597
611
  merge_base = require_commit_oid(record["merge_base"], "immutable merge base")
598
612
  tree_oid = require_commit_oid(record["tree_oid"], "immutable head tree")
@@ -603,11 +617,9 @@ def _bounded_materialize(
603
617
  json.JSONDecodeError,
604
618
  RuntimeError,
605
619
  ) as error:
606
- raise RuntimeError(
607
- "cannot materialize the immutable pull-request snapshot"
608
- ) from error
620
+ raise RuntimeError(MATERIALIZE_ERROR) from error
609
621
  if source_path != root / "source" or not source_path.is_dir():
610
- raise RuntimeError("cannot materialize the immutable pull-request snapshot")
622
+ raise RuntimeError(MATERIALIZE_ERROR)
611
623
  return MaterializedSnapshot(
612
624
  root=root, source_path=source_path, merge_base=merge_base, tree_oid=tree_oid
613
625
  )
@@ -616,12 +628,12 @@ def _bounded_materialize(
616
628
  def materialize_snapshot(
617
629
  *, repository: str, number: int, base_ref: str, base_oid: str, head_oid: str
618
630
  ) -> MaterializedSnapshot:
619
- """Fetch only a captured base branch and PR head into a fresh repository."""
631
+ """Materialize a captured base branch and pull-request head in a new repository."""
620
632
  canonical_repository = require_github_repository(repository, "GitHub repository")
621
633
  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")
634
+ raise RuntimeError("The pull-request number must be positive.")
635
+ canonical_base = require_commit_oid(base_oid, "captured base object identifier")
636
+ canonical_head = require_commit_oid(head_oid, "captured head object identifier")
625
637
  canonical_base_ref = _require_base_ref(base_ref)
626
638
  root = Path(tempfile.mkdtemp(prefix="athena-pr-review-"))
627
639
  template = root / "empty-template"
@@ -632,7 +644,7 @@ def materialize_snapshot(
632
644
  maximum_snapshot_bytes = (shutil.disk_usage(root).free * 9) // 10
633
645
  if maximum_snapshot_bytes < 1:
634
646
  raise RuntimeError(
635
- "immutable pull-request snapshot has no usable disk space"
647
+ "The immutable pull-request snapshot has no usable disk space."
636
648
  )
637
649
  source = _create_quota_volume(root, maximum_snapshot_bytes)
638
650
  if source is None:
@@ -659,9 +671,7 @@ def materialize_snapshot(
659
671
  _make_read_only(root)
660
672
  except (OSError, subprocess.TimeoutExpired):
661
673
  remove_snapshot(root)
662
- raise RuntimeError(
663
- "cannot materialize the immutable pull-request snapshot"
664
- ) from None
674
+ raise RuntimeError(MATERIALIZE_ERROR) from None
665
675
  except BaseException:
666
676
  remove_snapshot(root)
667
677
  raise
@@ -671,15 +681,13 @@ def materialize_snapshot(
671
681
 
672
682
 
673
683
  def remove_snapshot(root: Path) -> None:
674
- """Remove a snapshot that this helper created after host inspection ends."""
684
+ """After host inspection ends, remove a snapshot that this helper materialized."""
675
685
  resolved = root.resolve()
676
686
  temporary_root = Path(tempfile.gettempdir()).resolve()
677
687
  if resolved.parent != temporary_root or not resolved.name.startswith(
678
688
  "athena-pr-review-"
679
689
  ):
680
- raise RuntimeError(
681
- "refusing to remove a snapshot outside the managed temporary directory"
682
- )
690
+ raise RuntimeError(UNMANAGED_ROOT_ERROR)
683
691
  source = resolved / "source"
684
692
  if source.is_mount():
685
693
  _detach_volume(source)
@@ -690,10 +698,13 @@ def remove_snapshot(root: Path) -> None:
690
698
  if candidate.exists() and not candidate.is_symlink():
691
699
  candidate.chmod(0o700)
692
700
  if not callable(function):
693
- raise TypeError("cannot remove the immutable pull-request snapshot")
701
+ raise TypeError(REMOVE_ERROR)
694
702
  function(path)
695
703
 
696
- shutil.rmtree(resolved, onexc=make_removable)
704
+ if "onexc" in inspect.signature(shutil.rmtree).parameters:
705
+ shutil.rmtree(resolved, onexc=make_removable)
706
+ return
707
+ shutil.rmtree(resolved, onerror=make_removable)
697
708
 
698
709
 
699
710
  def main(argv: Sequence[str] | None = None) -> int: