claude-dev-env 2.11.0 → 2.13.0

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 (203) hide show
  1. package/_shared/advisor/reference/sol-rung.md +5 -2
  2. package/_shared/advisor/reference/warm-up.md +1 -0
  3. package/_shared/advisor/scripts/codex_sol_advisor.py +96 -31
  4. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +5 -0
  5. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +173 -11
  6. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
  7. package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
  8. package/agents/CLAUDE.md +1 -2
  9. package/agents/pr-description-writer.md +11 -81
  10. package/bin/install.mjs +48 -9
  11. package/bin/install.prune.test.mjs +26 -0
  12. package/bin/install.settings-defaults.test.mjs +60 -33
  13. package/bin/install.test.mjs +2 -4
  14. package/commands/CLAUDE.md +1 -0
  15. package/commands/sr-loop.md +52 -0
  16. package/docs/CLAUDE.md +0 -1
  17. package/docs/references/CLAUDE.md +0 -1
  18. package/hooks/CLAUDE.md +1 -1
  19. package/hooks/blocking/CLAUDE.md +2 -27
  20. package/hooks/blocking/config/CLAUDE.md +1 -13
  21. package/hooks/blocking/conventional_pr_title_gate.py +1 -2
  22. package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
  23. package/hooks/blocking/stop_dispatcher.py +4 -4
  24. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
  25. package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
  26. package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
  27. package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
  28. package/hooks/diagnostic/CLAUDE.md +3 -3
  29. package/hooks/diagnostic/hook_log_extractor.py +2 -36
  30. package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
  31. package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
  32. package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
  33. package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
  34. package/hooks/git-hooks/pre_push.py +3 -85
  35. package/hooks/git-hooks/test_pre_push.py +0 -130
  36. package/hooks/hooks.json +0 -35
  37. package/hooks/hooks_constants/CLAUDE.md +0 -9
  38. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
  39. package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
  40. package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
  41. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
  42. package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
  43. package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
  44. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
  45. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
  46. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
  47. package/hooks/lifecycle/CLAUDE.md +1 -3
  48. package/package.json +1 -1
  49. package/rules/CLAUDE.md +0 -2
  50. package/rules/gh-cli-conventions.md +0 -1
  51. package/rules/git-workflow.md +1 -3
  52. package/scripts/CLAUDE.md +0 -1
  53. package/scripts/_code_review_test_support.py +0 -95
  54. package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
  55. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
  56. package/scripts/invoke_code_review.py +8 -451
  57. package/scripts/test_invoke_code_review.py +5 -61
  58. package/scripts/test_invoke_code_review_cli.py +1 -45
  59. package/scripts/test_invoke_code_review_contract.py +1 -118
  60. package/settings.json +1 -10
  61. package/skills/CLAUDE.md +8 -0
  62. package/skills/autoconverge/reference/CLAUDE.md +1 -1
  63. package/skills/autoconverge/reference/convergence.md +7 -7
  64. package/skills/autoconverge/reference/gotchas.md +3 -10
  65. package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
  66. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
  67. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
  68. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
  69. package/skills/autoconverge/workflow/converge.mjs +201 -238
  70. package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
  71. package/skills/bugteam/CONSTRAINTS.md +1 -1
  72. package/skills/bugteam/SKILL.md +9 -0
  73. package/skills/bugteam/reference/audit-and-teammates.md +10 -6
  74. package/skills/bugteam/test_skill_additions.py +9 -0
  75. package/skills/comments/SKILL.md +65 -0
  76. package/skills/descriptions/SKILL.md +51 -0
  77. package/skills/emergencies/SKILL.md +42 -0
  78. package/skills/imagegen/SKILL.md +21 -0
  79. package/skills/imagegen/scripts/config/__init__.py +1 -0
  80. package/skills/imagegen/scripts/config/constants.py +28 -0
  81. package/skills/imagegen/scripts/imagegen.py +45 -0
  82. package/skills/imagegen/scripts/imagegen_core.py +528 -0
  83. package/skills/imagegen/scripts/test_imagegen.py +204 -0
  84. package/skills/orchestrator/SKILL.md +2 -4
  85. package/skills/plan-to-pr/SKILL.md +6 -7
  86. package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
  87. package/skills/plan-to-pr/reference/process-inventory.md +2 -2
  88. package/skills/plan-to-pr/reference/review-loop.md +2 -2
  89. package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
  90. package/skills/plan-to-pr/reference/task-seeds.md +1 -1
  91. package/skills/plan-to-pr/reference/task-ticket.md +3 -4
  92. package/skills/plan-to-pr/scripts/config/constants.py +1 -2
  93. package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
  94. package/skills/plan-to-pr/test_skill_contract.py +2 -2
  95. package/skills/pr-converge/SKILL.md +13 -16
  96. package/skills/pr-converge/reference/progress-checklist.md +6 -5
  97. package/skills/pr-converge/reference/state-schema.md +1 -27
  98. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  99. package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
  100. package/skills/pr-loop-lifecycle/SKILL.md +1 -1
  101. package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
  102. package/skills/prototype/SKILL.md +4 -4
  103. package/skills/prototype/reference/honest-limitations.md +4 -4
  104. package/skills/prototype/reference/promotion-tasks.md +2 -2
  105. package/skills/prototype/workflows/promotion.md +4 -6
  106. package/skills/reviews/SKILL.md +85 -0
  107. package/skills/small-cl/SKILL.md +52 -0
  108. package/skills/usage-pause/SKILL.md +1 -1
  109. package/skills/usage-pause/scripts/resolve_usage_window.py +32 -4
  110. package/skills/usage-pause/scripts/test_resolve_usage_window.py +26 -0
  111. package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +3 -1
  112. package/agents/code-verifier.md +0 -74
  113. package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
  114. package/docs/references/code-review-enforcement.md +0 -132
  115. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
  116. package/hooks/blocking/code_review_gate_deny.py +0 -74
  117. package/hooks/blocking/code_review_pr_create_gate.py +0 -198
  118. package/hooks/blocking/code_review_push_gate.py +0 -145
  119. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
  120. package/hooks/blocking/code_review_stamp_store.py +0 -233
  121. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
  122. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
  123. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
  124. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
  125. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
  126. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
  127. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
  128. package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
  129. package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
  130. package/hooks/blocking/config/verified_commit_constants.py +0 -160
  131. package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
  132. package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
  133. package/hooks/blocking/conftest.py +0 -123
  134. package/hooks/blocking/convergence_gate_blocker.py +0 -310
  135. package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
  136. package/hooks/blocking/pr_description_body_audit.py +0 -148
  137. package/hooks/blocking/pr_description_command_parser.py +0 -237
  138. package/hooks/blocking/pr_description_enforcer.py +0 -227
  139. package/hooks/blocking/pr_description_pr_number.py +0 -155
  140. package/hooks/blocking/pr_description_proof_of_work.py +0 -385
  141. package/hooks/blocking/pr_description_readability.py +0 -364
  142. package/hooks/blocking/reviewer_spawn_gate.py +0 -182
  143. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
  144. package/hooks/blocking/test_code_review_gate_deny.py +0 -54
  145. package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
  146. package/hooks/blocking/test_code_review_push_gate.py +0 -205
  147. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
  148. package/hooks/blocking/test_code_review_stamp_store.py +0 -205
  149. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
  150. package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
  151. package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
  152. package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
  153. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
  154. package/hooks/blocking/test_pr_description_enforcer.py +0 -221
  155. package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
  156. package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
  157. package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
  158. package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
  159. package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
  160. package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
  161. package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
  162. package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
  163. package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
  164. package/hooks/blocking/test_verification_verdict_store.py +0 -974
  165. package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
  166. package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
  167. package/hooks/blocking/test_verified_commit_gate.py +0 -581
  168. package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
  169. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
  170. package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
  171. package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
  172. package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
  173. package/hooks/blocking/verification_verdict_store.py +0 -1039
  174. package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
  175. package/hooks/blocking/verified_commit_gate.py +0 -113
  176. package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
  177. package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
  178. package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
  179. package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
  180. package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
  181. package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
  182. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
  183. package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
  184. package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
  185. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
  186. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
  187. package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
  188. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
  189. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
  190. package/hooks/blocking/verifier_verdict_minter.py +0 -280
  191. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
  192. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
  193. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
  194. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
  195. package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
  196. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
  197. package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
  198. package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
  199. package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
  200. package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
  201. package/rules/proof-of-work-pr-comments.md +0 -22
  202. package/rules/verified-commit-gate-skip.md +0 -4
  203. package/scripts/Show-Asset.ps1 +0 -106
