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.
Files changed (193) hide show
  1. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
  2. package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
  3. package/agents/CLAUDE.md +1 -2
  4. package/agents/pr-description-writer.md +11 -81
  5. package/bin/install.mjs +48 -9
  6. package/bin/install.prune.test.mjs +26 -0
  7. package/bin/install.settings-defaults.test.mjs +60 -33
  8. package/bin/install.test.mjs +2 -4
  9. package/commands/sr-loop.md +9 -5
  10. package/docs/CLAUDE.md +0 -1
  11. package/docs/references/CLAUDE.md +0 -1
  12. package/hooks/CLAUDE.md +1 -1
  13. package/hooks/blocking/CLAUDE.md +2 -27
  14. package/hooks/blocking/config/CLAUDE.md +1 -13
  15. package/hooks/blocking/conventional_pr_title_gate.py +1 -2
  16. package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
  17. package/hooks/blocking/stop_dispatcher.py +4 -4
  18. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
  19. package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
  20. package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
  21. package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
  22. package/hooks/diagnostic/CLAUDE.md +3 -3
  23. package/hooks/diagnostic/hook_log_extractor.py +2 -36
  24. package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
  25. package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
  26. package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
  27. package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
  28. package/hooks/git-hooks/pre_push.py +3 -85
  29. package/hooks/git-hooks/test_pre_push.py +0 -130
  30. package/hooks/hooks.json +0 -35
  31. package/hooks/hooks_constants/CLAUDE.md +0 -9
  32. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
  33. package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
  34. package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
  35. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
  36. package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
  37. package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
  38. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
  39. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
  40. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
  41. package/hooks/lifecycle/CLAUDE.md +1 -3
  42. package/package.json +1 -1
  43. package/rules/CLAUDE.md +0 -2
  44. package/rules/gh-cli-conventions.md +0 -1
  45. package/rules/git-workflow.md +1 -3
  46. package/scripts/CLAUDE.md +0 -1
  47. package/scripts/_code_review_test_support.py +0 -95
  48. package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
  49. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
  50. package/scripts/invoke_code_review.py +8 -451
  51. package/scripts/test_invoke_code_review.py +5 -61
  52. package/scripts/test_invoke_code_review_cli.py +1 -45
  53. package/scripts/test_invoke_code_review_contract.py +1 -118
  54. package/settings.json +1 -10
  55. package/skills/CLAUDE.md +8 -0
  56. package/skills/autoconverge/reference/CLAUDE.md +1 -1
  57. package/skills/autoconverge/reference/convergence.md +7 -7
  58. package/skills/autoconverge/reference/gotchas.md +3 -10
  59. package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
  60. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
  61. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
  62. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
  63. package/skills/autoconverge/workflow/converge.mjs +201 -238
  64. package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
  65. package/skills/bugteam/CONSTRAINTS.md +1 -1
  66. package/skills/bugteam/SKILL.md +9 -0
  67. package/skills/bugteam/reference/audit-and-teammates.md +10 -6
  68. package/skills/bugteam/test_skill_additions.py +9 -0
  69. package/skills/comments/SKILL.md +65 -0
  70. package/skills/descriptions/SKILL.md +51 -0
  71. package/skills/emergencies/SKILL.md +42 -0
  72. package/skills/imagegen/SKILL.md +21 -0
  73. package/skills/imagegen/scripts/config/__init__.py +1 -0
  74. package/skills/imagegen/scripts/config/constants.py +28 -0
  75. package/skills/imagegen/scripts/imagegen.py +45 -0
  76. package/skills/imagegen/scripts/imagegen_core.py +528 -0
  77. package/skills/imagegen/scripts/test_imagegen.py +204 -0
  78. package/skills/orchestrator/SKILL.md +2 -4
  79. package/skills/plan-to-pr/SKILL.md +6 -7
  80. package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
  81. package/skills/plan-to-pr/reference/process-inventory.md +2 -2
  82. package/skills/plan-to-pr/reference/review-loop.md +2 -2
  83. package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
  84. package/skills/plan-to-pr/reference/task-seeds.md +1 -1
  85. package/skills/plan-to-pr/reference/task-ticket.md +3 -4
  86. package/skills/plan-to-pr/scripts/config/constants.py +1 -2
  87. package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
  88. package/skills/plan-to-pr/test_skill_contract.py +2 -2
  89. package/skills/pr-converge/SKILL.md +13 -16
  90. package/skills/pr-converge/reference/progress-checklist.md +6 -5
  91. package/skills/pr-converge/reference/state-schema.md +1 -27
  92. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  93. package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
  94. package/skills/pr-loop-lifecycle/SKILL.md +1 -1
  95. package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
  96. package/skills/prototype/SKILL.md +4 -4
  97. package/skills/prototype/reference/honest-limitations.md +4 -4
  98. package/skills/prototype/reference/promotion-tasks.md +2 -2
  99. package/skills/prototype/workflows/promotion.md +4 -6
  100. package/skills/reviews/SKILL.md +85 -0
  101. package/skills/small-cl/SKILL.md +52 -0
  102. package/agents/code-verifier.md +0 -74
  103. package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
  104. package/docs/references/code-review-enforcement.md +0 -132
  105. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
  106. package/hooks/blocking/code_review_gate_deny.py +0 -74
  107. package/hooks/blocking/code_review_pr_create_gate.py +0 -198
  108. package/hooks/blocking/code_review_push_gate.py +0 -145
  109. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
  110. package/hooks/blocking/code_review_stamp_store.py +0 -233
  111. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
  112. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
  113. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
  114. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
  115. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
  116. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
  117. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
  118. package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
  119. package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
  120. package/hooks/blocking/config/verified_commit_constants.py +0 -160
  121. package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
  122. package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
  123. package/hooks/blocking/conftest.py +0 -123
  124. package/hooks/blocking/convergence_gate_blocker.py +0 -310
  125. package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
  126. package/hooks/blocking/pr_description_body_audit.py +0 -148
  127. package/hooks/blocking/pr_description_command_parser.py +0 -237
  128. package/hooks/blocking/pr_description_enforcer.py +0 -227
  129. package/hooks/blocking/pr_description_pr_number.py +0 -155
  130. package/hooks/blocking/pr_description_proof_of_work.py +0 -385
  131. package/hooks/blocking/pr_description_readability.py +0 -364
  132. package/hooks/blocking/reviewer_spawn_gate.py +0 -182
  133. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
  134. package/hooks/blocking/test_code_review_gate_deny.py +0 -54
  135. package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
  136. package/hooks/blocking/test_code_review_push_gate.py +0 -205
  137. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
  138. package/hooks/blocking/test_code_review_stamp_store.py +0 -205
  139. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
  140. package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
  141. package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
  142. package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
  143. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
  144. package/hooks/blocking/test_pr_description_enforcer.py +0 -221
  145. package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
  146. package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
  147. package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
  148. package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
  149. package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
  150. package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
  151. package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
  152. package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
  153. package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
  154. package/hooks/blocking/test_verification_verdict_store.py +0 -974
  155. package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
  156. package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
  157. package/hooks/blocking/test_verified_commit_gate.py +0 -581
  158. package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
  159. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
  160. package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
  161. package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
  162. package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
  163. package/hooks/blocking/verification_verdict_store.py +0 -1039
  164. package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
  165. package/hooks/blocking/verified_commit_gate.py +0 -113
  166. package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
  167. package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
  168. package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
  169. package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
  170. package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
  171. package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
  172. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
  173. package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
  174. package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
  175. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
  176. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
  177. package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
  178. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
  179. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
  180. package/hooks/blocking/verifier_verdict_minter.py +0 -280
  181. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
  182. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
  183. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
  184. package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
  185. package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
  186. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
  187. package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
  188. package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
  189. package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
  190. package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
  191. package/rules/proof-of-work-pr-comments.md +0 -22
  192. package/rules/verified-commit-gate-skip.md +0 -4
  193. package/scripts/Show-Asset.ps1 +0 -106
