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,193 @@
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 — the main session writes neither
11
+ the transcript nor the sidecar, so it cannot forge a passing verdict. A
12
+ further test holds the shipped settings.json to the minter docstring's
13
+ anti-forgery claim: the main session is denied writes to the verdict
14
+ directory, so only this hook can mint a passing verdict.
15
+ """
16
+
17
+ import importlib.util
18
+ import json
19
+ import pathlib
20
+ import subprocess
21
+ import sys
22
+
23
+ _HOOK_DIR = pathlib.Path(__file__).parent
24
+ if str(_HOOK_DIR) not in sys.path:
25
+ sys.path.insert(0, str(_HOOK_DIR))
26
+
27
+ _SETTINGS_PATH = _HOOK_DIR.parent.parent / "settings.json"
28
+
29
+ minter_spec = importlib.util.spec_from_file_location(
30
+ "verifier_verdict_minter",
31
+ _HOOK_DIR / "verifier_verdict_minter.py",
32
+ )
33
+ assert minter_spec is not None
34
+ assert minter_spec.loader is not None
35
+ minter_module = importlib.util.module_from_spec(minter_spec)
36
+ minter_spec.loader.exec_module(minter_module)
37
+ mint_for_payload = minter_module.mint_for_payload
38
+ resolved_subagent_type = minter_module.resolved_subagent_type
39
+
40
+ constants_spec = importlib.util.spec_from_file_location(
41
+ "verified_commit_constants",
42
+ _HOOK_DIR / "config" / "verified_commit_constants.py",
43
+ )
44
+ assert constants_spec is not None
45
+ assert constants_spec.loader is not None
46
+ constants_module = importlib.util.module_from_spec(constants_spec)
47
+ constants_spec.loader.exec_module(constants_module)
48
+ MINTING_AGENT_TYPE = constants_module.MINTING_AGENT_TYPE
49
+
50
+
51
+ def _write_sidecar(agent_transcript_file: pathlib.Path, agent_type: str) -> None:
52
+ sidecar_file = agent_transcript_file.with_name(f"{agent_transcript_file.stem}.meta.json")
53
+ sidecar_file.write_text(
54
+ json.dumps({"agentType": agent_type, "description": "Verify"}) + "\n",
55
+ encoding="utf-8",
56
+ )
57
+
58
+
59
+ def test_resolves_subagent_type_from_sidecar(tmp_path: pathlib.Path) -> None:
60
+ agent_transcript = tmp_path / "agent-7.jsonl"
61
+ agent_transcript.write_text("", encoding="utf-8")
62
+ _write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
63
+ payload = {"agent_transcript_path": str(agent_transcript)}
64
+ assert resolved_subagent_type(payload) == MINTING_AGENT_TYPE
65
+
66
+
67
+ def test_resolves_none_when_sidecar_absent(tmp_path: pathlib.Path) -> None:
68
+ agent_transcript = tmp_path / "agent-7.jsonl"
69
+ agent_transcript.write_text("", encoding="utf-8")
70
+ payload = {"agent_transcript_path": str(agent_transcript)}
71
+ assert resolved_subagent_type(payload) is None
72
+
73
+
74
+ def test_resolves_none_when_agent_transcript_path_empty() -> None:
75
+ assert resolved_subagent_type({"agent_transcript_path": ""}) is None
76
+ assert resolved_subagent_type({}) is None
77
+
78
+
79
+ def test_resolves_none_when_sidecar_names_no_string_type(tmp_path: pathlib.Path) -> None:
80
+ agent_transcript = tmp_path / "agent-7.jsonl"
81
+ agent_transcript.write_text("", encoding="utf-8")
82
+ sidecar_file = agent_transcript.with_name("agent-7.meta.json")
83
+ sidecar_file.write_text(json.dumps({"agentType": 123}), encoding="utf-8")
84
+ payload = {"agent_transcript_path": str(agent_transcript)}
85
+ assert resolved_subagent_type(payload) is None
86
+
87
+
88
+ def test_unparseable_sidecar_resolves_nothing(tmp_path: pathlib.Path) -> None:
89
+ agent_transcript = tmp_path / "agent-7.jsonl"
90
+ agent_transcript.write_text("", encoding="utf-8")
91
+ sidecar_file = agent_transcript.with_name("agent-7.meta.json")
92
+ sidecar_file.write_text("{not valid json", encoding="utf-8")
93
+ payload = {"agent_transcript_path": str(agent_transcript)}
94
+ assert resolved_subagent_type(payload) is None
95
+
96
+
97
+ def test_invalid_utf8_sidecar_resolves_nothing(tmp_path: pathlib.Path) -> None:
98
+ agent_transcript = tmp_path / "agent-7.jsonl"
99
+ agent_transcript.write_text("", encoding="utf-8")
100
+ sidecar_file = agent_transcript.with_name("agent-7.meta.json")
101
+ sidecar_file.write_bytes(b'{"agentType": "\xff\xfe bad"}')
102
+ payload = {"agent_transcript_path": str(agent_transcript)}
103
+ assert resolved_subagent_type(payload) is None
104
+
105
+
106
+ def test_non_object_json_sidecar_resolves_nothing(tmp_path: pathlib.Path) -> None:
107
+ agent_transcript = tmp_path / "agent-7.jsonl"
108
+ agent_transcript.write_text("", encoding="utf-8")
109
+ sidecar_file = agent_transcript.with_name("agent-7.meta.json")
110
+ sidecar_file.write_text(json.dumps(["agentType", "code-verifier"]), encoding="utf-8")
111
+ payload = {"agent_transcript_path": str(agent_transcript)}
112
+ assert resolved_subagent_type(payload) is None
113
+
114
+
115
+ def test_non_verifier_agent_type_mints_nothing(tmp_path: pathlib.Path) -> None:
116
+ agent_transcript = tmp_path / "agent-7.jsonl"
117
+ agent_transcript.write_text("", encoding="utf-8")
118
+ _write_sidecar(agent_transcript, "general-purpose")
119
+ payload = {"agent_transcript_path": str(agent_transcript)}
120
+ assert mint_for_payload(payload) is None
121
+
122
+
123
+ def test_verifier_type_without_a_verdict_mints_nothing(tmp_path: pathlib.Path) -> None:
124
+ agent_transcript = tmp_path / "agent-7.jsonl"
125
+ agent_transcript.write_text("", encoding="utf-8")
126
+ _write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
127
+ payload = {"agent_transcript_path": str(agent_transcript)}
128
+ assert mint_for_payload(payload) is None
129
+
130
+
131
+ def _init_repo_with_upstream_and_edit(repo_root: pathlib.Path) -> None:
132
+ subprocess.run(["git", "-C", str(repo_root), "init", "-q"], check=True)
133
+ subprocess.run(
134
+ ["git", "-C", str(repo_root), "config", "user.email", "verifier@test"], check=True
135
+ )
136
+ subprocess.run(["git", "-C", str(repo_root), "config", "user.name", "verifier"], check=True)
137
+ (repo_root / "module.py").write_text("answer = 1\n", encoding="utf-8")
138
+ subprocess.run(["git", "-C", str(repo_root), "add", "-A"], check=True)
139
+ subprocess.run(["git", "-C", str(repo_root), "commit", "-qm", "init"], check=True)
140
+ subprocess.run(["git", "-C", str(repo_root), "branch", "-f", "origin/main", "HEAD"], check=True)
141
+ (repo_root / "module.py").write_text("answer = 2\n", encoding="utf-8")
142
+
143
+
144
+ def test_clean_verifier_verdict_mints_a_verdict_file(tmp_path: pathlib.Path) -> None:
145
+ repo_root = tmp_path / "repo"
146
+ repo_root.mkdir()
147
+ _init_repo_with_upstream_and_edit(repo_root)
148
+ agent_transcript = tmp_path / "agent-7.jsonl"
149
+ agent_transcript.write_text(
150
+ json.dumps(
151
+ {
152
+ "type": "assistant",
153
+ "message": {
154
+ "content": [
155
+ {
156
+ "type": "text",
157
+ "text": 'ok\n```verdict\n{"all_pass": true, "findings": []}\n```\n',
158
+ }
159
+ ]
160
+ },
161
+ }
162
+ )
163
+ + "\n",
164
+ encoding="utf-8",
165
+ )
166
+ _write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
167
+ payload = {
168
+ "agent_transcript_path": str(agent_transcript),
169
+ "cwd": str(repo_root),
170
+ "agent_id": "a02b9583eedc74093",
171
+ }
172
+ verdict_path = mint_for_payload(payload)
173
+ try:
174
+ assert verdict_path is not None
175
+ verdict_record = json.loads(verdict_path.read_text(encoding="utf-8"))
176
+ assert verdict_record["all_pass"] is True
177
+ assert verdict_record["minted_from_agent_id"] == "a02b9583eedc74093"
178
+ finally:
179
+ if verdict_path is not None and verdict_path.exists():
180
+ verdict_path.unlink()
181
+
182
+
183
+ def _deny_rules() -> list[str]:
184
+ settings_record = json.loads(_SETTINGS_PATH.read_text(encoding="utf-8"))
185
+ return settings_record["permissions"]["deny"]
186
+
187
+
188
+ def test_settings_deny_verdict_directory_write() -> None:
189
+ assert "Write($HOME/.claude/verification/**)" in _deny_rules()
190
+
191
+
192
+ def test_settings_deny_verdict_directory_edit() -> None:
193
+ assert "Edit($HOME/.claude/verification/**)" in _deny_rules()