@@ -16,19 +16,15 @@ the spawn does not wait for interactive input. Result JSON on stdout only::
16
16
 
17
17
  {"mode", "served_command", "returncode", "dirty_tree"}
18
18
 
19
- ``--record-stamp`` forces chain mode, loops a capped number of review passes,
20
- and mints a clean stamp only when a pass exits 0 with a stable surface hash.
21
-
22
19
  Import ``invoke_code_review`` for the outcome object, or run as a CLI::
23
20
 
24
21
  python invoke_code_review.py --cwd <dir> --session-model <alias>
25
- [--timeout-seconds N] [--record-stamp] [effort]
22
+ [--timeout-seconds N] [effort]
26
23
  """
27
24
 
28
25
  from __future__ import annotations
29
26
 
30
27
  import argparse
31
- import importlib.util
32
28
  import json
33
29
  import subprocess
34
30
  import sys
@@ -36,7 +32,6 @@ import threading
36
32
  from collections.abc import Callable, Sequence
37
33
  from dataclasses import dataclass
38
34
  from pathlib import Path
39
- from types import ModuleType
40
35
 
41
36
  if str(Path(__file__).resolve().parent) not in sys.path:
42
37
  sys.path.insert(0, str(Path(__file__).resolve().parent))
@@ -64,7 +59,6 @@ from dev_env_scripts_constants.code_review_constants import ( # noqa: E402
64
59
  ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER,
65
60
  CLI_EFFORT_HELP,
66
61
  CLI_EFFORT_METAVAR,
67
- CLI_RECORD_STAMP_HELP,
68
62
  CLI_SESSION_MODEL_FLAG,
69
63
  CODE_REVIEW_FIX_FLAG,
70
64
  CODE_REVIEW_MODEL_ALIAS,
@@ -78,27 +72,14 @@ from dev_env_scripts_constants.code_review_constants import ( # noqa: E402
78
72
  IN_SESSION_RETURNCODE,
79
73
  INVALID_EFFORT_MESSAGE,
80
74
  INVALID_EFFORT_RETURNCODE,
81
- MAXIMUM_STAMP_MINT_PASSES,
82
75
  MODE_CHAIN,
83
76
  MODE_IN_SESSION,
84
77
  REVIEW_PERMISSION_MODE as PERMISSION_MODE_BYPASS,
85
78
  PERMISSION_MODE_FLAG,
86
- RECORD_STAMP_FLAG,
87
- RESULT_KEY_BOUND_HASH,
88
79
  RESULT_KEY_DIRTY_TREE,
89
80
  RESULT_KEY_MODE,
90
- RESULT_KEY_PASS_COUNT,
91
81
  RESULT_KEY_RETURNCODE,
92
82
  RESULT_KEY_SERVED_COMMAND,
93
- RESULT_KEY_STAMP_MINTED,
94
- STAMP_DID_NOT_CONVERGE_MESSAGE,
95
- STAMP_DID_NOT_CONVERGE_RETURNCODE,
96
- STAMP_STORE_IMPORT_FAILURE_MESSAGE,
97
- STAMP_STORE_LIVE_SURFACE_HASH_NAME,
98
- STAMP_STORE_MODULE_FILE_NAME,
99
- STAMP_STORE_MODULE_NAME,
100
- STAMP_STORE_RECORD_CLEAN_STAMP_NAME,
101
- STAMP_STORE_RESOLVE_REPO_ROOT_NAME,
102
83
  SUCCESSFUL_REVIEW_RETURNCODE,
103
84
  )
104
85
  from dev_env_scripts_constants.grok_worker_constants import ( # noqa: E402
@@ -139,25 +120,6 @@ class CodeReviewOutcome:
139
120
  is_dirty_tree: bool
140
121
 
141
122
 
142
- @dataclass(frozen=True)
143
- class StampMintOutcome:
144
- """Outcome of a ``--record-stamp`` minting run.
145
-
146
- ::
147
-
148
- stable rc 0 pass -> is_stamp_minted True, bound_hash set
149
- cap hit / unstable -> is_stamp_minted False, non-zero returncode
150
-
151
- The attributes carry the last review, whether a stamp was minted, the pass
152
- count, and the surface hash the stamp was bound to when minted.
153
- """
154
-
155
- review_outcome: CodeReviewOutcome
156
- is_stamp_minted: bool
157
- pass_count: int
158
- bound_hash: str | None
159
-
160
-
161
123
  review_claude_runner = run_claude
162
124
  review_host_profile_detector = detect_host_profile
163
125
  review_git_status_runner = subprocess.run
@@ -168,65 +130,6 @@ TextCapturingSubprocessRunner = Callable[
168
130
  ]
169
131
 
170
132
 
171
- def _stamp_store_file_path() -> Path:
172
- """Return the stamp store module path in the sibling hooks/blocking tree."""
173
- blocking_directory = Path(__file__).resolve().parent.parent / "hooks" / "blocking"
174
- return blocking_directory / STAMP_STORE_MODULE_FILE_NAME
175
-
176
-
177
- def _load_store_from_spec(store_file_path: Path) -> ModuleType:
178
- """Import the stamp store from *store_file_path*, re-raising a missing dep."""
179
- blocking_directory_string = str(store_file_path.parent)
180
- if blocking_directory_string not in sys.path:
181
- sys.path.insert(0, blocking_directory_string)
182
- module_spec = importlib.util.spec_from_file_location(
183
- STAMP_STORE_MODULE_NAME, store_file_path
184
- )
185
- if module_spec is None or module_spec.loader is None:
186
- raise ModuleNotFoundError(
187
- f"could not create import spec for {store_file_path}",
188
- name=STAMP_STORE_MODULE_NAME,
189
- )
190
- store_module = importlib.util.module_from_spec(module_spec)
191
- sys.modules[STAMP_STORE_MODULE_NAME] = store_module
192
- try:
193
- module_spec.loader.exec_module(store_module)
194
- except ModuleNotFoundError:
195
- del sys.modules[STAMP_STORE_MODULE_NAME]
196
- raise
197
- return store_module
198
-
199
-
200
- def load_code_review_stamp_store() -> ModuleType:
201
- """Import the stamp store module from the installed or repo hooks tree.
202
-
203
- ::
204
-
205
- code_review_stamp_store.py present -> module with record_clean_stamp
206
- module file missing -> ModuleNotFoundError (loud)
207
-
208
- A genuine missing dependency of the store still raises rather than being
209
- swallowed, so ``--record-stamp`` fails loudly when it cannot mint.
210
-
211
- Returns:
212
- The loaded ``code_review_stamp_store`` module.
213
-
214
- Raises:
215
- ModuleNotFoundError: When the store file is absent or a real import
216
- dependency of the store is missing.
217
- """
218
- store_file_path = _stamp_store_file_path()
219
- if not store_file_path.is_file():
220
- raise ModuleNotFoundError(
221
- f"code review stamp store not found at {store_file_path}",
222
- name=STAMP_STORE_MODULE_NAME,
223
- )
224
- cached_module = sys.modules.get(STAMP_STORE_MODULE_NAME)
225
- if cached_module is not None:
226
- return cached_module
227
- return _load_store_from_spec(store_file_path)
228
-
229
-
230
133
  def is_opus_session_model(session_model: str) -> bool:
231
134
  """Return True when *session_model* is the opus short alias (any letter case).
