claude-dev-env 1.59.0 → 1.61.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 (81) hide show
  1. package/CLAUDE.md +4 -0
  2. package/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md +1 -1
  3. package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -0
  4. package/audit-rubrics/category_rubrics/category-f-silent-failures.md +1 -1
  5. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
  6. package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
  7. package/audit-rubrics/prompts/category-e-dead-code.md +17 -4
  8. package/audit-rubrics/prompts/category-f-silent-failures.md +1 -0
  9. package/docs/CODE_RULES.md +2 -2
  10. package/hooks/blocking/code_rules_annotations_length.py +189 -10
  11. package/hooks/blocking/code_rules_dead_module_constant.py +321 -0
  12. package/hooks/blocking/code_rules_duplicate_body.py +152 -0
  13. package/hooks/blocking/code_rules_enforcer.py +38 -15
  14. package/hooks/blocking/code_rules_orphan_css_class.py +196 -0
  15. package/hooks/blocking/code_rules_typeddict_stub.py +172 -0
  16. package/hooks/blocking/config/__init__.py +5 -0
  17. package/hooks/blocking/config/verified_commit_constants.py +118 -0
  18. package/hooks/blocking/destructive_command_blocker.py +483 -61
  19. package/hooks/blocking/test_code_rules_enforcer_annotations.py +240 -0
  20. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
  21. package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +146 -0
  22. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +188 -0
  23. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +82 -0
  24. package/hooks/blocking/test_code_rules_enforcer_orphan_css_class.py +196 -0
  25. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +415 -0
  26. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +156 -0
  27. package/hooks/blocking/test_destructive_command_blocker.py +213 -0
  28. package/hooks/blocking/test_verdict_directory_write_blocker.py +720 -0
  29. package/hooks/blocking/test_verification_verdict_store.py +490 -0
  30. package/hooks/blocking/test_verified_commit_gate.py +495 -0
  31. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -0
  32. package/hooks/blocking/test_verifier_verdict_minter.py +193 -0
  33. package/hooks/blocking/verdict_directory_write_blocker.py +667 -0
  34. package/hooks/blocking/verification_verdict_store.py +686 -0
  35. package/hooks/blocking/verified_commit_gate.py +535 -0
  36. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +152 -0
  37. package/hooks/blocking/verifier_verdict_minter.py +221 -0
  38. package/hooks/diagnostic/test_hook_log_extractor.py +3 -3
  39. package/hooks/hooks.json +43 -1
  40. package/hooks/hooks_constants/blocking_check_limits.py +1 -0
  41. package/hooks/hooks_constants/code_rules_enforcer_constants.py +6 -0
  42. package/hooks/hooks_constants/dead_module_constant_constants.py +20 -0
  43. package/hooks/hooks_constants/destructive_command_segment_constants.py +15 -0
  44. package/hooks/hooks_constants/duplicate_function_body_constants.py +22 -5
  45. package/hooks/hooks_constants/orphan_css_class_constants.py +40 -0
  46. package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +1 -1
  47. package/hooks/validation/mypy_validator.py +59 -7
  48. package/hooks/validation/test_mypy_validator.py +94 -0
  49. package/package.json +1 -1
  50. package/rules/file-global-constants.md +7 -1
  51. package/rules/no-cross-skill-duplicate-helpers.md +29 -0
  52. package/rules/orphan-css-class.md +23 -0
  53. package/skills/_shared/pr-loop/scripts/preflight_worktree.py +392 -0
  54. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +70 -0
  55. package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +263 -0
  56. package/skills/autoconverge/SKILL.md +54 -17
  57. package/skills/autoconverge/reference/closing-report.md +59 -17
  58. package/skills/autoconverge/workflow/aggregate_runs.py +371 -0
  59. package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +192 -76
  60. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +76 -0
  61. package/skills/autoconverge/workflow/converge.contract.test.mjs +395 -206
  62. package/skills/autoconverge/workflow/converge.mjs +520 -57
  63. package/skills/autoconverge/workflow/convergence_summary.py +110 -0
  64. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +2 -0
  65. package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +7 -0
  66. package/skills/autoconverge/workflow/render_report.py +488 -397
  67. package/skills/autoconverge/workflow/test_aggregate_runs.py +134 -0
  68. package/skills/autoconverge/workflow/test_convergence_summary.py +132 -0
  69. package/skills/autoconverge/workflow/test_render_report.py +518 -259
  70. package/skills/pr-converge/reference/per-tick.md +28 -8
  71. package/skills/rebase/SKILL.md +2 -4
  72. package/system-prompts/software-engineer.xml +2 -6
  73. package/hooks/blocking/content_search_to_zoekt_redirector.py +0 -59
  74. package/hooks/blocking/content_search_zoekt_bash_block_reason.py +0 -25
  75. package/hooks/blocking/content_search_zoekt_block_payload.py +0 -21
  76. package/hooks/blocking/content_search_zoekt_indexed_paths.py +0 -24
  77. package/hooks/blocking/content_search_zoekt_indexed_roots_config.py +0 -131
  78. package/hooks/blocking/content_search_zoekt_redirect_guidance.py +0 -52
  79. package/hooks/blocking/test_content_search_to_zoekt_redirector_integration.py +0 -61
  80. package/hooks/blocking/test_content_search_to_zoekt_redirector_unit.py +0 -92
  81. package/hooks/blocking/test_content_search_zoekt_indexed_roots_config.py +0 -102
