@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,9 +1,10 @@
1
1
  #!/usr/bin/env python3
2
- """Resolve a change-review scope without changing repository or Git state."""
2
+ """Resolve the scope for a change review. Do not change the repository or Git state."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
6
  import hashlib
7
+ import importlib.util
7
8
  import json
8
9
  import os
9
10
  import stat
@@ -15,17 +16,30 @@ from dataclasses import dataclass
15
16
  from hashlib import sha256
16
17
  from operator import index
17
18
  from pathlib import Path
18
- from typing import Protocol, cast
19
-
20
- if __package__ in {None, ""}:
21
- sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
22
-
23
- from skills._cli import (
24
- argument_parser,
25
- git_read_arguments,
26
- git_read_environment,
27
- run_command,
28
- )
19
+ from typing import TYPE_CHECKING, Protocol, cast
20
+
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
29
43
 
30
44
  READ_CHUNK_SIZE = 1024 * 1024
31
45
  ERROR_OUTPUT_LIMIT = 16 * 1024
@@ -34,7 +48,7 @@ MAX_WORKTREE_CANDIDATES = 50_000
34
48
 
35
49
 
36
50
  def git_bytes(*arguments: str, repository_root: Path | None = None) -> bytes:
37
- """Run Git and return its raw stdout or raise a concise error."""
51
+ """Run Git and return its raw standard output or raise a concise error."""
38
52
  command = git_command(arguments, repository_root)
39
53
  result = run_command(
40
54
  command,
@@ -47,7 +61,9 @@ def git_bytes(*arguments: str, repository_root: Path | None = None) -> bytes:
47
61
  stderr = cast(bytes, result.stderr)
48
62
  if result.returncode != 0:
49
63
  message = stderr.decode("utf-8", errors="replace").strip()
50
- raise RuntimeError(message or f"git {' '.join(arguments)} failed")
64
+ raise RuntimeError(
65
+ message or f"The Git command failed. Command: git {' '.join(arguments)}"
66
+ )
51
67
  return stdout
52
68
 
53
69
 
@@ -60,13 +76,13 @@ def git_text(*arguments: str, repository_root: Path | None = None) -> str:
60
76
 
61
77
 
62
78
  def path_list(document: bytes) -> list[str]:
63
- """Return sorted Git NUL-delimited paths without lossy shell parsing."""
79
+ """Return sorted Git paths from null-byte-delimited data without lossy parsing."""
64
80
  return sorted(os.fsdecode(path) for path in document.split(b"\0") if path)
65
81
 
66
82
 
67
83
  @dataclass(frozen=True)
68
84
  class PathEntry:
69
- """One no-follow worktree or immutable Git-object manifest entry."""
85
+ """This record describes one worktree or immutable Git-object manifest entry."""
70
86
 
71
87
  path: str
72
88
  kind: str
@@ -77,7 +93,7 @@ class PathEntry:
77
93
 
78
94
  @dataclass(frozen=True)
79
95
  class ContentFingerprint:
80
- """Bounded identity for an arbitrarily large byte stream."""
96
+ """This record contains a bounded identity for a byte stream of any size."""
81
97
 
82
98
  length: int
83
99
  digest: str
@@ -85,7 +101,7 @@ class ContentFingerprint:
85
101
 
86
102
  @dataclass(frozen=True)
87
103
  class ScopeCapture:
88
- """One complete observed scope capture used to detect worktree races."""
104
+ """This record contains one scope observation that detects worktree races."""
89
105
 
90
106
  paths: tuple[str, ...]
91
107
  tracked_paths: tuple[str, ...]
@@ -135,12 +151,15 @@ def git_stream_fingerprint(
135
151
  )
136
152
  except FileNotFoundError as error:
137
153
  raise RuntimeError(
138
- f"required command unavailable: {error.filename or command[0]}"
154
+ "The required command is not available: "
155
+ f"'{error.filename or command[0]}'."
139
156
  ) from error
140
157
  try:
141
158
  stdout = process.stdout
142
159
  if stdout is None:
143
- raise RuntimeError("Git did not provide stdout for scope capture")
160
+ raise RuntimeError(
161
+ "Git did not provide standard output for scope capture."
162
+ )
144
163
  try:
145
164
  fingerprint = content_fingerprint(
146
165
  iter(lambda: stdout.read(READ_CHUNK_SIZE), b"")
@@ -160,10 +179,16 @@ def git_stream_fingerprint(
160
179
  .decode("utf-8", errors="replace")
161
180
  .strip()
162
181
  )
163
- raise RuntimeError(message or f"git {' '.join(arguments)} failed")
182
+ raise RuntimeError(
183
+ message
184
+ or f"The Git command failed. Command: git {' '.join(arguments)}"
185
+ )
164
186
  return fingerprint
165
187
  except OSError as error:
166
- raise RuntimeError(f"cannot stream git output: {error}") from error
188
+ raise RuntimeError(
189
+ "The tool cannot stream Git output. "
190
+ f"The operation returned this diagnostic.\n{error}"
191
+ ) from error
167
192
 
168
193
 
169
194
  def consume_git_nul_records(
@@ -171,7 +196,7 @@ def consume_git_nul_records(
171
196
  repository_root: Path,
172
197
  consume: Callable[[bytes], None],
173
198
  ) -> None:
174
- """Pass Git NUL records to a consumer without buffering command output."""
199
+ """Pass null-byte-delimited Git records without buffering command output."""
175
200
  command = git_command(arguments, repository_root)
176
201
  try:
177
202
  with tempfile.TemporaryFile() as error_output:
@@ -184,13 +209,16 @@ def consume_git_nul_records(
184
209
  )
185
210
  except FileNotFoundError as error:
186
211
  raise RuntimeError(
187
- f"required command unavailable: {error.filename or command[0]}"
212
+ "The required command is not available: "
213
+ f"'{error.filename or command[0]}'."
188
214
  ) from error
189
215
  stdout = process.stdout
190
216
  if stdout is None:
191
217
  process.kill()
192
218
  process.wait()
193
- raise RuntimeError("Git did not provide stdout for scope capture")
219
+ raise RuntimeError(
220
+ "Git did not provide standard output for scope capture."
221
+ )
194
222
  pending = b""
195
223
  try:
196
224
  while chunk := stdout.read(READ_CHUNK_SIZE):
@@ -199,17 +227,18 @@ def consume_git_nul_records(
199
227
  pending = records.pop()
200
228
  if len(pending) > MAX_METADATA_RECORD_BYTES:
201
229
  raise RuntimeError(
202
- "Git metadata record exceeds the safe scope limit"
230
+ "The Git metadata record is larger than the safe scope limit."
203
231
  )
204
232
  for record in records:
205
233
  if record:
206
234
  if len(record) > MAX_METADATA_RECORD_BYTES:
207
235
  raise RuntimeError(
208
- "Git metadata record exceeds the safe scope limit"
236
+ "The Git metadata record is larger than the safe scope "
237
+ "limit."
209
238
  )
210
239
  consume(record)
211
240
  if pending:
212
- raise RuntimeError("unterminated Git metadata record")
241
+ raise RuntimeError("The Git metadata record is not terminated.")
213
242
  return_code = process.wait()
214
243
  except BaseException:
215
244
  stdout.close()
@@ -225,9 +254,15 @@ def consume_git_nul_records(
225
254
  .decode("utf-8", errors="replace")
226
255
  .strip()
227
256
  )
228
- raise RuntimeError(message or f"git {' '.join(arguments)} failed")
257
+ raise RuntimeError(
258
+ message
259
+ or f"The Git command failed. Command: git {' '.join(arguments)}"
260
+ )
229
261
  except OSError as error:
230
- raise RuntimeError(f"cannot stream git metadata: {error}") from error
262
+ raise RuntimeError(
263
+ "The tool cannot stream Git metadata. "
264
+ f"The operation returned this diagnostic.\n{error}"
265
+ ) from error
231
266
 
232
267
 
233
268
  def pathspec_arguments(arguments: list[str], paths: Sequence[str]) -> list[str]:
@@ -237,7 +272,7 @@ def pathspec_arguments(arguments: list[str], paths: Sequence[str]) -> list[str]:
237
272
 
238
273
 
239
274
  def normalized_paths(repository_root: Path, paths: Sequence[str]) -> list[str]:
240
- """Keep lexical filters inside the repository without following symlinks."""
275
+ """Keep lexical filters inside the repository without following symbolic links."""
241
276
  root = Path(os.path.abspath(os.fspath(repository_root)))
242
277
  normalized: list[str] = []
243
278
  for raw_path in paths:
@@ -249,15 +284,17 @@ def normalized_paths(repository_root: Path, paths: Sequence[str]) -> list[str]:
249
284
  try:
250
285
  relative = resolved.relative_to(root)
251
286
  except ValueError as error:
252
- raise RuntimeError(f"path outside repository: {raw_path!r}") from error
287
+ raise RuntimeError(
288
+ f"The path is outside the repository: {raw_path!r}."
289
+ ) from error
253
290
  normalized.append(relative.as_posix())
254
291
  return sorted(set(normalized))
255
292
 
256
293
 
257
294
  def verified_commit(reference: str, repository_root: Path | None = None) -> str:
258
- """Resolve one non-option Git reference to an immutable commit OID."""
295
+ """Resolve one non-option Git reference to an immutable commit object identifier."""
259
296
  if not reference or reference.startswith("-"):
260
- raise RuntimeError(f"invalid Git reference: {reference!r}")
297
+ raise RuntimeError(f"The Git reference is not valid: {reference!r}.")
261
298
  return git_text(
262
299
  "rev-parse",
263
300
  "--verify",
@@ -267,9 +304,9 @@ def verified_commit(reference: str, repository_root: Path | None = None) -> str:
267
304
 
268
305
 
269
306
  def range_revisions(value: str, repository_root: Path) -> tuple[str, str]:
270
- """Resolve the required BASE..HEAD notation to immutable commit OIDs."""
307
+ """Resolve the required BASE..HEAD notation to immutable commit object identifiers."""
271
308
  if value.count("..") != 1:
272
- raise RuntimeError("range must use exactly one BASE..HEAD separator")
309
+ raise RuntimeError("The range must contain exactly one BASE..HEAD separator.")
273
310
  base_reference, head_reference = value.split("..", maxsplit=1)
274
311
  return (
275
312
  verified_commit(base_reference, repository_root),
@@ -368,8 +405,8 @@ def untracked_paths(paths: Sequence[str], repository_root: Path) -> list[str]:
368
405
  def consume(record: bytes) -> None:
369
406
  if len(selected) >= MAX_WORKTREE_CANDIDATES:
370
407
  raise RuntimeError(
371
- "untracked path limit "
372
- f"({MAX_WORKTREE_CANDIDATES}) reached; rerun with narrower PATH arguments"
408
+ f"The untracked path limit ({MAX_WORKTREE_CANDIDATES}) was reached. "
409
+ "Run the command again with narrower PATH arguments."
373
410
  )
374
411
  selected.append(os.fsdecode(record))
375
412
 
@@ -382,7 +419,7 @@ def untracked_paths(paths: Sequence[str], repository_root: Path) -> list[str]:
382
419
 
383
420
 
384
421
  class Digest(Protocol):
385
- """Minimal hashlib protocol used by the canonical scope digest."""
422
+ """This protocol defines the minimum hashlib operations for the scope digest."""
386
423
 
387
424
  def update(self, data: bytes) -> None:
388
425
  """Add bytes to the digest state."""
@@ -405,7 +442,7 @@ def path_components(relative_path: str) -> tuple[str, ...]:
405
442
  """Return a verified repository-relative path split into lexical components."""
406
443
  components = Path(relative_path).parts
407
444
  if not components or any(component in {".", ".."} for component in components):
408
- raise RuntimeError(f"invalid repository path: {relative_path!r}")
445
+ raise RuntimeError(f"The repository path is not valid: {relative_path!r}.")
409
446
  return components
410
447
 
411
448
 
@@ -421,10 +458,10 @@ def close_descriptor_quietly(descriptor: int) -> None:
421
458
  def nofollow_parent_descriptor(
422
459
  repository_root: Path, relative_path: str
423
460
  ) -> tuple[int, str]:
424
- """Open a path's parent without following any repository symlink."""
461
+ """Open a path's parent without following any repository symbolic link."""
425
462
  if not hasattr(os, "O_NOFOLLOW") or not hasattr(os, "O_DIRECTORY"):