232
135
 
@@ -345,8 +248,7 @@ def is_working_tree_dirty(working_directory: Path) -> bool:
345
248
  is_working_tree_dirty(dirty_repo) # ok: True
346
249
  is_working_tree_dirty(broken_git) # ok: True (non-zero status)
347
250
 
348
- A non-zero ``git status`` return code must not report clean: the caller
349
- treats an unknown tree as dirty so a clean stamp cannot bypass the gate.
251
+ A non-zero ``git status`` return code reports a dirty tree.
350
252
 
351
253
  Args:
352
254
  working_directory: Git working tree to inspect.
@@ -384,8 +286,7 @@ def is_successful_code_review(review_outcome: CodeReviewOutcome) -> bool:
384
286
  review_outcome: Structured outcome from ``invoke_code_review``.
385
287
 
386
288
  Returns:
387
- True when the outcome is a successful serve that may stamp clean if
388
- the working tree is also clean.
289
+ True when the outcome is a successful review serve.
389
290
  """
390
291
  if review_outcome.returncode != SUCCESSFUL_REVIEW_RETURNCODE:
391
292
  return False
@@ -394,32 +295,6 @@ def is_successful_code_review(review_outcome: CodeReviewOutcome) -> bool:
394
295
  return True
395
296
 
396
297
 
397
- def is_code_review_clean_stamp_allowed(review_outcome: CodeReviewOutcome) -> bool:
398
- """Return True when the outcome may set ``code_review_clean_at``.
399
-
400
- ::
401
-
402
- is_code_review_clean_stamp_allowed(chain_clean_ok) # ok: True
403
- is_code_review_clean_stamp_allowed(chain_failed) # ok: False
404
- is_code_review_clean_stamp_allowed(chain_dirty_ok) # ok: False
405
-
406
- Clean stamp requires a successful serve and a clean working tree.
407
- ``dirty_tree`` alone is not enough: a failed chain leaves the tree clean
408
- and must stay in CODE_REVIEW.
409
-
410
- Args:
411
- review_outcome: Structured outcome from ``invoke_code_review``.
412
-
413
- Returns:
414
- True only when the review succeeded and ``is_dirty_tree`` is False.
415
- """
416
- if not is_successful_code_review(review_outcome):
417
- return False
418
- if review_outcome.is_dirty_tree:
419
- return False
420
- return True
421
-
422
-
423
298
  def _run_claude_with_empty_stdin(
424
299
  all_claude_arguments: list[str],
425
300
  *,
@@ -521,34 +396,24 @@ def invoke_code_review(
521
396
  session_model: str,
522
397
  timeout_seconds: int,
523
398
  effort: str = DEFAULT_CODE_REVIEW_EFFORT,
524
- is_force_chain: bool = False,
525
399
  ) -> CodeReviewOutcome:
526
400
  """Run or hand off ``/code-review`` based on host profile and session model.
