claude-dev-env 2.12.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.
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
- package/agents/CLAUDE.md +1 -2
- package/agents/pr-description-writer.md +11 -81
- package/bin/install.mjs +48 -9
- package/bin/install.prune.test.mjs +26 -0
- package/bin/install.settings-defaults.test.mjs +60 -33
- package/bin/install.test.mjs +2 -4
- package/commands/sr-loop.md +9 -5
- package/docs/CLAUDE.md +0 -1
- package/docs/references/CLAUDE.md +0 -1
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -27
- package/hooks/blocking/config/CLAUDE.md +1 -13
- package/hooks/blocking/conventional_pr_title_gate.py +1 -2
- package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +4 -4
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
- package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
- package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
- package/hooks/diagnostic/CLAUDE.md +3 -3
- package/hooks/diagnostic/hook_log_extractor.py +2 -36
- package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
- package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
- package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
- package/hooks/git-hooks/pre_push.py +3 -85
- package/hooks/git-hooks/test_pre_push.py +0 -130
- package/hooks/hooks.json +0 -35
- package/hooks/hooks_constants/CLAUDE.md +0 -9
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
- package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
- package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
- package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
- package/hooks/lifecycle/CLAUDE.md +1 -3
- package/package.json +1 -1
- package/rules/CLAUDE.md +0 -2
- package/rules/gh-cli-conventions.md +0 -1
- package/rules/git-workflow.md +1 -3
- package/scripts/CLAUDE.md +0 -1
- package/scripts/_code_review_test_support.py +0 -95
- package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
- package/scripts/invoke_code_review.py +8 -451
- package/scripts/test_invoke_code_review.py +5 -61
- package/scripts/test_invoke_code_review_cli.py +1 -45
- package/scripts/test_invoke_code_review_contract.py +1 -118
- package/settings.json +1 -10
- package/skills/CLAUDE.md +8 -0
- package/skills/autoconverge/reference/CLAUDE.md +1 -1
- package/skills/autoconverge/reference/convergence.md +7 -7
- package/skills/autoconverge/reference/gotchas.md +3 -10
- package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
- package/skills/autoconverge/workflow/converge.mjs +201 -238
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
- package/skills/bugteam/CONSTRAINTS.md +1 -1
- package/skills/bugteam/SKILL.md +9 -0
- package/skills/bugteam/reference/audit-and-teammates.md +10 -6
- package/skills/bugteam/test_skill_additions.py +9 -0
- package/skills/comments/SKILL.md +65 -0
- package/skills/descriptions/SKILL.md +51 -0
- package/skills/emergencies/SKILL.md +42 -0
- package/skills/imagegen/SKILL.md +21 -0
- package/skills/imagegen/scripts/config/__init__.py +1 -0
- package/skills/imagegen/scripts/config/constants.py +28 -0
- package/skills/imagegen/scripts/imagegen.py +45 -0
- package/skills/imagegen/scripts/imagegen_core.py +528 -0
- package/skills/imagegen/scripts/test_imagegen.py +204 -0
- package/skills/orchestrator/SKILL.md +2 -4
- package/skills/plan-to-pr/SKILL.md +6 -7
- package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
- package/skills/plan-to-pr/reference/process-inventory.md +2 -2
- package/skills/plan-to-pr/reference/review-loop.md +2 -2
- package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
- package/skills/plan-to-pr/reference/task-seeds.md +1 -1
- package/skills/plan-to-pr/reference/task-ticket.md +3 -4
- package/skills/plan-to-pr/scripts/config/constants.py +1 -2
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
- package/skills/plan-to-pr/test_skill_contract.py +2 -2
- package/skills/pr-converge/SKILL.md +13 -16
- package/skills/pr-converge/reference/progress-checklist.md +6 -5
- package/skills/pr-converge/reference/state-schema.md +1 -27
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
- package/skills/pr-loop-lifecycle/SKILL.md +1 -1
- package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
- package/skills/prototype/SKILL.md +4 -4
- package/skills/prototype/reference/honest-limitations.md +4 -4
- package/skills/prototype/reference/promotion-tasks.md +2 -2
- package/skills/prototype/workflows/promotion.md +4 -6
- package/skills/reviews/SKILL.md +85 -0
- package/skills/small-cl/SKILL.md +52 -0
- package/agents/code-verifier.md +0 -74
- package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
- package/docs/references/code-review-enforcement.md +0 -132
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
- package/hooks/blocking/code_review_gate_deny.py +0 -74
- package/hooks/blocking/code_review_pr_create_gate.py +0 -198
- package/hooks/blocking/code_review_push_gate.py +0 -145
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
- package/hooks/blocking/code_review_stamp_store.py +0 -233
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
- package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
- package/hooks/blocking/config/verified_commit_constants.py +0 -160
- package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
- package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
- package/hooks/blocking/conftest.py +0 -123
- package/hooks/blocking/convergence_gate_blocker.py +0 -310
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
- package/hooks/blocking/pr_description_body_audit.py +0 -148
- package/hooks/blocking/pr_description_command_parser.py +0 -237
- package/hooks/blocking/pr_description_enforcer.py +0 -227
- package/hooks/blocking/pr_description_pr_number.py +0 -155
- package/hooks/blocking/pr_description_proof_of_work.py +0 -385
- package/hooks/blocking/pr_description_readability.py +0 -364
- package/hooks/blocking/reviewer_spawn_gate.py +0 -182
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
- package/hooks/blocking/test_code_review_gate_deny.py +0 -54
- package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
- package/hooks/blocking/test_code_review_push_gate.py +0 -205
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
- package/hooks/blocking/test_code_review_stamp_store.py +0 -205
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
- package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
- package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
- package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
- package/hooks/blocking/test_pr_description_enforcer.py +0 -221
- package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
- package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
- package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
- package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
- package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
- package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
- package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
- package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
- package/hooks/blocking/test_verification_verdict_store.py +0 -974
- package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
- package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
- package/hooks/blocking/test_verified_commit_gate.py +0 -581
- package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
- package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
- package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
- package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
- package/hooks/blocking/verification_verdict_store.py +0 -1039
- package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
- package/hooks/blocking/verified_commit_gate.py +0 -113
- package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
- package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
- package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
- package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
- package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
- package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
- package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
- package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
- package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
- package/hooks/blocking/verifier_verdict_minter.py +0 -280
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
- package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
- package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
- package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
- package/rules/proof-of-work-pr-comments.md +0 -22
- package/rules/verified-commit-gate-skip.md +0 -4
- 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] [
|
|
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
|
|
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
|
|
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
|
|
531
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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"
|