@@ -1,134 +0,0 @@
1
- """The verified-commit gate's deny payload teaches the verify-skip policy.
2
-
3
- A blocked commit prints a PreToolUse ``hookSpecificOutput`` deny whose
4
- ``additionalContext`` carries the ``# verify-skip`` usage rule, so the agent
5
- learns at the moment of the block when the marker is legitimate.
6
- """
7
-
8
- import importlib.util
9
- import io
10
- import json
11
- import pathlib
12
- import subprocess
13
- import sys
14
-
15
- import pytest
16
-
17
- _HOOK_DIR = pathlib.Path(__file__).parent
18
- if str(_HOOK_DIR) not in sys.path:
19
- sys.path.insert(0, str(_HOOK_DIR))
20
-
21
- gate_spec = importlib.util.spec_from_file_location(
22
- "verified_commit_gate",
23
- _HOOK_DIR / "verified_commit_gate.py",
24
- )
25
- assert gate_spec is not None
26
- assert gate_spec.loader is not None
27
- gate_module = importlib.util.module_from_spec(gate_spec)
28
- gate_spec.loader.exec_module(gate_module)
29
- gate_main = gate_module.main
30
-
31
- PRODUCTION_SOURCE = "def add(left: int, right: int) -> int:\n return left + right\n"
32
- CHANGED_SOURCE = "def add(left: int, right: int) -> int:\n return left - right\n"
33
-
34
-
35
- def _run_git(work_dir: pathlib.Path, *git_arguments: str) -> None:
36
- subprocess.run(
37
- ["git", "-C", str(work_dir), *git_arguments],
38
- check=True,
39
- capture_output=True,
40
- text=True,
41
- )
42
-
43
-
44
- def _make_gated_repo(tmp_path: pathlib.Path) -> pathlib.Path:
45
- origin_dir = tmp_path / "origin.git"
46
- work_dir = tmp_path / "work"
47
- work_dir.mkdir()
48
- subprocess.run(
49
- ["git", "init", "--bare", "--initial-branch=main", str(origin_dir)],
50
- check=True,
51
- capture_output=True,
52
- text=True,
53
- )
54
- _run_git(work_dir, "init", "--initial-branch=main")
55
- _run_git(work_dir, "config", "user.email", "tests@example.com")
56
- _run_git(work_dir, "config", "user.name", "Gate Tests")
57
- (work_dir / "app.py").write_text(PRODUCTION_SOURCE, encoding="utf-8")
58
- _run_git(work_dir, "add", "-A")
59
- _run_git(work_dir, "commit", "-m", "base")
60
- _run_git(work_dir, "remote", "add", "origin", str(origin_dir))
61
- _run_git(work_dir, "push", "-u", "origin", "main")
62
- (work_dir / "app.py").write_text(CHANGED_SOURCE, encoding="utf-8")
63
- return work_dir
64
-
65
-
66
- def _isolate_home(monkeypatch: pytest.MonkeyPatch, fake_home: pathlib.Path) -> None:
67
- home_text = str(fake_home)
68
- monkeypatch.setenv("HOME", home_text)
69
- monkeypatch.setenv("USERPROFILE", home_text)
70
- monkeypatch.delenv("HOMEDRIVE", raising=False)
71
- monkeypatch.delenv("HOMEPATH", raising=False)
72
-
73
-
74
- def _run_gate_main(
75
- monkeypatch: pytest.MonkeyPatch, command_text: str, work_dir: pathlib.Path
76
- ) -> None:
77
- payload_text = json.dumps(
78
- {
79
- "tool_name": "Bash",
80
- "tool_input": {"command": command_text},
81
- "cwd": str(work_dir),
82
- "transcript_path": "",
83
- }
84
- )
85
- monkeypatch.setattr(sys, "stdin", io.StringIO(payload_text))
86
- gate_main()
87
-
88
-
89
- def test_deny_payload_carries_verify_skip_additional_context(
90
- monkeypatch: pytest.MonkeyPatch,
91
- capsys: pytest.CaptureFixture[str],
92
- tmp_path: pathlib.Path,
93
- ) -> None:
94
- fake_home = tmp_path / "home"
95
- fake_home.mkdir()
96
- _isolate_home(monkeypatch, fake_home)
97
- work_dir = _make_gated_repo(tmp_path)
98
- _run_gate_main(monkeypatch, "git commit -m x", work_dir)
99
- deny_payload = json.loads(capsys.readouterr().out)
100
- hook_specific_output = deny_payload["hookSpecificOutput"]
101
- assert hook_specific_output["permissionDecision"] == "deny"
102
- additional_context = hook_specific_output["additionalContext"]
103
- assert "# verify-skip" in additional_context
104
- assert "already passed clean" in additional_context
105
- assert "fresh verification" in additional_context
106
-
107
-
108
- def test_marker_inside_a_quoted_message_still_denies(
109
- monkeypatch: pytest.MonkeyPatch,
110
- capsys: pytest.CaptureFixture[str],
111
- tmp_path: pathlib.Path,
112
- ) -> None:
113
- fake_home = tmp_path / "home"
114
- fake_home.mkdir()
115
- _isolate_home(monkeypatch, fake_home)
116
- work_dir = _make_gated_repo(tmp_path)
117
- quoted_marker_command = 'git commit -m "docs: explain the # verify-skip escape hatch"'
118
- _run_gate_main(monkeypatch, quoted_marker_command, work_dir)
119
- deny_payload = json.loads(capsys.readouterr().out)
120
- assert deny_payload["hookSpecificOutput"]["permissionDecision"] == "deny"
121
-
122
-
123
- def test_marker_as_a_trailing_comment_bypasses(
124
- monkeypatch: pytest.MonkeyPatch,
125
- capsys: pytest.CaptureFixture[str],
126
- tmp_path: pathlib.Path,
127
- ) -> None:
128
- fake_home = tmp_path / "home"
129
- fake_home.mkdir()
130
- _isolate_home(monkeypatch, fake_home)
131
- work_dir = _make_gated_repo(tmp_path)
132
- trailing_marker_command = 'git commit -m "wire up feature" # verify-skip'
133
- _run_gate_main(monkeypatch, trailing_marker_command, work_dir)
134
- assert capsys.readouterr().out == ""
@@ -1,131 +0,0 @@
1
- """Unit tests for the verified-commit-message-accuracy PreToolUse hook."""
2
-
3
- import importlib.util
4
- import pathlib
5
- import sys
6
-
7
- _HOOK_DIR = pathlib.Path(__file__).parent
8
- if str(_HOOK_DIR) not in sys.path:
9
- sys.path.insert(0, str(_HOOK_DIR))
10
-
11
- hook_spec = importlib.util.spec_from_file_location(
12
- "verified_commit_message_accuracy_blocker",
13
- _HOOK_DIR / "verified_commit_message_accuracy_blocker.py",
14
- )
15
- assert hook_spec is not None
16
- assert hook_spec.loader is not None
17
- hook_module = importlib.util.module_from_spec(hook_spec)
18
- hook_spec.loader.exec_module(hook_module)
19
- is_guarded_file = hook_module.is_guarded_file
20
- claims_blanket_comment_exemption = hook_module.claims_blanket_comment_exemption
21
- extract_written_text = hook_module.extract_written_text
22
- build_corrective_message = hook_module.build_corrective_message
23
-
24
- OFFENDING_MESSAGE = (
25
- "CORRECTIVE_MESSAGE = (\n"
26
- ' "BLOCKED: [VERIFIED_COMMIT_GATE] This branch surface has no passing "\n'
27
- ' "verification verdict. Spawn the code-verifier agent (Agent tool, "\n'
28
- " \"subagent_type 'code-verifier') with the task texts, the diff scope, \"\n"
29
- ' "and recorded baselines; when it finishes with a clean verdict the "\n'
30
- ' "SubagentStop hook mints the verdict and this command will pass. Any "\n'
31
- ' "file change after verification invalidates the verdict, so verify "\n'
32
- ' "last. Docs-, docstring-, comment-, and test-only surfaces are exempt "\n'
33
- ' "automatically."\n'
34
- ")\n"
35
- )
36
- ACCURATE_DOCS_EXEMPTION_MENTIONING_COMMENTS = (
37
- "Comments inside Python files are stripped; docs are exempt "
38
- "automatically by extension."
39
- )
40
- ACCURATE_MESSAGE = (
41
- "CORRECTIVE_MESSAGE = (\n"
42
- ' "BLOCKED: [VERIFIED_COMMIT_GATE] ... Docs and images are exempt by "\n'
43
- ' "extension, and Python files whose docstring- and comment-stripped AST "\n'
44
- ' "is unchanged."\n'
45
- ")\n"
46
- )
47
-
48
-
49
- def test_constants_file_is_guarded() -> None:
50
- assert is_guarded_file(
51
- "/repo/.claude/hooks/blocking/config/verified_commit_constants.py"
52
- )
53
-
54
-
55
- def test_unrelated_file_is_not_guarded() -> None:
56
- assert not is_guarded_file("/repo/.claude/hooks/blocking/gh_body_arg_blocker.py")
57
-
58
-
59
- def test_blanket_comment_exemption_claim_is_detected() -> None:
60
- assert claims_blanket_comment_exemption(OFFENDING_MESSAGE)
61
-
62
-
63
- def test_blanket_claim_detected_regardless_of_leading_words() -> None:
64
- assert claims_blanket_comment_exemption(
65
- "Comment-only surfaces are exempt automatically."
66
- )
67
-
68
-
69
- def test_accurate_exemption_wording_passes() -> None:
70
- assert not claims_blanket_comment_exemption(ACCURATE_MESSAGE)
71
-
72
-
73
- def test_accurate_docs_exemption_mentioning_comments_passes() -> None:
74
- assert not claims_blanket_comment_exemption(
75
- ACCURATE_DOCS_EXEMPTION_MENTIONING_COMMENTS
76
- )
77
-
78
-
79
- def test_comma_joined_docs_exemption_mentioning_comments_passes() -> None:
80
- assert not claims_blanket_comment_exemption(
81
- "Comments are handled, and docs are exempt automatically."
82
- )
83
-
84
-
85
- def test_python_ast_clause_mentioning_comments_passes() -> None:
86
- assert not claims_blanket_comment_exemption(
87
- "Python comment-stripped AST changes and docs are exempt automatically "
88
- "by extension"
89
- )
90
-
91
-
92
- def test_single_clause_python_ast_exemption_passes() -> None:
93
- assert not claims_blanket_comment_exemption(
94
- "Python files whose comment-stripped AST is unchanged are exempt "
95
- "automatically."
96
- )
97
-
98
-
99
- def test_commentary_word_stem_passes() -> None:
100
- assert not claims_blanket_comment_exemption(
101
- "Our commentary on the approach is exempt automatically from blame."
102
- )
103
-
104
-
105
- def test_corrective_message_names_only_the_two_real_exemptions() -> None:
106
- corrective_message = build_corrective_message()
107
- assert "exempt by extension" in corrective_message
108
- assert "docstring- and comment-stripped AST" in corrective_message
109
- assert "test file" not in corrective_message
110
- assert "by name convention" not in corrective_message
111
-
112
-
113
- def test_message_without_exemption_claim_passes() -> None:
114
- assert not claims_blanket_comment_exemption(
115
- 'CORRECTIVE_MESSAGE = "Spawn the code-verifier agent to earn a verdict."'
116
- )
117
-
118
-
119
- def test_write_content_is_extracted() -> None:
120
- written_text = extract_written_text({"content": OFFENDING_MESSAGE})
121
- assert claims_blanket_comment_exemption(written_text)
122
-
123
-
124
- def test_edit_new_string_is_extracted() -> None:
125
- written_text = extract_written_text({"new_string": OFFENDING_MESSAGE})
126
- assert claims_blanket_comment_exemption(written_text)
127
-
128
-
129
- def test_edit_new_string_with_accurate_wording_is_clean() -> None:
130
- written_text = extract_written_text({"new_string": ACCURATE_MESSAGE})
131
- assert not claims_blanket_comment_exemption(written_text)
@@ -1,299 +0,0 @@
1
- """Tests for the agent-type gate in verifier_verdict_minter.
2
-
3
- The minter mints a verdict only for a code-verifier stop event. The
4
- SubagentStop payload names the stopping subagent's own transcript
5
- (``agent_transcript_path``), which sits beside a harness-written
6
- ``agent-<id>.meta.json`` sidecar naming the spawning ``agentType``. These
7
- tests build that sidecar and assert the minter gates on the resolved type and
8
- on the shared MINTING_AGENT_TYPE constant, so a rename in config propagates to
9
- the minter without a second edit. A malformed or non-string sidecar resolves
10
- nothing, and an absent sidecar mints nothing even when the transcript carries a
11
- verdict fence — the main session controls the prompt of any Agent-tool subagent
12
- it spawns, so a verdict fence in the transcript proves nothing about the agent
13
- type, and only the harness-written sidecar attests a code-verifier. A further
14
- test holds the shipped settings.json to the minter docstring's anti-forgery
15
- claim: the main session is denied writes to the verdict directory, so only this
16
- hook can mint a passing verdict.
17
- """
18
-
19
- import importlib.util
20
- import json
21
- import pathlib
22
- import subprocess
23
- import sys
24
- from collections.abc import Callable
25
-
26
- _HOOK_DIR = pathlib.Path(__file__).parent
27
- if str(_HOOK_DIR) not in sys.path:
28
- sys.path.insert(0, str(_HOOK_DIR))
29
-
30
- _SETTINGS_PATH = _HOOK_DIR.parent.parent / "settings.json"
31
-
32
- minter_spec = importlib.util.spec_from_file_location(
33
- "verifier_verdict_minter",
34
- _HOOK_DIR / "verifier_verdict_minter.py",
35
- )
36
- assert minter_spec is not None
37
- assert minter_spec.loader is not None
38
- minter_module = importlib.util.module_from_spec(minter_spec)
39
- minter_spec.loader.exec_module(minter_module)
40
- mint_for_payload = minter_module.mint_for_payload
41
- resolved_subagent_type = minter_module.resolved_subagent_type
42
-
43
- store_spec = importlib.util.spec_from_file_location(
44
- "verification_verdict_store",
45
- _HOOK_DIR / "verification_verdict_store.py",
46
- )
47
- assert store_spec is not None
48
- assert store_spec.loader is not None
49
- store_module = importlib.util.module_from_spec(store_spec)
50
- store_spec.loader.exec_module(store_module)
51
- empty_surface_hash = store_module.empty_surface_hash
52
-
53
- constants_spec = importlib.util.spec_from_file_location(
54
- "verified_commit_constants",
55
- _HOOK_DIR / "config" / "verified_commit_constants.py",
56
- )
57
- assert constants_spec is not None
58
- assert constants_spec.loader is not None
59
- constants_module = importlib.util.module_from_spec(constants_spec)
60
- constants_spec.loader.exec_module(constants_module)
61
- MINTING_AGENT_TYPE = constants_module.MINTING_AGENT_TYPE
62
-
63
-
64
- def _write_sidecar(agent_transcript_file: pathlib.Path, agent_type: str) -> None:
65
- sidecar_file = agent_transcript_file.with_name(f"{agent_transcript_file.stem}.meta.json")
66
- sidecar_file.write_text(
67
- json.dumps({"agentType": agent_type, "description": "Verify"}) + "\n",
68
- encoding="utf-8",
69
- )
70
-
71
-
72
- def _write_verdict_transcript(agent_transcript_file: pathlib.Path, verdict_body: str) -> None:
73
- verdict_text = f"ok\n```verdict\n{verdict_body}\n```\n"
74
- agent_transcript_file.write_text(
75
- json.dumps(
76
- {
77
- "type": "assistant",
78
- "message": {"content": [{"type": "text", "text": verdict_text}]},
79
- }
80
- )
81
- + "\n",
82
- encoding="utf-8",
83
- )
84
-
85
-
86
- def _make_repo(tmp_path: pathlib.Path) -> pathlib.Path:
87
- repo_root = tmp_path / "repo"
88
- repo_root.mkdir()
89
- return repo_root
90
-
91
-
92
- def _mint_payload(
93
- agent_transcript_file: pathlib.Path, repo_root: pathlib.Path, agent_id: str
94
- ) -> dict[str, str]:
95
- return {
96
- "agent_transcript_path": str(agent_transcript_file),
97
- "cwd": str(repo_root),
98
- "agent_id": agent_id,
99
- }
100
-
101
-
102
- def test_resolves_subagent_type_from_sidecar(tmp_path: pathlib.Path) -> None:
103
- agent_transcript = tmp_path / "agent-7.jsonl"
104
- agent_transcript.write_text("", encoding="utf-8")
105
- _write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
106
- payload = {"agent_transcript_path": str(agent_transcript)}
107
- assert resolved_subagent_type(payload) == MINTING_AGENT_TYPE
108
-
109
-
110
- def test_resolves_none_when_sidecar_absent(tmp_path: pathlib.Path) -> None:
111
- agent_transcript = tmp_path / "agent-7.jsonl"
112
- agent_transcript.write_text("", encoding="utf-8")
113
- payload = {"agent_transcript_path": str(agent_transcript)}
114
- assert resolved_subagent_type(payload) is None
115
-
116
-
117
- def test_resolves_none_when_agent_transcript_path_empty() -> None:
118
- assert resolved_subagent_type({"agent_transcript_path": ""}) is None
119
- assert resolved_subagent_type({}) is None
120
-
121
-
122
- def test_resolves_none_when_sidecar_names_no_string_type(tmp_path: pathlib.Path) -> None:
123
- agent_transcript = tmp_path / "agent-7.jsonl"
124
- agent_transcript.write_text("", encoding="utf-8")
125
- sidecar_file = agent_transcript.with_name("agent-7.meta.json")
126
- sidecar_file.write_text(json.dumps({"agentType": 123}), encoding="utf-8")
127
- payload = {"agent_transcript_path": str(agent_transcript)}
128
- assert resolved_subagent_type(payload) is None
129
-
130
-
131
- def test_unparseable_sidecar_resolves_nothing(tmp_path: pathlib.Path) -> None:
132
- agent_transcript = tmp_path / "agent-7.jsonl"
133
- agent_transcript.write_text("", encoding="utf-8")
134
- sidecar_file = agent_transcript.with_name("agent-7.meta.json")
135
- sidecar_file.write_text("{not valid json", encoding="utf-8")
136
- payload = {"agent_transcript_path": str(agent_transcript)}
137
- assert resolved_subagent_type(payload) is None
138
-
139
-
140
- def test_invalid_utf8_sidecar_resolves_nothing(tmp_path: pathlib.Path) -> None:
141
- agent_transcript = tmp_path / "agent-7.jsonl"
142
- agent_transcript.write_text("", encoding="utf-8")
143
- sidecar_file = agent_transcript.with_name("agent-7.meta.json")
144
- sidecar_file.write_bytes(b'{"agentType": "\xff\xfe bad"}')
145
- payload = {"agent_transcript_path": str(agent_transcript)}
146
- assert resolved_subagent_type(payload) is None
147
-
148
-
149
- def test_non_object_json_sidecar_resolves_nothing(tmp_path: pathlib.Path) -> None:
150
- agent_transcript = tmp_path / "agent-7.jsonl"
151
- agent_transcript.write_text("", encoding="utf-8")
152
- sidecar_file = agent_transcript.with_name("agent-7.meta.json")
153
- sidecar_file.write_text(json.dumps(["agentType", "code-verifier"]), encoding="utf-8")
154
- payload = {"agent_transcript_path": str(agent_transcript)}
155
- assert resolved_subagent_type(payload) is None
156
-
157
-
158
- def test_non_verifier_agent_type_mints_nothing(tmp_path: pathlib.Path) -> None:
159
- agent_transcript = tmp_path / "agent-7.jsonl"
160
- agent_transcript.write_text("", encoding="utf-8")
161
- _write_sidecar(agent_transcript, "general-purpose")
162
- payload = {"agent_transcript_path": str(agent_transcript)}
163
- assert mint_for_payload(payload) is None
164
-
165
-
166
- def test_verifier_type_without_a_verdict_mints_nothing(tmp_path: pathlib.Path) -> None:
167
- agent_transcript = tmp_path / "agent-7.jsonl"
168
- agent_transcript.write_text("", encoding="utf-8")
169
- _write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
170
- payload = {"agent_transcript_path": str(agent_transcript)}
171
- assert mint_for_payload(payload) is None
172
-
173
-
174
- def _init_repo_with_upstream(repo_root: pathlib.Path) -> None:
175
- subprocess.run(["git", "-C", str(repo_root), "init", "-q"], check=True)
176
- subprocess.run(
177
- ["git", "-C", str(repo_root), "config", "user.email", "verifier@test"], check=True
178
- )
179
- subprocess.run(["git", "-C", str(repo_root), "config", "user.name", "verifier"], check=True)
180
- (repo_root / "module.py").write_text("answer = 1\n", encoding="utf-8")
181
- subprocess.run(["git", "-C", str(repo_root), "add", "-A"], check=True)
182
- subprocess.run(["git", "-C", str(repo_root), "commit", "-qm", "init"], check=True)
183
- subprocess.run(["git", "-C", str(repo_root), "branch", "-f", "origin/main", "HEAD"], check=True)
184
-
185
-
186
- def _init_repo_with_upstream_and_edit(repo_root: pathlib.Path) -> None:
187
- _init_repo_with_upstream(repo_root)
188
- (repo_root / "module.py").write_text("answer = 2\n", encoding="utf-8")
189
-
190
-
191
- def test_clean_verifier_verdict_mints_a_verdict_file(tmp_path: pathlib.Path) -> None:
192
- repo_root = _make_repo(tmp_path)
193
- _init_repo_with_upstream_and_edit(repo_root)
194
- agent_transcript = tmp_path / "agent-7.jsonl"
195
- _write_verdict_transcript(agent_transcript, '{"all_pass": true, "findings": []}')
196
- _write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
197
- payload = _mint_payload(agent_transcript, repo_root, "a02b9583eedc74093")
198
- verdict_path = mint_for_payload(payload)
199
- try:
200
- assert verdict_path is not None
201
- verdict_record = json.loads(verdict_path.read_text(encoding="utf-8"))
202
- assert verdict_record["all_pass"] is True
203
- assert verdict_record["minted_from_agent_id"] == "a02b9583eedc74093"
204
- finally:
205
- if verdict_path is not None and verdict_path.exists():
206
- verdict_path.unlink()
207
-
208
-
209
- def _deny_rules() -> list[str]:
210
- settings_record = json.loads(_SETTINGS_PATH.read_text(encoding="utf-8"))
211
- return settings_record["permissions"]["deny"]
212
-
213
-
214
- def test_settings_deny_verdict_directory_write() -> None:
215
- assert "Edit($HOME/.claude/verification/**)" in _deny_rules()
216
- assert "Write($HOME/.claude/verification/**)" not in _deny_rules()
217
-
218
-
219
- def test_settings_deny_verdict_directory_edit() -> None:
220
- assert "Edit($HOME/.claude/verification/**)" in _deny_rules()
221
-
222
-
223
- def test_minter_refuses_when_attested_hash_equals_empty_surface_hash(
224
- tmp_path: pathlib.Path,
225
- ) -> None:
226
- repo_root = _make_repo(tmp_path)
227
- _init_repo_with_upstream_and_edit(repo_root)
228
- attested_empty = empty_surface_hash()
229
- verdict_fence = json.dumps(
230
- {"all_pass": True, "findings": [], "manifest_sha256": attested_empty}
231
- )
232
- agent_transcript = tmp_path / "agent-7.jsonl"
233
- _write_verdict_transcript(agent_transcript, verdict_fence)
234
- _write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
235
- payload = _mint_payload(agent_transcript, repo_root, "empty-surface-1")
236
- assert mint_for_payload(payload) is None
237
-
238
-
239
- def test_minter_refuses_when_recomputed_surface_is_empty(
240
- tmp_path: pathlib.Path,
241
- ) -> None:
242
- repo_root = _make_repo(tmp_path)
243
- _init_repo_with_upstream(repo_root)
244
- agent_transcript = tmp_path / "agent-7.jsonl"
245
- _write_verdict_transcript(agent_transcript, '{"all_pass": true, "findings": []}')
246
- _write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
247
- payload = _mint_payload(agent_transcript, repo_root, "empty-recompute-1")
248
- assert mint_for_payload(payload) is None
249
-
250
-
251
- def test_resolves_none_when_sidecar_absent_even_with_verdict_fence(
252
- tmp_path: pathlib.Path,
253
- ) -> None:
254
- agent_transcript = tmp_path / "agent-7.jsonl"
255
- _write_verdict_transcript(agent_transcript, '{"all_pass": true, "findings": []}')
256
- payload = {"agent_transcript_path": str(agent_transcript)}
257
- assert resolved_subagent_type(payload) is None
258
-
259
-
260
- def test_does_not_mint_when_sidecar_absent_but_transcript_has_verdict(
261
- tmp_path: pathlib.Path,
262
- ) -> None:
263
- repo_root = _make_repo(tmp_path)
264
- _init_repo_with_upstream_and_edit(repo_root)
265
- agent_transcript = tmp_path / "agent-7.jsonl"
266
- _write_verdict_transcript(agent_transcript, '{"all_pass": true, "findings": []}')
267
- payload = _mint_payload(agent_transcript, repo_root, "no-sidecar-1")
268
- assert mint_for_payload(payload) is None
269
-
270
-
271
- def test_attested_manifest_hash_binds_over_cwd_surface(tmp_path: pathlib.Path) -> None:
272
- repo_root = _make_repo(tmp_path)
273
- _init_repo_with_upstream_and_edit(repo_root)
274
- attested_hash = "c" * 64
275
- verdict_fence = json.dumps(
276
- {"all_pass": True, "findings": [], "manifest_sha256": attested_hash}
277
- )
278
- agent_transcript = tmp_path / "agent-7.jsonl"
279
- _write_verdict_transcript(agent_transcript, verdict_fence)
280
- _write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
281
- payload = _mint_payload(agent_transcript, repo_root, "attest-1")
282
- verdict_path = mint_for_payload(payload)
283
- try:
284
- assert verdict_path is not None
285
- verdict_record = json.loads(verdict_path.read_text(encoding="utf-8"))
286
- assert verdict_record["manifest_sha256"] == attested_hash
287
- finally:
288
- if verdict_path is not None and verdict_path.exists():
289
- verdict_path.unlink()
290
-
291
-
292
- def test_minter_imports_under_foreign_config_shadow(
293
- run_under_config_shadow: Callable[[str], subprocess.CompletedProcess[str]],
294
- ) -> None:
295
- completed_probe = run_under_config_shadow(
296
- "import verifier_verdict_minter\nprint('IMPORT_OK')\n"
297
- )
298
- assert completed_probe.returncode == 0, completed_probe.stderr
299
- assert "IMPORT_OK" in completed_probe.stdout
@@ -1,41 +0,0 @@
1
- """Entry-boundary behavior of the verified_commit_gate hook process.
2
-
3
- Runs the hook as a subprocess the way Claude Code does and pins the two
4
- silent paths: a tool the gate does not cover, and a gated command carrying
5
- the bypass marker.
6
- """
7
-
8
- import json
9
- import pathlib
10
- import subprocess
11
- import sys
12
-
13
- _BLOCKING_DIRECTORY = pathlib.Path(__file__).resolve().parent.parent
14
- _GATE_SCRIPT_PATH = _BLOCKING_DIRECTORY / "verified_commit_gate.py"
15
-
16
-
17
- def _run_gate_process(tool_name: str, command_text: str) -> str:
18
- payload_text = json.dumps(
19
- {
20
- "tool_name": tool_name,
21
- "tool_input": {"command": command_text},
22
- "cwd": str(_BLOCKING_DIRECTORY),
23
- "transcript_path": "",
24
- }
25
- )
26
- completed_process = subprocess.run(
27
- [sys.executable, str(_GATE_SCRIPT_PATH)],
28
- input=payload_text,
29
- capture_output=True,
30
- text=True,
31
- check=True,
32
- )
33
- return completed_process.stdout
34
-
35
-
36
- def test_uncovered_tool_name_stays_silent() -> None:
37
- assert _run_gate_process("Read", "git commit -m x") == ""
38
-
39
-
40
- def test_bypass_marker_stays_silent_at_the_process_boundary() -> None:
41
- assert _run_gate_process("Bash", "git commit -m x # verify-skip") == ""