527
401
 
528
402
  ::
529
403
 
530
- Claude + opus, is_force_chain False -> in_session (no spawn)
531
- Claude + opus, is_force_chain True -> chain (headless spawn)
532
- any host with sonnet -> chain
404
+ Claude + opus -> in_session (no spawn)
405
+ any host with sonnet -> chain (headless spawn)
533
406
 
534
407
  Args:
535
408
  working_directory: PR working tree used as cwd for the chain spawn.
536
409
  session_model: Caller-stated session model short alias.
537
410
  timeout_seconds: Timeout applied to each chain binary invocation.
538
411
  effort: Effort token embedded in the ``/code-review`` prompt.
539
- is_force_chain: When True, always spawn chain mode (used by
540
- ``--record-stamp`` so the invoker observes the review).
541
412
 
542
413
  Returns:
543
414
  Structured outcome including mode, served binary, return code, and
544
415
  whether the working tree is dirty after a chain run.
545
416
  """
546
- if is_force_chain:
547
- return _run_chain_review(
548
- working_directory=working_directory,
549
- timeout_seconds=timeout_seconds,
550
- effort=effort,
551
- )
552
417
  host_profile = review_host_profile_detector()
553
418
  review_mode = decide_review_mode(
554
419
  host_profile=host_profile,
@@ -563,188 +428,6 @@ def invoke_code_review(
563
428
  )
564
429
 
565
430
 
566
- def _surface_hash_before_and_after_are_stable(
567
- before_hash: str | None,
568
- after_hash: str | None,
569
- ) -> bool:
570
- if before_hash is None:
571
- return False
572
- if after_hash is None:
573
- return False
574
- return before_hash == after_hash
575
-
576
-
577
- def _mint_stamp_for_stable_pass(
578
- *,
579
- store_module: ModuleType,
580
- working_directory: Path,
581
- surface_hash: str,
582
- effort: str,
583
- ) -> bool:
584
- resolve_repo_root = getattr(store_module, STAMP_STORE_RESOLVE_REPO_ROOT_NAME)
585
- record_clean_stamp = getattr(store_module, STAMP_STORE_RECORD_CLEAN_STAMP_NAME)
586
- repo_root = resolve_repo_root(str(working_directory))
587
- if repo_root is None:
588
- return False
589
- record_clean_stamp(repo_root, surface_hash, effort)
590
- return True
591
-
592
-
593
- def _mint_outcome_when_stable_clean(
594
- *,
595
- store_module: ModuleType,
596
- working_directory: Path,
597
- effort: str,
598
- pass_number: int,
599
- before_hash: str,
600
- review_outcome: CodeReviewOutcome,
601
- ) -> StampMintOutcome | None:
602
- is_minted = _mint_stamp_for_stable_pass(
603
- store_module=store_module,
604
- working_directory=working_directory,
605
- surface_hash=before_hash,
606
- effort=effort,
607
- )
608
- if not is_minted:
609
- return None
610
- return StampMintOutcome(
611
- review_outcome=review_outcome,
612
- is_stamp_minted=True,
613
- pass_count=pass_number,
614
- bound_hash=before_hash,
615
- )
616
-
617
-
618
- def _unminted_pass_outcome(
619
- review_outcome: CodeReviewOutcome, pass_number: int
620
- ) -> StampMintOutcome:
621
- return StampMintOutcome(
622
- review_outcome=review_outcome,
623
- is_stamp_minted=False,
624
- pass_count=pass_number,
625
- bound_hash=None,
626
- )
627
-
628
-
629
- def _stamp_outcome_for_pass(
630
- *,
631
- store_module: ModuleType,
632
- working_directory: Path,
633
- effort: str,
634
- pass_number: int,
635
- before_hash: str | None,
636
- after_hash: str | None,
637
- review_outcome: CodeReviewOutcome,
638
- ) -> StampMintOutcome | None:
639
- is_stable = _surface_hash_before_and_after_are_stable(before_hash, after_hash)
640
- is_successful = is_successful_code_review(review_outcome)
641
- is_empty_surface = before_hash is None and after_hash is None
642
- if is_successful and is_empty_surface:
643
- return _unminted_pass_outcome(review_outcome, pass_number)
644
- if is_successful and is_stable and before_hash is not None:
645
- minted = _mint_outcome_when_stable_clean(
646
- store_module=store_module,
647
- working_directory=working_directory,
648
- effort=effort,
649
- pass_number=pass_number,
650
- before_hash=before_hash,
651
- review_outcome=review_outcome,
652
- )
653
- if minted is not None:
654
- return minted
655
- if is_stable:
656
- return _unminted_pass_outcome(review_outcome, pass_number)
657
- return None
658
-
659
-
660
- def _run_one_stamp_mint_pass(
661
- *,
662
- store_module: ModuleType,
663
- live_surface_hash: Callable[..., str | None],
664
- working_directory: Path,
665
- timeout_seconds: int,
666
- effort: str,
667
- pass_number: int,
668
- ) -> tuple[CodeReviewOutcome, StampMintOutcome | None]:
669
- before_hash = live_surface_hash(str(working_directory))
670
- review_outcome = invoke_code_review(
671
- working_directory=working_directory,
672
- session_model=CODE_REVIEW_MODEL_ALIAS,
673
- timeout_seconds=timeout_seconds,
674
- effort=effort,
675
- is_force_chain=True,
676
- )
677
- after_hash = live_surface_hash(str(working_directory))
678
- maybe_outcome = _stamp_outcome_for_pass(
679
- store_module=store_module,
680
- working_directory=working_directory,
681
- effort=effort,
682
- pass_number=pass_number,
683
- before_hash=before_hash,
684
- after_hash=after_hash,
685
- review_outcome=review_outcome,
686
- )
687
- return review_outcome, maybe_outcome
688
-
689
-
690
- def _iterate_stamp_mint_passes(
691
- *,
692
- store_module: ModuleType,
693
- live_surface_hash: Callable[..., str | None],
694
- working_directory: Path,
695
- timeout_seconds: int,
696
- effort: str,
697
- maximum_passes: int,
698
- ) -> StampMintOutcome:
699
- last_review_outcome = _failure_code_review_outcome(
700
- STAMP_DID_NOT_CONVERGE_RETURNCODE
701
- )
702
- for each_pass_number in range(1, maximum_passes + 1):
703
- last_review_outcome, maybe_mint_outcome = _run_one_stamp_mint_pass(
704
- store_module=store_module,
705
- live_surface_hash=live_surface_hash,
706
- working_directory=working_directory,
707
- timeout_seconds=timeout_seconds,
708
- effort=effort,
709
- pass_number=each_pass_number,
710
- )
711
- if maybe_mint_outcome is not None:
712
- return maybe_mint_outcome
713
- return _unminted_pass_outcome(last_review_outcome, maximum_passes)
714
-
715
-
716
- def invoke_code_review_and_record_stamp(
717
- *,
718
- working_directory: Path,
719
- session_model: str,
720
- timeout_seconds: int,
721
- effort: str,
722
- maximum_passes: int = MAXIMUM_STAMP_MINT_PASSES,
723
- ) -> StampMintOutcome:
724
- """Force chain review passes until the surface is stable, then mint a stamp.
725
-
726
- Args:
727
- working_directory: PR working tree used as cwd for each chain spawn.
728
- session_model: Session model short alias (unused; chain mode forced).
729
- timeout_seconds: Timeout applied to each chain binary invocation.
730
- effort: Effort token the clean review records on the stamp.
731
- maximum_passes: Hard cap on review passes before giving up.
732
-
733
- Returns:
734
- A StampMintOutcome for the last review and whether a stamp was minted.
735
- """
736
- del session_model
737
- store_module = load_code_review_stamp_store()
738
- return _iterate_stamp_mint_passes(
739
- store_module=store_module,
740
- live_surface_hash=getattr(store_module, STAMP_STORE_LIVE_SURFACE_HASH_NAME),
741
- working_directory=working_directory,
742
- timeout_seconds=timeout_seconds,
743
- effort=effort,
744
- maximum_passes=maximum_passes,
745
- )
746
-
747
-
748
431
  def encode_code_review_outcome(
749
432
  review_outcome: CodeReviewOutcome,
750
433
  ) -> dict[str, object]:
@@ -764,27 +447,6 @@ def encode_code_review_outcome(
764
447
  }
765
448
 
766
449
 
767
- def encode_stamp_mint_outcome(
768
- mint_outcome: StampMintOutcome,
769
- ) -> dict[str, object]:
770
- """Encode a stamp-mint outcome as the JSON-serializable payload.
771
-
772
- Args:
773
- mint_outcome: The ``--record-stamp`` outcome to encode.
774
-
775
- Returns:
776
- A plain dict with the review fields plus mint metadata.
777
- """
778
- encoded_payload = encode_code_review_outcome(mint_outcome.review_outcome)
779
- encoded_payload[RESULT_KEY_STAMP_MINTED] = mint_outcome.is_stamp_minted
780
- encoded_payload[RESULT_KEY_PASS_COUNT] = mint_outcome.pass_count
781
- encoded_payload[RESULT_KEY_BOUND_HASH] = mint_outcome.bound_hash
782
- if not mint_outcome.is_stamp_minted:
783
- if mint_outcome.pass_count >= MAXIMUM_STAMP_MINT_PASSES:
784
- encoded_payload[RESULT_KEY_RETURNCODE] = STAMP_DID_NOT_CONVERGE_RETURNCODE
785
- return encoded_payload
786
-
787
-
788
450
  def _add_review_arguments(parser: argparse.ArgumentParser) -> None:
789
451
  parser.add_argument(
790
452
  CWD_FLAG,
@@ -808,13 +470,7 @@ def _add_review_arguments(parser: argparse.ArgumentParser) -> None:
808
470
  )
809
471
 
810
472
 
811
- def _add_stamp_and_effort_arguments(parser: argparse.ArgumentParser) -> None:
812
- parser.add_argument(
813
- RECORD_STAMP_FLAG,
814
- dest="is_record_stamp",
815
- action="store_true",
816
- help=CLI_RECORD_STAMP_HELP,
817
- )
473
+ def _add_effort_argument(parser: argparse.ArgumentParser) -> None:
818
474
  parser.add_argument(
819
475
  CLI_EFFORT_METAVAR,
820
476
  nargs="?",
@@ -830,7 +486,7 @@ def _build_argument_parser() -> argparse.ArgumentParser:
830
486
  )
831
487
  )
832
488
  _add_review_arguments(parser)
833
- _add_stamp_and_effort_arguments(parser)
489
+ _add_effort_argument(parser)
834
490
  return parser
835
491
 
836
492
 
@@ -842,96 +498,6 @@ def _emit_invalid_effort_and_exit_code(effort: str) -> int:
842
498
  return INVALID_EFFORT_RETURNCODE
843
499
 
844
500
 
845
- def _import_failure_payload() -> dict[str, object]:
846
- failure_outcome = _failure_code_review_outcome(INVALID_EFFORT_RETURNCODE)
847
- encoded_payload = encode_code_review_outcome(failure_outcome)
848
- encoded_payload[RESULT_KEY_STAMP_MINTED] = False
849
- encoded_payload[RESULT_KEY_PASS_COUNT] = 0
850
- encoded_payload[RESULT_KEY_BOUND_HASH] = None
851
- return encoded_payload
852
-
853
-
854
- def _no_mint_outcome(returncode: int) -> StampMintOutcome:
855
- return StampMintOutcome(
856
- review_outcome=_failure_code_review_outcome(returncode),
857
- is_stamp_minted=False,
858
- pass_count=0,
859
- bound_hash=None,
860
- )
861
-
862
-
863
- def _mint_or_config_outcome(
864
- *,
865
- working_directory: Path,
866
- session_model: str,
867
- timeout_seconds: int,
868
- effort: str,
869
- ) -> StampMintOutcome:
870
- try:
871
- return invoke_code_review_and_record_stamp(
872
- working_directory=working_directory,
873
- session_model=session_model,
874
- timeout_seconds=timeout_seconds,
875
- effort=effort,
876
- )
877
- except ChainConfigurationError as configuration_error:
878
- sys.stderr.write(str(configuration_error) + "\n")
879
- return _no_mint_outcome(CHAIN_CONFIG_ERROR_EXIT_CODE)
880
- except ValueError as host_profile_error:
881
- sys.stderr.write(str(host_profile_error) + "\n")
882
- return _no_mint_outcome(HOST_PROFILE_ERROR_RETURNCODE)
883
-
884
-
885
- def _emit_import_failure(import_error: ModuleNotFoundError) -> int:
886
- sys.stderr.write(
887
- STAMP_STORE_IMPORT_FAILURE_MESSAGE.format(error=import_error) + "\n"
888
- )
889
- sys.stdout.write(json.dumps(_import_failure_payload()) + "\n")
890
- return INVALID_EFFORT_RETURNCODE
891
-
892
-
893
- def _record_stamp_exit_code(mint_outcome: StampMintOutcome) -> int:
894
- if mint_outcome.is_stamp_minted:
895
- return mint_outcome.review_outcome.returncode
896
- if mint_outcome.pass_count >= MAXIMUM_STAMP_MINT_PASSES:
897
- return STAMP_DID_NOT_CONVERGE_RETURNCODE
898
- return mint_outcome.review_outcome.returncode
899
-
900
-
901
- def _emit_mint_outcome(mint_outcome: StampMintOutcome) -> int:
902
- encoded_payload = encode_stamp_mint_outcome(mint_outcome)
903
- did_not_converge = (
904
- not mint_outcome.is_stamp_minted
905
- and mint_outcome.pass_count >= MAXIMUM_STAMP_MINT_PASSES
906
- )
907
- if did_not_converge:
908
- sys.stderr.write(
909
- STAMP_DID_NOT_CONVERGE_MESSAGE.format(pass_count=mint_outcome.pass_count)
910
- + "\n"
911
- )
912
- sys.stdout.write(json.dumps(encoded_payload) + "\n")
913
- return _record_stamp_exit_code(mint_outcome)
914
-
915
-
916
- def _run_record_stamp_cli(
917
- *,
918
- working_directory: Path,
919
- session_model: str,
920
- timeout_seconds: int,
921
- effort: str,
922
- ) -> int:
923
- try:
924
- mint_outcome = _mint_or_config_outcome(
925
- working_directory=working_directory,
926
- session_model=session_model,
927
- timeout_seconds=timeout_seconds,
928
- effort=effort,
929
- )
930
- except ModuleNotFoundError as import_error:
931
- return _emit_import_failure(import_error)
932
- return _emit_mint_outcome(mint_outcome)
933
-
934
-
935
501
  def _run_plain_review_cli(*, parsed_arguments: argparse.Namespace, effort: str) -> int:
936
502
  try:
937
503
  review_outcome = invoke_code_review(
@@ -951,9 +517,7 @@ def _run_plain_review_cli(*, parsed_arguments: argparse.Namespace, effort: str)
951
517
  def main(all_command_arguments: list[str]) -> int:
952
518
  """Run the invoker for CLI arguments and print the JSON outcome.
