@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,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
@@ -1,17 +1,15 @@
1
1
  #!/usr/bin/env python3
2
- """Resolve an explicit PR or the sole open PR for the current branch."""
2
+ """Resolve an explicit pull request or the only open pull request for the current branch."""
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 dataclasses import dataclass
10
11
  from pathlib import Path
11
- from typing import Any
12
-
13
- if __package__ in {None, ""}:
14
- sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
12
+ from typing import TYPE_CHECKING, Any
15
13
 
16
14
  from pr_identity import (
17
15
  canonical_pull_request_url,
@@ -23,38 +21,59 @@ from pr_identity import (
23
21
  validate_pr_identifier,
24
22
  )
25
23
 
26
- from skills._cli import (
27
- argument_parser,
28
- git_read_arguments,
29
- git_read_environment,
30
- run_command,
31
- )
24
+ if TYPE_CHECKING or __package__ not in {None, ""}:
25
+ from skills._cli import (
26
+ argument_parser,
27
+ git_read_arguments,
28
+ git_read_environment,
29
+ run_command,
30
+ )
31
+ else:
32
+ _cli_path = Path(__file__).resolve().parents[2] / "_cli.py"
33
+ _cli_spec = importlib.util.spec_from_file_location(
34
+ "athena_installed_cli", _cli_path
35
+ )
36
+ if _cli_spec is None or _cli_spec.loader is None:
37
+ raise RuntimeError(
38
+ f"The installed Athena CLI helper is unavailable: '{_cli_path}'."
39
+ )
40
+ _cli = importlib.util.module_from_spec(_cli_spec)
41
+ _cli_spec.loader.exec_module(_cli)
42
+ argument_parser = _cli.argument_parser
43
+ git_read_arguments = _cli.git_read_arguments
44
+ git_read_environment = _cli.git_read_environment
45
+ run_command = _cli.run_command
32
46
 
33
47
  FIELDS = "number,url,state,headRefName,baseRefName,headRefOid,baseRefOid"
34
48
 
35
49
 
36
50
  @dataclass(frozen=True)
37
51
  class RepositoryTarget:
38
- """A forge target supplied without ambient CLI or checkout inference."""
52
+ """The workflow gets this forge target from explicit input.
53
+
54
+ It does not infer the target from the checkout.
55
+ """
39
56
 
40
57
  host: str
41
58
  repository: str
42
59
 
43
60
  def repository_argument(self) -> str:
44
- """Return the fully qualified repository target accepted by gh."""
61
+ """Return the fully qualified repository target for the GitHub CLI."""
45
62
  return f"{self.host}/{self.repository}"
46
63
 
47
64
 
48
65
  def command(*arguments: str) -> str:
49
66
  result = run_command(arguments, capture_output=True, text=True, check=False)
50
67
  if result.returncode != 0:
51
- message = result.stderr.strip() or f"command failed: {' '.join(arguments)}"
68
+ message = result.stderr.strip() or (
69
+ f"The command failed. Command: {' '.join(arguments)}"
70
+ )
52
71
  raise RuntimeError(message)
53
72
  return result.stdout
54
73
 
55
74
 
56
75
  def current_branch() -> str:
57
- """Return the current branch through the hermetic Git read boundary."""
76
+ """Return the current branch through the isolated Git read boundary."""
58
77
  result = run_command(
59
78
  ["git", *git_read_arguments(), "branch", "--show-current"],
60
79
  capture_output=True,
@@ -63,7 +82,9 @@ def current_branch() -> str:
63
82
  check=False,
64
83
  )
65
84
  if result.returncode != 0:
66
- message = result.stderr.strip() or "git branch --show-current failed"
85
+ message = (
86
+ result.stderr.strip() or "The git branch --show-current command failed."
87
+ )
67
88
  raise RuntimeError(message)
68
89
  return result.stdout.strip()
69
90
 
@@ -71,7 +92,7 @@ def current_branch() -> str:
71
92
  def load_object(output: str) -> dict[str, Any]:
72
93
  value = json.loads(output)
73
94
  if not isinstance(value, dict):
74
- raise TypeError("GitHub returned an invalid pull-request object")
95
+ raise TypeError("GitHub returned a pull-request object that is not valid.")
75
96
  return value
76
97
 
77
98
 
@@ -81,18 +102,18 @@ def target_from_arguments(
81
102
  host: str | None,
82
103
  repository: str | None,
83
104
  ) -> RepositoryTarget:
84
- """Resolve a trusted target from explicit flags or one canonical PR URL."""
105
+ """Resolve a trusted target from explicit options or one canonical pull-request URL."""
85
106
 
86
107
  def identity_from_url(value: str) -> tuple[int, str]:
87
108
  try:
88
109
  number = pull_request_number(value)
89
110
  return number, repository_from_pr_url(value, number)
90
111
  except RuntimeError as error:
91
- parser.error(f"invalid pull-request URL: {error}")
92
- raise AssertionError("argument parser returned after a URL error")
112
+ parser.error(f"The pull-request URL is not valid: {error}")
113
+ raise AssertionError("The argument parser returned after a URL error.")
93
114
 
94
115
  if (host is None) != (repository is None):
95
- parser.error("--target-host and --target-repository must be supplied together")
116
+ parser.error("Specify --target-host and --target-repository together.")
96
117
  if host is not None and repository is not None:
97
118
  try:
98
119
  target = RepositoryTarget(
@@ -104,7 +125,9 @@ def target_from_arguments(
104
125
  if identifier is not None and identifier.startswith("https://"):
105
126
  _, supplied_repository = identity_from_url(identifier)
106
127
  if supplied_repository.casefold() != target.repository.casefold():
107
- parser.error("pull-request URL does not match --target-repository")
128
+ parser.error(
129
+ "The pull-request URL does not match '--target-repository'."
130
+ )
108
131
  return target
109
132
  if identifier is not None and identifier.startswith("https://"):
110
133
  _, repository = identity_from_url(identifier)
@@ -113,20 +136,22 @@ def target_from_arguments(
113
136
  repository=repository,
114
137
  )
115
138
  parser.error(
116
- "numeric pull requests and branch discovery require --target-host and "
117
- "--target-repository"
139
+ "For a numeric pull-request identifier or branch discovery, specify "
140
+ "'--target-host' and '--target-repository'."
118
141
  )
119
- raise AssertionError("argument parser returned after a target error")
142
+ raise AssertionError("The argument parser returned after a target error.")
120
143
 
121
144
 
122
145
  def _resolve_open_pr(identifier: str, target: RepositoryTarget) -> dict[str, Any]:
123
- """Return the complete metadata for one explicitly identified open PR."""
146
+ """Return complete metadata for one explicitly identified pull request."""
124
147
  validate_pr_identifier(identifier)
125
148
  number = pull_request_number(identifier)
126
149
  if identifier.startswith("https://"):
127
150
  supplied_repository = repository_from_pr_url(identifier, number)
128
151
  if supplied_repository.casefold() != target.repository.casefold():
129
- raise RuntimeError("pull-request URL does not match the retained target")
152
+ raise RuntimeError(
153
+ "The pull-request URL does not match the retained target."
154
+ )
130
155
  pull_request = load_object(
131
156
  command(
132
157
  "gh",
@@ -140,12 +165,14 @@ def _resolve_open_pr(identifier: str, target: RepositoryTarget) -> dict[str, Any
140
165
  )
141
166
  )
142
167
  if pull_request.get("state") != "OPEN":
143
- raise RuntimeError(f"pull request {identifier} is not open")
168
+ raise RuntimeError(f"The pull request is not open: '{identifier}'.")
144
169
  if pull_request.get("number") != number:
145
- raise RuntimeError("GitHub returned a pull request different from the request")
170
+ raise RuntimeError(
171
+ "GitHub returned a pull request that differs from the request."
172
+ )
146
173
  for field in ("baseRefOid", "headRefOid"):
147
174
  require_commit_oid(
148
- pull_request.get(field), f"GitHub immutable PR revision {field}"
175
+ pull_request.get(field), f"GitHub immutable pull-request revision {field}"
149
176
  )
150
177
  return pull_request
151
178
 
@@ -153,18 +180,21 @@ def _resolve_open_pr(identifier: str, target: RepositoryTarget) -> dict[str, Any
153
180
  def _validate_repository_identity(
154
181
  pull_request: dict[str, Any], target: RepositoryTarget
155
182
  ) -> None:
156
- """Reject a PR URL that differs from the retained explicit forge target."""
183
+ """Reject a pull-request URL that differs from the retained forge target."""
157
184
  number = pull_request.get("number")
158
185
  url = pull_request.get("url")
159
186
  if not isinstance(number, int) or not isinstance(url, str):
160
- raise TypeError("GitHub returned incomplete pull-request identity")
187
+ raise TypeError("GitHub returned an incomplete pull-request identity.")
161
188
  pull_repository = repository_from_pr_url(url, number)
162
189
  if pull_repository.casefold() != target.repository.casefold():
163
190
  raise RuntimeError(
164
- f"pull request {url} does not belong to target repository {target.repository}"
191
+ f"The pull request {url} is not in the target repository "
192
+ f"'{target.repository}'."
165
193
  )
166
194
  if url != canonical_pull_request_url(target.repository, number):
167
- raise RuntimeError(f"GitHub returned invalid pull-request URL: {url}")
195
+ raise RuntimeError(
196
+ f"GitHub returned a pull-request URL that is not valid: '{url}'."
197
+ )
168
198
  pull_request["review_target"] = {
169
199
  "host": target.host,
170
200
  "kind": "github",
@@ -180,7 +210,9 @@ def resolve(explicit: str | None, target: RepositoryTarget) -> dict[str, Any]:
180
210
 
181
211
  branch = current_branch()
182
212
  if not branch:
183
- raise RuntimeError("current checkout is detached; provide a PR number or URL")
213
+ raise RuntimeError(
214
+ "The current checkout is detached. Specify a pull-request number or URL."
215
+ )
184
216
  raw_candidates = json.loads(
185
217
  command(
186
218
  "gh",
@@ -199,20 +231,24 @@ def resolve(explicit: str | None, target: RepositoryTarget) -> dict[str, Any]:
199
231
  )
200
232
  )
201
233
  if not isinstance(raw_candidates, list):
202
- raise TypeError("GitHub returned an invalid pull-request list")
234
+ raise TypeError("GitHub returned a pull-request list that is not valid.")
203
235
  candidates = [item for item in raw_candidates if isinstance(item, dict)]
204
236
  if len(candidates) == 1:
205
237
  number = candidates[0].get("number")
206
238
  if not isinstance(number, int) or number < 1:
207
- raise RuntimeError("GitHub returned an invalid pull-request candidate")
239
+ raise RuntimeError(
240
+ "GitHub returned a pull-request candidate that is not valid."
241
+ )
208
242
  return _resolve_open_pr(str(number), target)
209
243
  if not candidates:
210
- raise LookupError(f"no open pull request found for branch {branch!r}")
244
+ raise LookupError(f"GitHub found no open pull request for branch {branch!r}.")
211
245
  rendered = "\n".join(
212
246
  f" #{candidate.get('number')}: {candidate.get('url')}"
213
247
  for candidate in candidates
214
248
  )
215
- raise ValueError(f"multiple open pull requests found for {branch!r}:\n{rendered}")
249
+ raise ValueError(
250
+ f"GitHub found multiple open pull requests for {branch!r}.\n{rendered}"
251
+ )
216
252
 
217
253
 
218
254
  def main(argv: Sequence[str] | None = None) -> int:
@@ -220,12 +256,12 @@ def main(argv: Sequence[str] | None = None) -> int:
220
256
  parser.add_argument(
221
257
  "--target-host",
222
258
  metavar="HOST",
223
- help="canonical GitHub host from the configured forge capability",
259
+ help="Use the canonical GitHub host from the configured forge capability.",
224
260
  )
225
261
  parser.add_argument(
226
262
  "--target-repository",
227
263
  metavar="OWNER/REPOSITORY",
228
- help="canonical GitHub repository from the configured forge capability",
264
+ help="Use the canonical GitHub repository from the configured forge capability.",
229
265
  )
230
266
  parser.add_argument("pull_request", nargs="?", metavar="PR_NUMBER_OR_URL")
231
267
  arguments = parser.parse_args(argv)