426
463
  raise RuntimeError(
427
- "host cannot inspect repository paths without following links"
464
+ "The host cannot inspect repository paths without following symbolic links."
428
465
  )
429
466
  components = path_components(relative_path)
430
467
  descriptor: int | None = os.open(
@@ -442,13 +479,14 @@ def nofollow_parent_descriptor(
442
479
  )
443
480
  except (NotImplementedError, TypeError) as error:
444
481
  raise RuntimeError(
445
- "host cannot inspect repository paths without following links"
482
+ "The host cannot inspect repository paths without following symbolic "
483
+ "links."
446
484
  ) from error
447
485
  try:
448
486
  os.close(descriptor)
449
487
  except OSError:
450
- # `close()` leaves descriptor state unspecified on error; do not
451
- # retry the parent descriptor, but never leak the opened child.
488
+ # After an error, `close()` leaves the descriptor state unspecified.
489
+ # Do not try the parent descriptor again. Always close the opened child.
452
490
  descriptor = None
453
491
  close_descriptor_quietly(child_descriptor)
454
492
  raise
@@ -462,7 +500,7 @@ def nofollow_parent_descriptor(
462
500
 
463
501
 
464
502
  def worktree_path_entry(repository_root: Path, relative_path: str) -> PathEntry:
465
- """Describe a path without following repository or target symlinks."""
503
+ """Describe a path without following repository or target symbolic links."""
466
504
  try:
467
505
  parent_descriptor, filename = nofollow_parent_descriptor(
468
506
  repository_root, relative_path
@@ -474,7 +512,7 @@ def worktree_path_entry(repository_root: Path, relative_path: str) -> PathEntry:
474
512
  mode = os.lstat(filename, dir_fd=parent_descriptor).st_mode
475
513
  except (NotImplementedError, TypeError) as error:
476
514
  raise RuntimeError(
477
- "host cannot inspect repository paths without following links"
515
+ "The host cannot inspect repository paths without following symbolic links."
478
516
  ) from error
479
517
  except FileNotFoundError:
480
518
  return PathEntry(relative_path, "absent")
@@ -483,7 +521,8 @@ def worktree_path_entry(repository_root: Path, relative_path: str) -> PathEntry:
483
521
  target = os.readlink(filename, dir_fd=parent_descriptor)
484
522
  except (NotImplementedError, TypeError) as error:
485
523
  raise RuntimeError(
486
- "host cannot inspect repository links without following them"
524
+ "The host cannot inspect repository symbolic links without following "
525
+ "them."
487
526
  ) from error
488
527
  return PathEntry(
489
528
  relative_path,
@@ -504,7 +543,7 @@ def worktree_path_entry(repository_root: Path, relative_path: str) -> PathEntry:
504
543
  def worktree_path_entries(
505
544
  repository_root: Path, paths: Sequence[str]
506
545
  ) -> tuple[PathEntry, ...]:
507
- """Describe every selected repository object without dereferencing links."""
546
+ """Describe each selected repository object without following symbolic links."""
508
547
  return tuple(worktree_path_entry(repository_root, path) for path in paths)
509
548
 
510
549
 
@@ -520,7 +559,7 @@ def git_object_kind(mode: str, object_type: str) -> str:
520
559
 
521
560
 
522
561
  def nul_records(document: bytes) -> list[bytes]:
523
- """Split a Git NUL-delimited record stream while preserving path bytes."""
562
+ """Split null-byte-delimited Git records and preserve the path bytes."""
524
563
  return [record for record in document.split(b"\0") if record]
525
564
 
526
565
 
@@ -540,12 +579,14 @@ def index_entry_map(
540
579
  raw_mode, raw_object_id, raw_stage = header.split()
541
580
  except ValueError as error:
542
581
  raise RuntimeError(
543
- "invalid Git index entry while resolving scope"
582
+ "The Git index entry is not valid for scope resolution."
544
583
  ) from error
545
584
  path = os.fsdecode(raw_path)
546
585
  stage = raw_stage.decode("ascii")
547
586
  if stage != "0":
548
- raise RuntimeError(f"unmerged index entry in selected scope: {path}")
587
+ raise RuntimeError(
588
+ f"The selected scope contains an unmerged index entry: '{path}'."
589
+ )
549
590
  mode = raw_mode.decode("ascii")
550
591
  object_id = raw_object_id.decode("ascii")
551
592
  entries[path] = PathEntry(
@@ -583,7 +624,7 @@ def head_tree_entry_map(
583
624
  raw_mode, raw_type, raw_object_id = header.split()
584
625
  except ValueError as error:
585
626
  raise RuntimeError(
586
- "invalid Git tree entry while resolving scope"
627
+ "The Git tree entry is not valid for scope resolution."
587
628
  ) from error
588
629
  path = os.fsdecode(raw_path)
589
630
  mode = raw_mode.decode("ascii")
@@ -609,7 +650,7 @@ def head_tree_path_entries(
609
650
 
610
651
  @dataclass(frozen=True)
611
652
  class WorktreeMetadata:
612
- """Bounded immutable metadata needed to compare raw worktree candidates."""
653
+ """This record contains bounded metadata for comparison of worktree candidates."""
613
654
 
614
655
  head_entries: dict[str, PathEntry]
615
656
  index_entries: dict[str, PathEntry]
@@ -623,8 +664,8 @@ def add_worktree_candidate(candidates: set[str], path: str) -> None:
623
664
  return
624
665
  if len(candidates) >= MAX_WORKTREE_CANDIDATES:
625
666
  raise RuntimeError(
626
- "worktree candidate limit "
627
- f"({MAX_WORKTREE_CANDIDATES}) reached; rerun with narrower PATH arguments"
667
+ f"The worktree candidate limit ({MAX_WORKTREE_CANDIDATES}) was reached. "
668
+ "Run the command again with narrower PATH arguments."
628
669
  )
629
670
  candidates.add(path)
630
671
 
@@ -635,7 +676,9 @@ def parse_head_tree_record(record: bytes) -> PathEntry:
635
676
  header, raw_path = record.split(b"\t", maxsplit=1)
636
677
  raw_mode, raw_type, raw_object_id = header.split()
637
678
  except ValueError as error:
638
- raise RuntimeError("invalid Git tree entry while resolving scope") from error
679
+ raise RuntimeError(
680
+ "The Git tree entry is not valid for scope resolution."
681
+ ) from error
639
682
  path = os.fsdecode(raw_path)
640
683
  mode = raw_mode.decode("ascii")
641
684
  object_type = raw_type.decode("ascii")
@@ -654,11 +697,15 @@ def parse_tagged_index_record(record: bytes) -> tuple[PathEntry, bool]:
654
697
  header, raw_path = raw_entry.split(b"\t", maxsplit=1)
655
698
  raw_mode, raw_object_id, raw_stage = header.split()
656
699
  except ValueError as error:
657
- raise RuntimeError("invalid Git index entry while resolving scope") from error
700
+ raise RuntimeError(
701
+ "The Git index entry is not valid for scope resolution."
702
+ ) from error
658
703
  path = os.fsdecode(raw_path)
659
704
  stage = raw_stage.decode("ascii")
660
705
  if stage != "0":
661
- raise RuntimeError(f"unmerged index entry in selected scope: {path}")
706
+ raise RuntimeError(
707
+ f"The selected scope contains an unmerged index entry: '{path}'."
708
+ )
662
709
  mode = raw_mode.decode("ascii")
663
710
  return (
664
711
  PathEntry(
@@ -674,7 +721,7 @@ def parse_tagged_index_record(record: bytes) -> tuple[PathEntry, bool]:
674
721
  def worktree_metadata(
675
722
  head: str, paths: Sequence[str], repository_root: Path
676
723
  ) -> WorktreeMetadata:
677
- """Stream bounded HEAD/index metadata without reading worktree bytes."""
724
+ """Stream bounded HEAD and index metadata without reading worktree bytes."""
678
725
  candidates: set[str] = set()
679
726
  head_entries: dict[str, PathEntry] = {}
680
727
  index_entries: dict[str, PathEntry] = {}
@@ -697,7 +744,8 @@ def worktree_metadata(
697
744
  path = os.fsdecode(record)
698
745
  if path not in candidates:
699
746
  raise RuntimeError(
700
- f"staged change path was missing from worktree scope metadata: {path}"
747
+ "The worktree scope metadata does not contain this staged change "
748
+ f"path: '{path}'."
701
749
  )
702
750
  staged_change_paths.add(path)
703
751
 
@@ -747,7 +795,7 @@ def worktree_metadata(
747
795
 
748
796
  @dataclass(frozen=True)
749
797
  class FileSnapshot:
750
- """A regular file's streamed content identity and optional Git blob OID."""
798
+ """This record contains a regular-file identity and optional Git blob object identifier."""
751
799
 
752
800
  fingerprint: ContentFingerprint
753
801
  object_id: str | None
@@ -756,7 +804,7 @@ class FileSnapshot:
756
804
 
757
805
  @dataclass(frozen=True)
758
806
  class WorktreePathSnapshot:
759
- """No-follow worktree metadata plus raw content identity where applicable."""
807
+ """This record contains worktree metadata and the applicable raw content identity."""
760
808
 
761
809
  entry: PathEntry
762
810
  content: ContentFingerprint | None = None
@@ -765,7 +813,7 @@ class WorktreePathSnapshot:
765
813
 
766
814
  @dataclass(frozen=True)
767
815
  class WorktreeTrackedCapture:
768
- """One bounded representation of all worktree changes relative to HEAD."""
816
+ """This record contains all worktree changes relative to HEAD within a size limit."""
769
817
 
770
818
  paths: tuple[str, ...]
771
819
  fingerprint: ContentFingerprint
@@ -780,7 +828,8 @@ def git_object_format(repository_root: Path) -> str:
780
828
  hashlib.new(object_format)
781
829
  except ValueError as error:
782
830
  raise RuntimeError(
783
- f"unsupported Git object format for worktree review: {object_format}"
831
+ "The worktree review does not support this Git object format: "
832
+ f"'{object_format}'."
784
833
  ) from error
785
834
  return object_format
786
835
 
@@ -817,7 +866,7 @@ def read_regular_file_snapshot_without_following(
817
866
  relative_path: str,
818
867
  object_format: str | None = None,
819
868
  ) -> FileSnapshot:
820
- """Fingerprint a regular file without following links or blocking on a FIFO."""
869
+ """Fingerprint a regular file without following symbolic links or blocking on a named pipe."""
821
870
  nonblocking_value = getattr(os, "O_NONBLOCK", None)
822
871
  try:
823
872
  if nonblocking_value is None:
@@ -825,7 +874,8 @@ def read_regular_file_snapshot_without_following(
825
874
  nonblocking_flag = index(nonblocking_value)
826
875
  except TypeError as error:
827
876
  raise RuntimeError(
828
- "host cannot inspect repository files without nonblocking open support"
877
+ "The host cannot inspect repository files without support for a "
878
+ "nonblocking open operation."
829
879
  ) from error
830
880
  parent_descriptor, filename = nofollow_parent_descriptor(
831
881
  repository_root, relative_path
@@ -840,7 +890,8 @@ def read_regular_file_snapshot_without_following(
840
890
  )
841
891
  except (NotImplementedError, TypeError) as error:
842
892
  raise RuntimeError(
843
- "host cannot inspect repository paths without following links"
893
+ "The host cannot inspect repository paths without following symbolic "
894
+ "links."
844
895
  ) from error
845
896
  finally:
846
897
  try:
@@ -853,7 +904,9 @@ def read_regular_file_snapshot_without_following(
853
904
  try:
854
905
  initial_stat = os.fstat(descriptor)
855
906
  if not stat.S_ISREG(initial_stat.st_mode):
856
- raise RuntimeError(f"untracked path is not a regular file: {relative_path}")
907
+ raise RuntimeError(
908
+ f"The untracked path is not a regular file: '{relative_path}'."
909
+ )
857
910
  content_digest = sha256()
858
911
  object_digest = (
859
912
  hashlib.new(object_format) if object_format is not None else None
@@ -871,7 +924,7 @@ def read_regular_file_snapshot_without_following(
871
924
  initial_stat, final_stat
872
925
  ):
873
926
  raise RuntimeError(
874
- f"repository file changed while resolving scope: {relative_path}"
927
+ f"The repository file changed during scope resolution: '{relative_path}'."
875
928
  )
876
929
  return FileSnapshot(
877
930
  fingerprint=ContentFingerprint(
@@ -910,7 +963,8 @@ def worktree_path_snapshot(
910
963
  if entry.kind == "symlink":
911
964
  if entry.target is None:
912
965
  raise RuntimeError(
913
- f"repository link changed while resolving scope: {relative_path}"
966
+ "The repository symbolic link changed during scope resolution: "
967
+ f"'{relative_path}'."
914
968
  )
915
969
  contents = os.fsencode(entry.target)
916
970
  return WorktreePathSnapshot(
@@ -924,7 +978,7 @@ def worktree_path_snapshot(
924
978
  def git_mode_for_worktree_file(entry: PathEntry) -> str:
925
979
  """Map a regular filesystem mode to Git's executable-bit-only mode."""
926
980
  if entry.kind != "file" or entry.mode is None:
927
- raise RuntimeError(f"invalid worktree file entry: {entry.path}")
981
+ raise RuntimeError(f"The worktree file entry is not valid: '{entry.path}'.")
928
982
  return "100755" if int(entry.mode, 8) & 0o111 else "100644"
929
983
 
930
984
 
@@ -947,7 +1001,8 @@ def worktree_matches_entry(
947
1001
  and tree_entry.mode == "120000"
948
1002
  )
949
1003
  raise RuntimeError(
950
- f"worktree scope cannot safely compare Git object kind for {tree_entry.path}"
1004
+ "The worktree scope cannot safely compare the Git object type for "
1005
+ f"'{tree_entry.path}'."
951
1006
  )
952
1007
 
953
1008
 
@@ -997,15 +1052,15 @@ def worktree_tracked_capture(
997
1052
  and index_entry.kind == "git-submodule"
998
1053
  ):
999
1054
  raise RuntimeError(
1000
- "worktree scope cannot safely determine submodule state for "
1001
- f"{path}; use --staged or --range"
1055
+ "The worktree scope cannot safely determine the submodule state for "
1056
+ f"'{path}'. Use '--staged' or '--range'."
1002
1057
  )
1003
1058
  index_differs_from_head = index_entry != head_entry
1004
1059
  if path in metadata.skip_worktree_paths and snapshot.entry.kind == "absent":
1005
1060
  if index_differs_from_head:
1006
1061
  raise RuntimeError(
1007
- "worktree scope cannot safely inspect staged change in "
1008
- f"skip-worktree path {path}; use --staged"
1062
+ "The worktree scope cannot safely inspect the staged change in "
1063
+ f"skip-worktree path '{path}'. Use '--staged'."
1009
1064
  )
1010
1065
  continue
1011
1066
  if (
@@ -1014,8 +1069,8 @@ def worktree_tracked_capture(
1014
1069
  and not worktree_matches_entry(snapshot, index_entry)
1015
1070
  ):
1016
1071
  raise RuntimeError(
1017
- "worktree scope cannot safely inspect staged change whose live "
1018
- f"bytes differ from the index for {path}; use --staged"
1072
+ "The worktree scope cannot safely inspect a staged change whose live "
1073
+ f"bytes differ from the index for '{path}'. Use '--staged'."
1019
1074
  )
1020
1075
  if worktree_matches_entry(snapshot, head_entry):
1021
1076
  continue
@@ -1039,14 +1094,17 @@ def untracked_content(
1039
1094
  if entry.kind == "symlink":
1040
1095
  if entry.target is None:
1041
1096
  raise RuntimeError(
1042
- f"untracked link changed while resolving scope: {relative_path}"
1097
+ "The untracked symbolic link changed during scope resolution: "
1098
+ f"'{relative_path}'."
1043
1099
  )
1044
1100
  return b"symlink", content_fingerprint((os.fsencode(entry.target),))
1045
1101
  if entry.kind == "file":
1046
1102
  return b"file", read_regular_file_without_following(
1047
1103
  repository_root, relative_path
1048
1104
  )
1049
- raise RuntimeError(f"untracked path changed while resolving scope: {relative_path}")
1105
+ raise RuntimeError(
1106
+ f"The untracked path changed during scope resolution: '{relative_path}'."
1107
+ )
1050
1108
 
1051
1109
 
1052
1110
  def scope_digest(
@@ -1154,7 +1212,7 @@ def resolve_scope(
1154
1212
  paths = normalized_paths(repository_root, selected_paths)
1155
1213
  if scope == "range":
1156
1214
  if range_value is None:
1157
- raise RuntimeError("range scope requires BASE..HEAD")
1215
+ raise RuntimeError("The range scope requires 'BASE..HEAD'.")
1158
1216
  base, head = range_revisions(range_value, repository_root)
1159
1217
  else:
1160
1218
  head = verified_commit("HEAD", repository_root)
@@ -1163,9 +1221,11 @@ def resolve_scope(
1163
1221
  first_capture = capture_scope(scope, base, head, paths, repository_root)
1164
1222
  second_capture = capture_scope(scope, base, head, paths, repository_root)
1165
1223
  if first_capture != second_capture:
1166
- raise RuntimeError("change scope changed while resolving; retry the review")
1224
+ raise RuntimeError(
1225
+ "The change scope changed during resolution. Run the review again."
1226
+ )
1167
1227
  if scope != "range" and verified_commit("HEAD", repository_root) != head:
1168
- raise RuntimeError("HEAD changed while resolving; retry the review")
1228
+ raise RuntimeError("HEAD changed during resolution. Run the review again.")
1169
1229
  return {
1170
1230
  "base": base,
1171
1231
  "content_source": (