953
519
 
954
- ``--record-stamp`` forces chain mode and mints only on a surface-stable
955
- returncode-0 pass; an unknown or ``ultra`` effort exits non-zero before any
956
- review runs.
520
+ An unknown or ``ultra`` effort exits non-zero before any review runs.
957
521
 
958
522
  Args:
959
523
  all_command_arguments: The argument vector after the program name.
@@ -968,13 +532,6 @@ def main(all_command_arguments: list[str]) -> int:
968
532
  invalid_effort_exit_code = _emit_invalid_effort_and_exit_code(effort_token)
969
533
  if invalid_effort_exit_code != SUCCESSFUL_REVIEW_RETURNCODE:
970
534
  return invalid_effort_exit_code
971
- if parsed_arguments.is_record_stamp:
972
- return _run_record_stamp_cli(
973
- working_directory=parsed_arguments.working_directory,
974
- session_model=parsed_arguments.session_model,
975
- timeout_seconds=parsed_arguments.timeout_seconds,
976
- effort=effort_token,
977
- )
978
535
  return _run_plain_review_cli(parsed_arguments=parsed_arguments, effort=effort_token)
979
536
 
980
537
 
@@ -1,10 +1,4 @@
1
- """Specifications for what a blocked mint tells the person it blocked.
2
-
3
- Enforcement refuses every push until a clean stamp covers the branch surface,
4
- and ``invoke_code_review.py --record-stamp`` is the only way to mint one. When
5
- that call cannot run, the reason it names is the person's only route back to a
6
- working push.
7
- """
1
+ """Specifications for code-review error reporting and review-loop outcomes."""
8
2
 
