claude-dev-env 1.58.0 → 1.60.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/CLAUDE.md +2 -2
- package/_shared/pr-loop/scripts/code_rules_gate.py +36 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +6 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +12 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +265 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +29 -0
- package/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md +1 -1
- package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
- package/bin/install.mjs +100 -27
- package/bin/install.test.mjs +133 -1
- package/docs/CODE_RULES.md +3 -3
- package/hooks/blocking/code_rules_annotations_length.py +153 -0
- package/hooks/blocking/code_rules_dead_dataclass_field.py +319 -0
- package/hooks/blocking/code_rules_dead_module_constant.py +321 -0
- package/hooks/blocking/code_rules_duplicate_body.py +439 -0
- package/hooks/blocking/code_rules_enforcer.py +190 -21
- package/hooks/blocking/code_rules_magic_values.py +98 -0
- package/hooks/blocking/code_rules_shared.py +41 -0
- package/hooks/blocking/code_rules_typeddict_stub.py +172 -0
- package/hooks/blocking/config/__init__.py +5 -0
- package/hooks/blocking/config/verified_commit_constants.py +106 -0
- package/hooks/blocking/destructive_command_blocker.py +1027 -12
- package/hooks/blocking/hook_prose_detector_consistency.py +150 -0
- package/hooks/blocking/subprocess_budget_completeness.py +380 -0
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +225 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
- package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +146 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_dataclass_field.py +467 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +188 -0
- package/hooks/blocking/test_code_rules_enforcer_duplicate_body.py +330 -0
- package/hooks/blocking/test_code_rules_enforcer_duplicate_body_hook_routing.py +179 -0
- package/hooks/blocking/test_code_rules_enforcer_magic_slice_bounds.py +133 -0
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +415 -0
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +156 -0
- package/hooks/blocking/test_destructive_command_blocker.py +622 -3
- package/hooks/blocking/test_hook_prose_detector_consistency.py +265 -0
- package/hooks/blocking/test_subprocess_budget_completeness.py +588 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +720 -0
- package/hooks/blocking/test_verification_verdict_store.py +278 -0
- package/hooks/blocking/test_verified_commit_gate.py +368 -0
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -0
- package/hooks/blocking/test_verifier_verdict_minter.py +214 -0
- package/hooks/blocking/test_workflow_substitution_slot_blocker.py +242 -0
- package/hooks/blocking/verdict_directory_write_blocker.py +667 -0
- package/hooks/blocking/verification_verdict_store.py +446 -0
- package/hooks/blocking/verified_commit_gate.py +523 -0
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +152 -0
- package/hooks/blocking/verifier_verdict_minter.py +299 -0
- package/hooks/blocking/workflow_substitution_slot_blocker.py +159 -0
- package/hooks/diagnostic/test_hook_log_extractor.py +3 -3
- package/hooks/hooks.json +58 -1
- package/hooks/hooks_constants/blocking_check_limits.py +1 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +16 -0
- package/hooks/hooks_constants/dead_dataclass_field_constants.py +25 -0
- package/hooks/hooks_constants/dead_module_constant_constants.py +20 -0
- package/hooks/hooks_constants/destructive_command_segment_constants.py +178 -0
- package/hooks/hooks_constants/duplicate_function_body_constants.py +34 -0
- package/hooks/hooks_constants/hook_prose_detector_consistency_constants.py +30 -0
- package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +1 -1
- package/hooks/hooks_constants/subprocess_budget_completeness_constants.py +5 -0
- package/hooks/hooks_constants/workflow_substitution_slot_blocker_constants.py +22 -0
- package/package.json +1 -1
- package/rules/docstring-prose-matches-implementation.md +43 -0
- package/rules/file-global-constants.md +7 -1
- package/rules/hook-prose-matches-detector.md +26 -0
- package/rules/no-cross-skill-duplicate-helpers.md +29 -0
- package/rules/no-inline-destructive-literals.md +11 -0
- package/rules/workflow-substitution-slots.md +7 -0
- package/skills/_shared/pr-loop/scripts/preflight_worktree.py +392 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +70 -0
- package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +263 -0
- package/skills/autoconverge/SKILL.md +67 -19
- package/skills/autoconverge/reference/closing-report.md +59 -17
- package/skills/autoconverge/reference/convergence.md +7 -3
- package/skills/autoconverge/reference/stop-conditions.md +7 -2
- package/skills/autoconverge/workflow/aggregate_runs.py +371 -0
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +193 -76
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +76 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +206 -206
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +265 -0
- package/skills/autoconverge/workflow/converge.mjs +234 -42
- package/skills/autoconverge/workflow/convergence_summary.py +110 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +7 -0
- package/skills/autoconverge/workflow/render_report.py +488 -397
- package/skills/autoconverge/workflow/test_aggregate_runs.py +134 -0
- package/skills/autoconverge/workflow/test_convergence_summary.py +132 -0
- package/skills/autoconverge/workflow/test_render_report.py +488 -259
- package/skills/pr-converge/reference/per-tick.md +28 -8
- package/skills/pr-converge/scripts/check_convergence.py +195 -64
- package/skills/pr-converge/scripts/test_check_convergence.py +173 -2
- package/skills/rebase/SKILL.md +2 -4
- package/skills/update/SKILL.md +37 -5
- package/system-prompts/software-engineer.xml +2 -6
- package/hooks/blocking/content_search_to_zoekt_redirector.py +0 -59
- package/hooks/blocking/content_search_zoekt_bash_block_reason.py +0 -25
- package/hooks/blocking/content_search_zoekt_block_payload.py +0 -21
- package/hooks/blocking/content_search_zoekt_indexed_paths.py +0 -24
- package/hooks/blocking/content_search_zoekt_indexed_roots_config.py +0 -131
- package/hooks/blocking/content_search_zoekt_redirect_guidance.py +0 -52
- package/hooks/blocking/test_content_search_to_zoekt_redirector_integration.py +0 -61
- package/hooks/blocking/test_content_search_to_zoekt_redirector_unit.py +0 -92
- package/hooks/blocking/test_content_search_zoekt_indexed_roots_config.py +0 -102
|
@@ -26,6 +26,7 @@ from hooks_constants.blocking_check_limits import ( # noqa: E402
|
|
|
26
26
|
MAX_STUB_IMPLEMENTATION_ISSUES,
|
|
27
27
|
MAX_THIN_WRAPPER_ISSUES,
|
|
28
28
|
MAX_TYPED_DICT_PAIR_ISSUES,
|
|
29
|
+
MAX_ZERO_PAYLOAD_ALIAS_ISSUES,
|
|
29
30
|
)
|
|
30
31
|
|
|
31
32
|
def _pascal_to_snake_case(pascal_name: str) -> str:
|
|
@@ -58,6 +59,16 @@ def _collect_module_function_names(parsed_tree: ast.AST) -> set[str]:
|
|
|
58
59
|
return module_function_names
|
|
59
60
|
|
|
60
61
|
|
|
62
|
+
def _collect_module_function_nodes_by_name(
|
|
63
|
+
parsed_tree: ast.AST,
|
|
64
|
+
) -> dict[str, ast.FunctionDef | ast.AsyncFunctionDef]:
|
|
65
|
+
function_node_by_name: dict[str, ast.FunctionDef | ast.AsyncFunctionDef] = {}
|
|
66
|
+
for each_statement in parsed_tree.body:
|
|
67
|
+
if isinstance(each_statement, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
68
|
+
function_node_by_name[each_statement.name] = each_statement
|
|
69
|
+
return function_node_by_name
|
|
70
|
+
|
|
71
|
+
|
|
61
72
|
def _is_init_file(file_path: str) -> bool:
|
|
62
73
|
return file_path.replace("\\", "/").rsplit("/", 1)[-1] == "__init__.py"
|
|
63
74
|
|
|
@@ -126,6 +137,167 @@ def check_thin_wrapper_files(content: str, file_path: str) -> list[str]:
|
|
|
126
137
|
return issues[:MAX_THIN_WRAPPER_ISSUES]
|
|
127
138
|
|
|
128
139
|
|
|
140
|
+
def _function_parameter_names_in_order(
|
|
141
|
+
function_node: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
142
|
+
) -> list[str]:
|
|
143
|
+
arguments = function_node.args
|
|
144
|
+
positional_arguments = [*arguments.posonlyargs, *arguments.args]
|
|
145
|
+
return [each_argument.arg for each_argument in positional_arguments]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _has_only_positional_parameters(
|
|
149
|
+
function_node: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
150
|
+
) -> bool:
|
|
151
|
+
arguments = function_node.args
|
|
152
|
+
has_no_parameter_defaults = not arguments.defaults and not arguments.kw_defaults
|
|
153
|
+
return (
|
|
154
|
+
not arguments.kwonlyargs
|
|
155
|
+
and arguments.vararg is None
|
|
156
|
+
and arguments.kwarg is None
|
|
157
|
+
and has_no_parameter_defaults
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _single_return_call(
|
|
162
|
+
function_node: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
163
|
+
) -> ast.Call | None:
|
|
164
|
+
body_statements = function_node.body
|
|
165
|
+
statements_after_docstring = (
|
|
166
|
+
body_statements[1:]
|
|
167
|
+
if body_statements and _statement_is_docstring(body_statements[0])
|
|
168
|
+
else body_statements
|
|
169
|
+
)
|
|
170
|
+
if len(statements_after_docstring) != 1:
|
|
171
|
+
return None
|
|
172
|
+
only_statement = statements_after_docstring[0]
|
|
173
|
+
if not isinstance(only_statement, ast.Return):
|
|
174
|
+
return None
|
|
175
|
+
return only_statement.value if isinstance(only_statement.value, ast.Call) else None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _forwards_parameters_unchanged(call_node: ast.Call, all_parameter_names: list[str]) -> bool:
|
|
179
|
+
if call_node.keywords:
|
|
180
|
+
return False
|
|
181
|
+
if len(call_node.args) != len(all_parameter_names):
|
|
182
|
+
return False
|
|
183
|
+
for each_argument, each_parameter_name in zip(call_node.args, all_parameter_names):
|
|
184
|
+
if not isinstance(each_argument, ast.Name) or each_argument.id != each_parameter_name:
|
|
185
|
+
return False
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _function_is_async(function_node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
|
190
|
+
return isinstance(function_node, ast.AsyncFunctionDef)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _alias_target_name(call_node: ast.Call, all_sibling_function_names: set[str]) -> str:
|
|
194
|
+
callee = call_node.func
|
|
195
|
+
if not isinstance(callee, ast.Name):
|
|
196
|
+
return ""
|
|
197
|
+
return callee.id if callee.id in all_sibling_function_names else ""
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _module_string_literal_values(parsed_tree: ast.AST) -> set[str]:
|
|
201
|
+
string_literal_values: set[str] = set()
|
|
202
|
+
for each_node in ast.walk(parsed_tree):
|
|
203
|
+
if isinstance(each_node, ast.Constant) and isinstance(each_node.value, str):
|
|
204
|
+
string_literal_values.add(each_node.value)
|
|
205
|
+
return string_literal_values
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _target_accepts_forwarded_positional_call(
|
|
209
|
+
target_node: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
210
|
+
forwarded_argument_count: int,
|
|
211
|
+
) -> bool:
|
|
212
|
+
arguments = target_node.args
|
|
213
|
+
if any(default is None for default in arguments.kw_defaults):
|
|
214
|
+
return False
|
|
215
|
+
positional_parameters = [*arguments.posonlyargs, *arguments.args]
|
|
216
|
+
total_positional_count = len(positional_parameters)
|
|
217
|
+
required_positional_count = total_positional_count - len(arguments.defaults)
|
|
218
|
+
if arguments.vararg is not None:
|
|
219
|
+
return forwarded_argument_count >= required_positional_count
|
|
220
|
+
return required_positional_count <= forwarded_argument_count <= total_positional_count
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def check_zero_payload_function_alias(content: str, file_path: str) -> list[str]:
|
|
224
|
+
"""Flag a module-level function that only forwards its parameters to a sibling.
|
|
225
|
+
|
|
226
|
+
A function whose entire body (after an optional docstring) is a single
|
|
227
|
+
`return sibling_function(first_param, second_param, ...)` that forwards its
|
|
228
|
+
own parameters unchanged to another function defined in the same module is a
|
|
229
|
+
second name for one behavior — indirection without payload, which CODE_RULES
|
|
230
|
+
discourages. Callers should invoke the sibling directly. Both `def` and
|
|
231
|
+
`async def` forwarders are inspected.
|
|
232
|
+
|
|
233
|
+
A forwarder is left unflagged when any of these makes a direct call to the
|
|
234
|
+
target not equivalent to the alias: a decorator (caching, `@property`, route
|
|
235
|
+
registration); a parameter carrying a default value the target rejects; a
|
|
236
|
+
keyword-only / `*args` / `**kwargs` parameter on the alias; an awaitability
|
|
237
|
+
mismatch where one of the alias and target is `async def` and the other is
|
|
238
|
+
not; a forwarded positional call the live target's signature rejects (the
|
|
239
|
+
target has a keyword-only parameter without a default, or its positional
|
|
240
|
+
arity does not admit the forwarded argument count); or a name dispatched by a
|
|
241
|
+
string literal elsewhere in the module, where the named handler must exist for
|
|
242
|
+
a registry to resolve it.
|
|
243
|
+
|
|
244
|
+
Hook infrastructure is intentionally NOT exempt — pass-through aliases inside
|
|
245
|
+
hook modules are the motivating case. Test files and config files are exempt
|
|
246
|
+
because re-binding aliases are legitimate scaffolding there.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
content: The source under inspection.
|
|
250
|
+
file_path: Path to the file, used for the test and config exemptions.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
One issue string per pass-through alias, capped at
|
|
254
|
+
MAX_ZERO_PAYLOAD_ALIAS_ISSUES.
|
|
255
|
+
"""
|
|
256
|
+
if is_test_file(file_path) or is_config_file(file_path):
|
|
257
|
+
return []
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
parsed_tree = ast.parse(content)
|
|
261
|
+
except SyntaxError:
|
|
262
|
+
return []
|
|
263
|
+
|
|
264
|
+
function_node_by_name = _collect_module_function_nodes_by_name(parsed_tree)
|
|
265
|
+
all_sibling_function_names = set(function_node_by_name)
|
|
266
|
+
all_string_literal_values = _module_string_literal_values(parsed_tree)
|
|
267
|
+
issues: list[str] = []
|
|
268
|
+
for each_statement in parsed_tree.body:
|
|
269
|
+
if not isinstance(each_statement, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
270
|
+
continue
|
|
271
|
+
if each_statement.decorator_list:
|
|
272
|
+
continue
|
|
273
|
+
if each_statement.name in all_string_literal_values:
|
|
274
|
+
continue
|
|
275
|
+
if not _has_only_positional_parameters(each_statement):
|
|
276
|
+
continue
|
|
277
|
+
call_node = _single_return_call(each_statement)
|
|
278
|
+
if call_node is None:
|
|
279
|
+
continue
|
|
280
|
+
target_name = _alias_target_name(call_node, all_sibling_function_names)
|
|
281
|
+
if not target_name or target_name == each_statement.name:
|
|
282
|
+
continue
|
|
283
|
+
target_node = function_node_by_name[target_name]
|
|
284
|
+
if _function_is_async(each_statement) != _function_is_async(target_node):
|
|
285
|
+
continue
|
|
286
|
+
forwarded_parameter_names = _function_parameter_names_in_order(each_statement)
|
|
287
|
+
if not _forwards_parameters_unchanged(call_node, forwarded_parameter_names):
|
|
288
|
+
continue
|
|
289
|
+
if not _target_accepts_forwarded_positional_call(
|
|
290
|
+
target_node, len(forwarded_parameter_names)
|
|
291
|
+
):
|
|
292
|
+
continue
|
|
293
|
+
issues.append(
|
|
294
|
+
f"Line {each_statement.lineno}: {file_path}: zero-payload alias — "
|
|
295
|
+
f"{each_statement.name} only forwards its parameters to {target_name}; "
|
|
296
|
+
f"callers should call {target_name} directly (indirection without payload)"
|
|
297
|
+
)
|
|
298
|
+
return issues[:MAX_ZERO_PAYLOAD_ALIAS_ISSUES]
|
|
299
|
+
|
|
300
|
+
|
|
129
301
|
def check_typed_dict_encode_decode(content: str, file_path: str) -> list[str]:
|
|
130
302
|
"""Flag TypedDict declarations missing companion `_encode_*` / `_decode_*` functions."""
|
|
131
303
|
if (
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Constants for the verified-commit gate hook family.
|
|
2
|
+
|
|
3
|
+
Shared by ``verification_verdict_store.py``, ``verified_commit_gate.py``,
|
|
4
|
+
and ``verifier_verdict_minter.py`` so every tunable lives in one place.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
GIT_TIMEOUT_SECONDS = 30
|
|
10
|
+
ROOT_KEY_HEX_LENGTH = 16
|
|
11
|
+
VERDICT_JSON_INDENT = 2
|
|
12
|
+
CLAUDE_HOME_DIRECTORY_NAME = ".claude"
|
|
13
|
+
VERDICT_DIRECTORY_NAME = "verification"
|
|
14
|
+
VERDICT_DIRECTORY_NAME_SEPARATOR_PATTERN = r"['\"\\/,\s]+"
|
|
15
|
+
VERDICT_DIRECTORY_PATH_BOUNDARY_PATTERN = r"(?=['\"]*[\\/,])"
|
|
16
|
+
RELATIVE_VERDICT_DIRECTORY_PATTERN = r"(?:^|(?<=[\s;&|(='\"]))verification[\\/]"
|
|
17
|
+
VERDICT_PATH_GLUE_PATTERN = r"['\"+\\/\s]*[\\/]['\"+\\/\s]*"
|
|
18
|
+
VERDICT_DIRECTORY_CHANGE_TARGET_PATTERN = r"[ \t]+['\"]?verification[\\/]?['\"]?(?=[\s;&|]|$)"
|
|
19
|
+
VERDICT_PATH_JOINED_VARIABLE_PATTERN = r"\$\{?(\w+)\}?[\\/]|[\\/]\$\{?(\w+)\}?"
|
|
20
|
+
VERDICT_PATH_VARIABLE_ASSIGNMENT_PATTERN = r"(?:^|(?<=[\s;&|(]))%s=(\S+)"
|
|
21
|
+
VERDICT_FILE_RELATIVE_REFERENCE_PATTERN = (
|
|
22
|
+
rf"(?:^|(?<=[\s;&|(='\"\\/]))verification[\\/][0-9a-f]{{{ROOT_KEY_HEX_LENGTH}}}\.json"
|
|
23
|
+
)
|
|
24
|
+
PATH_OBFUSCATION_PRIMITIVE_PATTERN = (
|
|
25
|
+
r"chr\s*\(|bytes\.fromhex\s*\(|b64decode\s*\(|codecs\.decode\s*\("
|
|
26
|
+
r"|(?:bytes|bytearray)\s*\(\s*\[|\[char\[?\]?\]"
|
|
27
|
+
)
|
|
28
|
+
ALL_VERDICT_PATH_SEGMENT_NAMES = (".claude", "verification")
|
|
29
|
+
ALL_VERDICT_PATH_SEGMENT_BODIES = ("claude", "verification")
|
|
30
|
+
HEX_TOKEN_PATTERN = r"(?<![0-9a-fx])([0-9a-f]{6,})(?![0-9a-f])"
|
|
31
|
+
BASE64_TOKEN_PATTERN = r"[A-Za-z0-9+/]{8,}={0,2}"
|
|
32
|
+
CHARACTER_CODE_SEQUENCE_PATTERN = r"\d{1,3}(?:\s*,\s*\d{1,3})+"
|
|
33
|
+
CHR_CALL_CHAIN_PATTERN = r"chr\(\s*\d{1,3}\s*\)(?:\s*\+\s*chr\(\s*\d{1,3}\s*\))+"
|
|
34
|
+
CHR_CALL_CODE_PATTERN = r"chr\(\s*(\d{1,3})\s*\)"
|
|
35
|
+
HEX_DIGITS_PER_BYTE = 2
|
|
36
|
+
FILE_WRITE_PRIMITIVE_PATTERN = (
|
|
37
|
+
r"\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
|
|
38
|
+
r"|Out-File|Set-Content|Add-Content|\btee\b|>"
|
|
39
|
+
)
|
|
40
|
+
NON_REDIRECT_FILE_WRITE_PRIMITIVE_PATTERN = (
|
|
41
|
+
r"\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
|
|
42
|
+
r"|Out-File|Set-Content|Add-Content|\btee\b"
|
|
43
|
+
)
|
|
44
|
+
WRITE_CALL_REGION_PATTERN = (
|
|
45
|
+
r"(?:\bopen\s*\(|\.write_text\s*\(|\.write_bytes\s*\("
|
|
46
|
+
r"|Out-File|Set-Content|Add-Content|\btee\b)[^;&|\n]*"
|
|
47
|
+
)
|
|
48
|
+
VERDICT_KEY_ALL_PASS = "all_pass"
|
|
49
|
+
VERDICT_KEY_MANIFEST_SHA256 = "manifest_sha256"
|
|
50
|
+
DOCS_ONLY_EXTENSIONS = frozenset(
|
|
51
|
+
{".md", ".txt", ".rst", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico"}
|
|
52
|
+
)
|
|
53
|
+
PYTHON_EXTENSION = ".py"
|
|
54
|
+
TEST_FILE_PREFIX = "test_"
|
|
55
|
+
TEST_FILE_SUFFIX = "_test.py"
|
|
56
|
+
CONFTEST_FILE_NAME = "conftest.py"
|
|
57
|
+
MINIMUM_STATUS_FIELD_COUNT = 2
|
|
58
|
+
ALL_FALLBACK_BASE_REFERENCES = ("origin/main", "origin/master")
|
|
59
|
+
ALL_TOOLING_STATE_PREFIXES = (
|
|
60
|
+
".claude/verification/",
|
|
61
|
+
".claude/worktrees/",
|
|
62
|
+
".claude/daemon/",
|
|
63
|
+
".claude/teams/",
|
|
64
|
+
".claude/sessions/",
|
|
65
|
+
".cursor/worktrees/",
|
|
66
|
+
)
|
|
67
|
+
GATED_GIT_SUBCOMMANDS = frozenset({"commit", "push"})
|
|
68
|
+
ALL_GIT_BINARY_NAMES = frozenset({"git", "git.exe"})
|
|
69
|
+
VALUE_TAKING_GIT_OPTIONS = frozenset({"-C", "-c", "--git-dir", "--work-tree", "--namespace"})
|
|
70
|
+
REPO_DIRECTORY_OPTION = "-C"
|
|
71
|
+
WORK_TREE_OPTION = "--work-tree"
|
|
72
|
+
DIRECTORY_CHANGE_VERBS = frozenset({"cd", "pushd", "set-location", "sl"})
|
|
73
|
+
DIRECTORY_CHANGE_PATH_OPTIONS = frozenset({"-path", "-literalpath"})
|
|
74
|
+
DIRECTORY_CHANGE_OPTION_TERMINATOR = "--"
|
|
75
|
+
DIRECTORY_CHANGE_PATTERN_PREFIX = r"(?:^|(?<=[\s;&|(]))(?:"
|
|
76
|
+
DIRECTORY_CHANGE_PATTERN_SUFFIX = r")(?=\s|$)"
|
|
77
|
+
DIRECTORY_CHANGE_OPTION_PREFIX_PATTERN = r"(?:[ \t]+(?:%s)(?=\s|$))*"
|
|
78
|
+
DIRECTORY_CHANGE_TARGET_PATTERN = r"[ \t]+['\"]?\S*"
|
|
79
|
+
CLAUDE_HOME_TARGET_BOUNDARY_PATTERN = r"[\\/]?['\"]?(?=[\s;&|]|$)"
|
|
80
|
+
VERDICT_DIRECTORY_TARGET_BOUNDARY_PATTERN = r"[\\/]?['\"]?(?=[\s;&|]|$)"
|
|
81
|
+
COMMAND_AFTER_DIRECTORY_CHANGE_PATTERN = r"[;&|\n][\s]*\S"
|
|
82
|
+
OPTION_WITH_VALUE_STEP = 2
|
|
83
|
+
ALL_GATED_TOOL_NAMES = ("Bash", "PowerShell")
|
|
84
|
+
HASH_PREVIEW_LENGTH = 16
|
|
85
|
+
MINTING_AGENT_TYPE = "code-verifier"
|
|
86
|
+
SPAWN_LOOKUP_ATTEMPT_COUNT = 3
|
|
87
|
+
SPAWN_LOOKUP_RETRY_DELAY_SECONDS = 0.1
|
|
88
|
+
VERDICT_DIRECTORY_GUARD_MESSAGE = (
|
|
89
|
+
"BLOCKED: [VERDICT_DIRECTORY_GUARD] Shell access to the verification "
|
|
90
|
+
"verdict directory (~/.claude/verification/) is denied. Only the "
|
|
91
|
+
"verifier_verdict_minter.py SubagentStop hook writes verdict files; a "
|
|
92
|
+
"shell write here would forge a passing verdict and defeat the "
|
|
93
|
+
"verified-commit gate. Spawn the code-verifier agent to earn a verdict "
|
|
94
|
+
"instead of writing one."
|
|
95
|
+
)
|
|
96
|
+
CORRECTIVE_MESSAGE = (
|
|
97
|
+
"BLOCKED: [VERIFIED_COMMIT_GATE] This branch surface has no passing "
|
|
98
|
+
"verification verdict. Spawn the code-verifier agent (Agent tool, "
|
|
99
|
+
"subagent_type 'code-verifier') with the task texts, the diff scope, "
|
|
100
|
+
"and recorded baselines; when it finishes with a clean verdict the "
|
|
101
|
+
"SubagentStop hook mints the verdict and this command will pass. Any "
|
|
102
|
+
"file change after verification invalidates the verdict, so verify "
|
|
103
|
+
"last. Exempt automatically: docs/image files, pytest test files, and "
|
|
104
|
+
"Python files whose docstring- and comment-stripped AST is unchanged "
|
|
105
|
+
"(comment-only edits in non-Python files are not exempt)."
|
|
106
|
+
)
|