@@ -0,0 +1,720 @@
1
+ """Tests for the verdict-directory shell guard.
2
+
3
+ The verified-commit gate trusts that only the minter hook writes verdict
4
+ files. The Write/Edit/MultiEdit deny rules in settings.json stop the file
5
+ tools, but a shell command (``python -c``, a redirect, an Out-File) reaches
6
+ the same directory unless a Bash/PowerShell guard blocks it. These tests
7
+ exercise that guard: every shell spelling that targets the verdict directory
8
+ is denied, and commands that touch unrelated paths pass.
9
+ """
10
+
11
+ import importlib.util
12
+ import json
13
+ import pathlib
14
+ import sys
15
+
16
+ _HOOK_DIR = pathlib.Path(__file__).parent
17
+ if str(_HOOK_DIR) not in sys.path:
18
+ sys.path.insert(0, str(_HOOK_DIR))
19
+
20
+ guard_spec = importlib.util.spec_from_file_location(
21
+ "verdict_directory_write_blocker",
22
+ _HOOK_DIR / "verdict_directory_write_blocker.py",
23
+ )
24
+ assert guard_spec is not None
25
+ assert guard_spec.loader is not None
26
+ guard_module = importlib.util.module_from_spec(guard_spec)
27
+ guard_spec.loader.exec_module(guard_module)
28
+ references_verdict_directory = guard_module.references_verdict_directory
29
+ decision_for_payload = guard_module.decision_for_payload
30
+
31
+ constants_spec = importlib.util.spec_from_file_location(
32
+ "verified_commit_constants",
33
+ _HOOK_DIR / "config" / "verified_commit_constants.py",
34
+ )
35
+ assert constants_spec is not None
36
+ assert constants_spec.loader is not None
37
+ constants_module = importlib.util.module_from_spec(constants_spec)
38
+ constants_spec.loader.exec_module(constants_module)
39
+ ROOT_KEY_HEX_LENGTH = constants_module.ROOT_KEY_HEX_LENGTH
40
+
41
+
42
+ def test_python_write_text_into_verdict_directory_is_flagged() -> None:
43
+ command_text = (
44
+ 'python -c "import pathlib; '
45
+ "pathlib.Path.home().joinpath('.claude','verification','x.json')"
46
+ ".write_text('{}')\""
47
+ )
48
+ assert references_verdict_directory(command_text) is True
49
+
50
+
51
+ def test_home_var_redirect_into_verdict_directory_is_flagged() -> None:
52
+ assert (
53
+ references_verdict_directory(
54
+ "echo forged > $HOME/.claude/verification/abc.json"
55
+ )
56
+ is True
57
+ )
58
+
59
+
60
+ def test_tilde_redirect_into_verdict_directory_is_flagged() -> None:
61
+ assert (
62
+ references_verdict_directory("echo forged > ~/.claude/verification/abc.json")
63
+ is True
64
+ )
65
+
66
+
67
+ def test_powershell_out_file_into_verdict_directory_is_flagged() -> None:
68
+ assert (
69
+ references_verdict_directory(
70
+ "'{}' | Out-File $HOME/.claude/verification/abc.json"
71
+ )
72
+ is True
73
+ )
74
+
75
+
76
+ def test_backslash_verdict_path_is_flagged() -> None:
77
+ assert (
78
+ references_verdict_directory(
79
+ "echo forged > C:\\Users\\jon\\.claude\\verification\\abc.json"
80
+ )
81
+ is True
82
+ )
83
+
84
+
85
+ def test_unrelated_claude_path_is_not_flagged() -> None:
86
+ assert (
87
+ references_verdict_directory(
88
+ "python $HOME/.claude/hooks/blocking/verified_commit_gate.py"
89
+ )
90
+ is False
91
+ )
92
+
93
+
94
+ def test_plain_git_commit_is_not_flagged() -> None:
95
+ assert references_verdict_directory("git commit -m x") is False
96
+
97
+
98
+ def test_cd_then_tilde_relative_write_is_flagged() -> None:
99
+ assert (
100
+ references_verdict_directory(
101
+ "cd ~/.claude && echo x > verification/a.json"
102
+ )
103
+ is True
104
+ )
105
+
106
+
107
+ def test_cd_then_absolute_relative_write_is_flagged() -> None:
108
+ assert (
109
+ references_verdict_directory(
110
+ "cd /home/u/.claude && echo x > verification/a.json"
111
+ )
112
+ is True
113
+ )
114
+
115
+
116
+ def test_cd_with_trailing_slash_then_relative_write_is_flagged() -> None:
117
+ assert (
118
+ references_verdict_directory(
119
+ "cd ~/.claude/ ; echo x > verification/x.json"
120
+ )
121
+ is True
122
+ )
123
+
124
+
125
+ def test_pushd_then_relative_write_is_flagged() -> None:
126
+ assert (
127
+ references_verdict_directory(
128
+ "pushd $HOME/.claude; echo x > verification/abc.json"
129
+ )
130
+ is True
131
+ )
132
+
133
+
134
+ def test_set_location_then_relative_write_is_flagged() -> None:
135
+ assert (
136
+ references_verdict_directory(
137
+ "Set-Location ~/.claude; 'x' | Out-File verification/abc.json"
138
+ )
139
+ is True
140
+ )
141
+
142
+
143
+ def test_relative_verdict_filename_shape_is_flagged() -> None:
144
+ assert (
145
+ references_verdict_directory(
146
+ "echo forged > verification/0123456789abcdef.json"
147
+ )
148
+ is True
149
+ )
150
+
151
+
152
+ def test_verdict_filename_of_root_key_length_is_flagged() -> None:
153
+ root_key_length_name = "a" * ROOT_KEY_HEX_LENGTH
154
+ assert (
155
+ references_verdict_directory(
156
+ f"echo forged > verification/{root_key_length_name}.json"
157
+ )
158
+ is True
159
+ )
160
+
161
+
162
+ def test_verdict_filename_one_hex_short_of_root_key_length_is_not_flagged() -> None:
163
+ too_short_name = "a" * (ROOT_KEY_HEX_LENGTH - 1)
164
+ assert (
165
+ references_verdict_directory(
166
+ f"echo forged > verification/{too_short_name}.json"
167
+ )
168
+ is False
169
+ )
170
+
171
+
172
+ def test_verdict_file_pattern_quantifier_tracks_root_key_length() -> None:
173
+ expected_quantifier = "{" + str(ROOT_KEY_HEX_LENGTH) + "}"
174
+ assert expected_quantifier in constants_module.VERDICT_FILE_RELATIVE_REFERENCE_PATTERN
175
+
176
+
177
+ def test_cd_into_verdict_directory_then_bare_write_is_flagged() -> None:
178
+ assert (
179
+ references_verdict_directory(
180
+ "cd ~/.claude/verification && echo x > 0123456789abcdef.json"
181
+ )
182
+ is True
183
+ )
184
+
185
+
186
+ def test_pushd_into_verdict_directory_then_bare_write_is_flagged() -> None:
187
+ assert (
188
+ references_verdict_directory(
189
+ "pushd $HOME/.claude/verification; echo x > 0123456789abcdef.json"
190
+ )
191
+ is True
192
+ )
193
+
194
+
195
+ def test_set_location_into_verdict_directory_then_bare_write_is_flagged() -> None:
196
+ assert (
197
+ references_verdict_directory(
198
+ "Set-Location ~/.claude/verification; 'x' | Out-File 0123456789abcdef.json"
199
+ )
200
+ is True
201
+ )
202
+
203
+
204
+ def test_cd_into_verdict_directory_with_trailing_slash_is_flagged() -> None:
205
+ assert (
206
+ references_verdict_directory(
207
+ "cd ~/.claude/verification/ && echo x > 0123456789abcdef.json"
208
+ )
209
+ is True
210
+ )
211
+
212
+
213
+ def test_set_location_path_option_into_verdict_directory_then_bare_write_is_flagged() -> None:
214
+ assert (
215
+ references_verdict_directory(
216
+ "Set-Location -Path ~/.claude/verification; "
217
+ "echo forged > 0123456789abcdef.json"
218
+ )
219
+ is True
220
+ )
221
+
222
+
223
+ def test_set_location_literal_path_option_into_verdict_directory_then_bare_write_is_flagged() -> None:
224
+ assert (
225
+ references_verdict_directory(
226
+ "Set-Location -LiteralPath ~/.claude/verification; "
227
+ "echo forged > 0123456789abcdef.json"
228
+ )
229
+ is True
230
+ )
231
+
232
+
233
+ def test_set_location_path_option_into_claude_home_then_relative_write_is_flagged() -> None:
234
+ assert (
235
+ references_verdict_directory(
236
+ "Set-Location -Path ~/.claude; 'x' | Out-File verification/abc.json"
237
+ )
238
+ is True
239
+ )
240
+
241
+
242
+ def test_cd_double_dash_terminator_into_verdict_directory_then_bare_write_is_flagged() -> None:
243
+ assert (
244
+ references_verdict_directory(
245
+ "cd -- ~/.claude/verification && echo forged > 0123456789abcdef.json"
246
+ )
247
+ is True
248
+ )
249
+
250
+
251
+ def test_set_location_path_option_into_unrelated_directory_then_write_is_not_flagged() -> None:
252
+ assert (
253
+ references_verdict_directory(
254
+ "Set-Location -Path ~/.claude/hooks; echo x > note.txt"
255
+ )
256
+ is False
257
+ )
258
+
259
+
260
+ def test_cd_into_verdict_directory_then_cp_is_flagged() -> None:
261
+ assert (
262
+ references_verdict_directory(
263
+ "cd ~/.claude/verification && cp /tmp/forged.json 0123456789abcdef.json"
264
+ )
265
+ is True
266
+ )
267
+
268
+
269
+ def test_cd_into_verdict_directory_then_mv_is_flagged() -> None:
270
+ assert (
271
+ references_verdict_directory(
272
+ "cd ~/.claude/verification && mv /tmp/forged.json 0123456789abcdef.json"
273
+ )
274
+ is True
275
+ )
276
+
277
+
278
+ def test_cd_into_verdict_directory_then_tee_is_flagged() -> None:
279
+ assert (
280
+ references_verdict_directory(
281
+ "cd ~/.claude/verification && echo '{}' | tee 0123456789abcdef.json"
282
+ )
283
+ is True
284
+ )
285
+
286
+
287
+ def test_cd_into_verdict_directory_then_python_write_text_is_flagged() -> None:
288
+ assert (
289
+ references_verdict_directory(
290
+ "cd ~/.claude/verification && "
291
+ "python -c \"import pathlib; "
292
+ "pathlib.Path('0123456789abcdef.json').write_text('{}')\""
293
+ )
294
+ is True
295
+ )
296
+
297
+
298
+ def test_cd_into_verdict_directory_then_install_is_flagged() -> None:
299
+ assert (
300
+ references_verdict_directory(
301
+ "cd ~/.claude/verification && install /tmp/forged.json 0123456789abcdef.json"
302
+ )
303
+ is True
304
+ )
305
+
306
+
307
+ def test_cd_into_verdict_directory_ampersand_abutting_tee_is_flagged() -> None:
308
+ assert (
309
+ references_verdict_directory(
310
+ "cd ~/.claude/verification&& echo {} | tee 8a482d8ecd29493f.json"
311
+ )
312
+ is True
313
+ )
314
+
315
+
316
+ def test_cd_into_verdict_directory_pipe_abutting_cp_is_flagged() -> None:
317
+ assert (
318
+ references_verdict_directory(
319
+ "cd ~/.claude/verification|cp /tmp/f.json 8a482d8ecd29493f.json"
320
+ )
321
+ is True
322
+ )
323
+
324
+
325
+ def test_cd_into_claude_home_ampersand_abutting_relative_write_is_flagged() -> None:
326
+ assert (
327
+ references_verdict_directory("cd ~/.claude&& echo x > verification/a.json")
328
+ is True
329
+ )
330
+
331
+
332
+ def test_cd_into_claude_home_pipe_abutting_relative_write_is_flagged() -> None:
333
+ assert (
334
+ references_verdict_directory("cd ~/.claude|tee verification/a.json")
335
+ is True
336
+ )
337
+
338
+
339
+ def test_add_content_obfuscated_path_write_is_flagged() -> None:
340
+ probe_command = (
341
+ "$p = bytes.fromhex('2e636c61756465').decode() ; Add-Content $p '{}'"
342
+ )
343
+ assert references_verdict_directory(probe_command) is True
344
+
345
+
346
+ def test_char_cast_obfuscated_path_write_is_flagged() -> None:
347
+ probe_command = (
348
+ "$p = [char]46 + [char[]]@(99,108,97,117,100,101) ; Set-Content $p '{}'"
349
+ )
350
+ assert references_verdict_directory(probe_command) is True
351
+
352
+
353
+ def test_chr_built_path_write_is_flagged() -> None:
354
+ probe_command = (
355
+ "python -c \"import os; "
356
+ "open(os.path.join(os.path.expanduser(chr(126)),chr(46)+'claude',"
357
+ "'verification','a.json'),'w')\""
358
+ )
359
+ assert references_verdict_directory(probe_command) is True
360
+
361
+
362
+ def test_pure_chr_chain_verdict_path_forge_is_flagged() -> None:
363
+ chr_chain = "+".join(
364
+ f"chr({each_byte})"
365
+ for each_byte in b"/.claude/verification/a"
366
+ )
367
+ probe_command = f"python -c \"open({chr_chain},chr(119)).write(chr(48))\""
368
+ assert references_verdict_directory(probe_command) is True
369
+
370
+
371
+ def test_pure_chr_chain_verification_segment_forge_is_flagged() -> None:
372
+ chr_chain = "+".join(f"chr({each_byte})" for each_byte in b"verification")
373
+ probe_command = f"python -c \"open({chr_chain},'w')\""
374
+ assert references_verdict_directory(probe_command) is True
375
+
376
+
377
+ def test_bytes_fromhex_path_write_is_flagged() -> None:
378
+ probe_command = (
379
+ "python -c \"import os; "
380
+ "open(bytes.fromhex('2e636c61756465').decode(),'w')\""
381
+ )
382
+ assert references_verdict_directory(probe_command) is True
383
+
384
+
385
+ def test_base64_decoded_path_write_is_flagged() -> None:
386
+ probe_command = (
387
+ "python -c \"import base64; "
388
+ "open(base64.b64decode('LmNsYXVkZQ==').decode(),'w')\""
389
+ )
390
+ assert references_verdict_directory(probe_command) is True
391
+
392
+
393
+ def test_int_list_bytes_path_write_is_flagged() -> None:
394
+ claude_segment_codes = ",".join(str(each_byte) for each_byte in b".claude")
395
+ probe_command = (
396
+ f"python -c \"open(bytes([{claude_segment_codes}]).decode(),'w')\""
397
+ )
398
+ assert references_verdict_directory(probe_command) is True
399
+
400
+
401
+ def test_int_list_bytearray_path_write_is_flagged() -> None:
402
+ verification_segment_codes = ",".join(
403
+ str(each_byte) for each_byte in b"verification"
404
+ )
405
+ probe_command = (
406
+ f"python -c \"open(bytearray([{verification_segment_codes}]).decode(),'w')\""
407
+ )
408
+ assert references_verdict_directory(probe_command) is True
409
+
410
+
411
+ def test_int_list_full_verdict_path_forge_is_flagged() -> None:
412
+ verdict_path_codes = ",".join(
413
+ str(each_byte)
414
+ for each_byte in b"/home/u/.claude/verification/0123456789abcdef.json"
415
+ )
416
+ probe_command = (
417
+ f'python3 -c "f=open(bytes([{verdict_path_codes}]).decode(),'
418
+ "'w');f.write('{}')\""
419
+ )
420
+ assert references_verdict_directory(probe_command) is True
421
+
422
+
423
+ def test_int_list_bytes_to_unrelated_file_is_not_flagged() -> None:
424
+ unrelated_segment_codes = ",".join(str(each_byte) for each_byte in b"ab")
425
+ probe_command = (
426
+ f"python -c \"open(bytes([{unrelated_segment_codes}]).decode(),'w')\""
427
+ )
428
+ assert references_verdict_directory(probe_command) is False
429
+
430
+
431
+ def test_chr_without_write_primitive_is_not_flagged() -> None:
432
+ assert references_verdict_directory("python -c \"print(chr(126))\"") is False
433
+
434
+
435
+ def test_chr_write_to_unrelated_path_is_not_flagged() -> None:
436
+ assert (
437
+ references_verdict_directory('python -c "print(chr(65))" > /tmp/out.txt')
438
+ is False
439
+ )
440
+
441
+
442
+ def test_base64_decode_to_unrelated_file_is_not_flagged() -> None:
443
+ assert (
444
+ references_verdict_directory(
445
+ 'python -c "import base64,sys; '
446
+ "open('decoded.bin','wb').write(base64.b64decode(sys.argv[1]))\""
447
+ )
448
+ is False
449
+ )
450
+
451
+
452
+ def test_set_content_char_to_unrelated_file_is_not_flagged() -> None:
453
+ assert references_verdict_directory("Set-Content out.txt ([char]65)") is False
454
+
455
+
456
+ def test_bytes_fromhex_to_unrelated_file_is_not_flagged() -> None:
457
+ assert (
458
+ references_verdict_directory(
459
+ "python -c \"open(bytes.fromhex('6162').decode(),'w')\""
460
+ )
461
+ is False
462
+ )
463
+
464
+
465
+ def test_obfuscated_write_to_unrelated_path_with_incidental_verification_word_is_not_flagged() -> None:
466
+ assert (
467
+ references_verdict_directory(
468
+ "python -c \"open(chr(47)+chr(116)); print('verification done')\""
469
+ )
470
+ is False
471
+ )
472
+
473
+
474
+ def test_obfuscated_write_with_decoded_segment_after_separator_stays_flagged() -> None:
475
+ assert (
476
+ references_verdict_directory(
477
+ "python -c \"open(bytes.fromhex('2e636c61756465').decode(),'w'); "
478
+ "print('saved')\""
479
+ )
480
+ is True
481
+ )
482
+
483
+
484
+ def test_obfuscated_write_with_incidental_claude_word_after_separator_is_not_flagged() -> None:
485
+ assert (
486
+ references_verdict_directory(
487
+ "python -c \"open(chr(47)+chr(116)); print('claude run complete')\""
488
+ )
489
+ is False
490
+ )
491
+
492
+
493
+ def test_base64_encoded_verification_segment_obfuscated_write_is_flagged() -> None:
494
+ probe_command = (
495
+ "python -c \"import base64; "
496
+ "open(base64.b64decode('dmVyaWZpY2F0aW9u').decode(),'w')\""
497
+ )
498
+ assert references_verdict_directory(probe_command) is True
499
+
500
+
501
+ def test_hex_encoded_verification_segment_obfuscated_write_is_flagged() -> None:
502
+ probe_command = (
503
+ "python -c \"open(bytes.fromhex('766572696669636174696f6e').decode(),'w')\""
504
+ )
505
+ assert references_verdict_directory(probe_command) is True
506
+
507
+
508
+ def test_split_cd_into_verdict_directory_then_bare_write_is_flagged() -> None:
509
+ assert (
510
+ references_verdict_directory(
511
+ "cd ~/.claude && cd verification && echo x > 0123456789abcdef.json"
512
+ )
513
+ is True
514
+ )
515
+
516
+
517
+ def test_split_cd_into_unrelated_subdirectory_then_write_is_not_flagged() -> None:
518
+ assert (
519
+ references_verdict_directory(
520
+ "cd ~/.claude && cd hooks && echo x > note.txt"
521
+ )
522
+ is False
523
+ )
524
+
525
+
526
+ def test_split_cd_into_verdict_directory_then_copy_is_flagged() -> None:
527
+ assert (
528
+ references_verdict_directory(
529
+ "cd ~/.claude && cd verification && cp /tmp/f.json out.json"
530
+ )
531
+ is True
532
+ )
533
+
534
+
535
+ def test_split_cd_into_verdict_directory_then_move_is_flagged() -> None:
536
+ assert (
537
+ references_verdict_directory(
538
+ "cd ~/.claude && cd verification && mv /tmp/f.json out.json"
539
+ )
540
+ is True
541
+ )
542
+
543
+
544
+ def test_split_cd_into_unrelated_subdirectory_then_copy_is_not_flagged() -> None:
545
+ assert (
546
+ references_verdict_directory("cd ~/.claude && cd hooks && cp a b") is False
547
+ )
548
+
549
+
550
+ def test_concatenated_string_literal_verdict_path_write_is_flagged() -> None:
551
+ assert (
552
+ references_verdict_directory(
553
+ "python -c \"open(str(pathlib.Path.home())+'/.claude'+'/verification'"
554
+ "+'/a.json','w')\""
555
+ )
556
+ is True
557
+ )
558
+
559
+
560
+ def test_concatenated_absolute_verdict_path_write_is_flagged() -> None:
561
+ assert (
562
+ references_verdict_directory(
563
+ "python -c \"open('/home/u/.claude'+'/verification/a.json','w')\""
564
+ )
565
+ is True
566
+ )
567
+
568
+
569
+ def test_shell_variable_home_then_verdict_path_write_is_flagged() -> None:
570
+ assert (
571
+ references_verdict_directory(
572
+ "p=~/.claude; echo x > $p/verification/a.json"
573
+ )
574
+ is True
575
+ )
576
+
577
+
578
+ def test_shell_variable_verdict_directory_name_write_is_flagged() -> None:
579
+ assert (
580
+ references_verdict_directory(
581
+ "VDIR=verification; cd ~/.claude && echo x > $VDIR/a.json"
582
+ )
583
+ is True
584
+ )
585
+
586
+
587
+ def test_write_without_obfuscation_primitive_is_not_flagged() -> None:
588
+ assert (
589
+ references_verdict_directory("echo hello > /tmp/notes.txt")
590
+ is False
591
+ )
592
+
593
+
594
+ def test_unrelated_claude_path_with_no_obfuscation_stays_unflagged() -> None:
595
+ assert (
596
+ references_verdict_directory(
597
+ "python $HOME/.claude/hooks/blocking/verified_commit_gate.py"
598
+ )
599
+ is False
600
+ )
601
+
602
+
603
+ def test_cd_outside_claude_then_relative_write_is_not_flagged() -> None:
604
+ assert (
605
+ references_verdict_directory("cd /tmp/work && echo x > verification/a.json")
606
+ is False
607
+ )
608
+
609
+
610
+ def test_sibling_verification_directory_is_not_flagged() -> None:
611
+ assert (
612
+ references_verdict_directory("cat ~/.claude/verification-docs/readme.md")
613
+ is False
614
+ )
615
+
616
+
617
+ def test_commit_message_naming_verdict_path_is_not_flagged() -> None:
618
+ assert (
619
+ references_verdict_directory('git commit -m "fix .claude/verification gate"')
620
+ is False
621
+ )
622
+
623
+
624
+ def test_verdict_write_with_path_separator_stays_flagged() -> None:
625
+ assert (
626
+ references_verdict_directory("echo x > ~/.claude/verification/a.json")
627
+ is True
628
+ )
629
+
630
+
631
+ def test_benign_redirect_mentioning_both_words_in_message_is_not_flagged() -> None:
632
+ assert (
633
+ references_verdict_directory(
634
+ 'echo "note: updated .claude docs about the verification flow"'
635
+ " > /tmp/notes.txt"
636
+ )
637
+ is False
638
+ )
639
+
640
+
641
+ def test_benign_single_quoted_message_mentioning_both_words_is_not_flagged() -> None:
642
+ assert (
643
+ references_verdict_directory(
644
+ "echo 'see .claude and verification notes' > /tmp/out.txt"
645
+ )
646
+ is False
647
+ )
648
+
649
+
650
+ def test_commit_message_naming_verdict_path_with_redirect_is_not_flagged() -> None:
651
+ assert (
652
+ references_verdict_directory(
653
+ 'git commit -m "fix .claude/verification gate" > /tmp/commit.log'
654
+ )
655
+ is False
656
+ )
657
+
658
+
659
+ def test_decision_for_bash_verdict_write_denies() -> None:
660
+ payload = {
661
+ "tool_name": "Bash",
662
+ "tool_input": {"command": "echo x > ~/.claude/verification/a.json"},
663
+ }
664
+ decision = decision_for_payload(payload)
665
+ assert decision is not None
666
+ assert decision["hookSpecificOutput"]["permissionDecision"] == "deny"
667
+
668
+
669
+ def test_decision_for_powershell_verdict_write_denies() -> None:
670
+ payload = {
671
+ "tool_name": "PowerShell",
672
+ "tool_input": {
673
+ "command": "'x' | Set-Content $HOME/.claude/verification/a.json"
674
+ },
675
+ }
676
+ decision = decision_for_payload(payload)
677
+ assert decision is not None
678
+ assert decision["hookSpecificOutput"]["permissionDecision"] == "deny"
679
+
680
+
681
+ def test_decision_for_unrelated_bash_command_is_none() -> None:
682
+ payload = {"tool_name": "Bash", "tool_input": {"command": "git status"}}
683
+ assert decision_for_payload(payload) is None
684
+
685
+
686
+ def test_decision_for_non_shell_tool_is_none() -> None:
687
+ payload = {
688
+ "tool_name": "Read",
689
+ "tool_input": {"command": "echo x > ~/.claude/verification/a.json"},
690
+ }
691
+ assert decision_for_payload(payload) is None
692
+
693
+
694
+ def _hooks_manifest_path() -> pathlib.Path:
695
+ return _HOOK_DIR.parent / "hooks.json"
696
+
697
+
698
+ def _pretooluse_commands_for_matcher(matcher_substring: str) -> list[str]:
699
+ manifest_record = json.loads(_hooks_manifest_path().read_text(encoding="utf-8"))
700
+ matching_commands: list[str] = []
701
+ for each_group in manifest_record["hooks"]["PreToolUse"]:
702
+ if matcher_substring not in each_group.get("matcher", ""):
703
+ continue
704
+ for each_hook in each_group.get("hooks", []):
705
+ matching_commands.append(each_hook.get("command", ""))
706
+ return matching_commands
707
+
708
+
709
+ def test_guard_is_registered_on_bash() -> None:
710
+ assert any(
711
+ "verdict_directory_write_blocker.py" in each_command
712
+ for each_command in _pretooluse_commands_for_matcher("Bash")
713
+ )
714
+
715
+
716
+ def test_guard_is_registered_on_powershell() -> None:
717
+ assert any(
718
+ "verdict_directory_write_blocker.py" in each_command
719
+ for each_command in _pretooluse_commands_for_matcher("PowerShell")
720
+ )