9
3
  from __future__ import annotations
10
4
 
@@ -13,8 +7,7 @@ from pathlib import Path
13
7
  import pytest
14
8
 
15
9
  import invoke_code_review as invoker
16
- from claude_chain_runner import ChainConfigurationError, ChainInvocationOutcome
17
- from _code_review_test_support import FIXTURE_SESSION_OPUS
10
+ from claude_chain_runner import ChainInvocationOutcome
18
11
  from dev_env_scripts_constants.claude_chain_constants import TERMINAL_STATUS_SERVED
19
12
  from dev_env_scripts_constants.code_review_constants import (
20
13
  ALL_FINDING_SEVERITIES,
@@ -52,8 +45,7 @@ from dev_env_scripts_constants.code_review_constants import (
52
45
  def test_review_arguments_carry_the_permission_mode_this_caller_resolves() -> None:
53
46
  """The review command asks for a permission mode the binary accepts here.
54
47
 
55
- The binary refuses the bypass mode outright for a root caller, so asking
56
- for it there means no review runs and no stamp is ever minted.
48
+ The binary refuses the bypass mode outright for a root caller.
57
49
  """
58
50
  all_arguments = invoker.build_code_review_arguments(DEFAULT_CODE_REVIEW_EFFORT)
59
51
 
@@ -104,8 +96,8 @@ def test_failed_review_reports_what_the_served_binary_wrote(
104
96
  """A review that a served binary refused names the refusal.
105
97
 
106
98
  The binary can decline for reasons the caller must act on, such as a
107
- permission mode it will not accept. Dropping its words leaves a bare
108
- exit code, and no stamp is minted either way.
99
+ permission mode it will not accept. Dropping its words leaves a bare exit
100
+ code without a usable recovery step.
109
101
  """
110
102
  monkeypatch.setattr(
111
103
  invoker, "_run_claude_with_empty_stdin", _serve_a_refusing_binary
@@ -122,54 +114,6 @@ def test_failed_review_reports_what_the_served_binary_wrote(
122
114
  assert REVIEW_BINARY_REFUSAL_TEXT in captured_streams.err
123
115
 
124
116
 
125
- def test_missing_chain_config_reports_its_remedy_on_stderr(
126
- monkeypatch: pytest.MonkeyPatch,
127
- tmp_path: Path,
128
- capsys: pytest.CaptureFixture[str],
129
- ) -> None:
130
- def raise_chain_configuration_error(**_all_keyword_arguments: object) -> None:
131
- raise ChainConfigurationError(CHAIN_CONFIG_REMEDY_TEXT)
132
-
133
- monkeypatch.setattr(
134
- invoker, "invoke_code_review_and_record_stamp", raise_chain_configuration_error
135
- )
136
-
137
- outcome = invoker._mint_or_config_outcome(
138
- working_directory=tmp_path,
139
- session_model=FIXTURE_SESSION_OPUS,
140
- timeout_seconds=MINT_TIMEOUT_SECONDS,
141
- effort=DEFAULT_CODE_REVIEW_EFFORT,
142
- )
143
-
144
- captured_streams = capsys.readouterr()
145
- assert outcome.is_stamp_minted is False
146
- assert CHAIN_CONFIG_REMEDY_TEXT in captured_streams.err
147
-
148
-
149
- def test_host_profile_failure_reports_its_reason_on_stderr(
150
- monkeypatch: pytest.MonkeyPatch,
151
- tmp_path: Path,
152
- capsys: pytest.CaptureFixture[str],
153
- ) -> None:
154
- def raise_host_profile_error(**_all_keyword_arguments: object) -> None:
155
- raise ValueError(HOST_PROFILE_FAILURE_TEXT)
156
-
157
- monkeypatch.setattr(
158
- invoker, "invoke_code_review_and_record_stamp", raise_host_profile_error
159
- )
160
-
161
- outcome = invoker._mint_or_config_outcome(
162
- working_directory=tmp_path,
163
- session_model=FIXTURE_SESSION_OPUS,
164
- timeout_seconds=MINT_TIMEOUT_SECONDS,
165
- effort=DEFAULT_CODE_REVIEW_EFFORT,
166
- )
167
-
168
- captured_streams = capsys.readouterr()
169
- assert outcome.is_stamp_minted is False
170
- assert HOST_PROFILE_FAILURE_TEXT in captured_streams.err
171
-
172
-
173
117
  HEAD_SHA_ONE: str = "aaa111"
174
118
  HEAD_SHA_TWO: str = "bbb222"
175
119
  HEAD_SHA_THREE: str = "ccc333"