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
@@ -0,0 +1,204 @@
1
+ """Focused tests for exact-resolution image generation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import hashlib
7
+ import json
8
+ import sys
9
+ from collections.abc import Mapping, Sequence
10
+ from io import BytesIO
11
+ from pathlib import Path
12
+
13
+ import pytest
14
+ from PIL import Image
15
+
16
+ sys.path.insert(0, str(Path(__file__).parent))
17
+
18
+ import imagegen_core
19
+ from imagegen_core import (
20
+ ImagegenError,
21
+ ImageSize,
22
+ build_oauth_environment,
23
+ decode_image,
24
+ download_https_image,
25
+ generate_image,
26
+ parse_size,
27
+ publish_artifact,
28
+ resize_image,
29
+ )
30
+
31
+
32
+ def make_png(image_size: tuple[int, int]) -> bytes:
33
+ """Create real PNG bytes for provider-boundary tests."""
34
+ destination = BytesIO()
35
+ Image.new("RGB", image_size, "purple").save(destination, format="PNG")
36
+ return destination.getvalue()
37
+
38
+
39
+ def test_native_output_preserves_source_bytes_and_receipt(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
40
+ source_bytes = make_png((2880, 2880))
41
+ monkeypatch.setenv("OPENAI_API_KEY", "present")
42
+ provider_payload = b'{"data":[{"b64_json":"' + base64.b64encode(source_bytes) + b'"}]}'
43
+ receipt = generate_image("native", "openai-api", ImageSize(2880, 2880), tmp_path / "native.png", "forbid", False, transport=lambda _url, _body, _headers: provider_payload)
44
+ assert (tmp_path / "native.png").read_bytes() == source_bytes
45
+ assert receipt["transformation"] == "native"
46
+ assert receipt["source_sha256"] == hashlib.sha256(source_bytes).hexdigest()
47
+
48
+
49
+ def test_oauth_output_resizes_and_strips_api_key(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
50
+ monkeypatch.setenv("OPENAI_API_KEY", "secret")
51
+ captured_environment: dict[str, str] = {}
52
+
53
+ def runner(_arguments: Sequence[str], work_directory: Path, environment: Mapping[str, str]) -> None:
54
+ captured_environment.update(environment)
55
+ (work_directory / "generated.png").write_bytes(make_png((1254, 1254)))
56
+
57
+ receipt = generate_image("resize", "codex-oauth", ImageSize(2880, 2880), tmp_path / "resized.png", "allow", False, runner=runner)
58
+ assert decode_image((tmp_path / "resized.png").read_bytes()) == ImageSize(2880, 2880)
59
+ assert receipt["transformation"] == "resized"
60
+ assert "OPENAI_API_KEY" not in captured_environment
61
+
62
+
63
+ @pytest.mark.parametrize("invalid_size", ("2880", "2880x1440", "2881x2881", "3841x3841", "3840x3840"))
64
+ def test_invalid_sizes_fail(invalid_size: str) -> None:
65
+ with pytest.raises(ImagegenError):
66
+ parse_size(invalid_size)
67
+
68
+
69
+ @pytest.mark.parametrize("valid_size", ("256x256", "512x512", "1028x1028", "2880x2880"))
70
+ def test_final_sizes_are_independent_from_native_api_sizes(valid_size: str) -> None:
71
+ assert parse_size(valid_size).as_text() == valid_size
72
+
73
+
74
+ def test_openai_unsupported_size_fails_before_authentication(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
75
+ monkeypatch.delenv("OPENAI_API_KEY", raising=False)
76
+
77
+ with pytest.raises(ImagegenError, match="resize-policy"):
78
+ generate_image("prompt", "openai-api", ImageSize(1028, 1028), tmp_path / "artifact.png", "forbid", False)
79
+
80
+
81
+ def test_openai_native_size_requires_api_key(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
82
+ monkeypatch.delenv("OPENAI_API_KEY", raising=False)
83
+
84
+ with pytest.raises(ImagegenError, match="OPENAI_API_KEY"):
85
+ generate_image("prompt", "openai-api", ImageSize(2880, 2880), tmp_path / "artifact.png", "forbid", False)
86
+
87
+
88
+ def test_openai_unsupported_size_requests_valid_provider_size_before_resize(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
89
+ monkeypatch.setenv("OPENAI_API_KEY", "present")
90
+ request_sizes: list[str] = []
91
+ request_formats: list[str] = []
92
+ source_bytes = make_png((2048, 2048))
93
+ provider_payload = b'{"data":[{"b64_json":"' + base64.b64encode(source_bytes) + b'"}]}'
94
+
95
+ def transport(_url: str, body: bytes, _headers: Mapping[str, str]) -> bytes:
96
+ request_payload = json.loads(body)
97
+ request_sizes.append(request_payload["size"])
98
+ request_formats.append(request_payload["output_format"])
99
+ return provider_payload
100
+
101
+ receipt = generate_image("prompt", "openai-api", ImageSize(1028, 1028), tmp_path / "artifact.png", "allow", False, transport=transport)
102
+
103
+ assert request_sizes == ["2048x2048"]
104
+ assert request_formats == ["png"]
105
+ assert receipt["source_size"] == "2048x2048"
106
+ assert receipt["final_size"] == "1028x1028"
107
+ assert receipt["transformation"] == "resized"
108
+
109
+
110
+ def test_oauth_environment_strips_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
111
+ monkeypatch.setenv("OPENAI_API_KEY", "secret")
112
+ assert "OPENAI_API_KEY" not in build_oauth_environment()
113
+
114
+
115
+ def test_library_validates_square_size_and_png_destination(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
116
+ monkeypatch.delenv("OPENAI_API_KEY", raising=False)
117
+
118
+ with pytest.raises(ImagegenError, match="size exceeds"):
119
+ generate_image("prompt", "openai-api", ImageSize(1028, 1024), tmp_path / "artifact.png", "forbid", False)
120
+ with pytest.raises(ImagegenError, match=".png"):
121
+ generate_image("prompt", "openai-api", ImageSize(2880, 2880), tmp_path / "artifact.jpg", "forbid", False)
122
+
123
+
124
+ def test_http_provider_image_url_is_rejected() -> None:
125
+ with pytest.raises(ImagegenError, match="HTTPS"):
126
+ download_https_image("http://example.invalid/image.png")
127
+
128
+
129
+ def test_provider_image_url_redirects_are_rejected() -> None:
130
+ handler = imagegen_core._RejectRedirectHandler()
131
+ with pytest.raises(ImagegenError, match="redirects"):
132
+ handler.redirect_request(None, None, 302, "Found", {}, "https://example.invalid/other.png")
133
+
134
+
135
+ def test_non_png_provider_bytes_are_rejected() -> None:
136
+ destination = BytesIO()
137
+ Image.new("RGB", (16, 16), "purple").save(destination, format="JPEG")
138
+
139
+ with pytest.raises(ImagegenError, match="PNG"):
140
+ decode_image(destination.getvalue())
141
+
142
+
143
+ @pytest.mark.parametrize("artifact_names", ((), ("one.png", "two.png")))
144
+ def test_missing_or_multiple_artifacts_fail(tmp_path: Path, artifact_names: tuple[str, ...]) -> None:
145
+ def runner(_arguments: Sequence[str], work_directory: Path, _environment: Mapping[str, str]) -> None:
146
+ for each_artifact_name in artifact_names:
147
+ (work_directory / each_artifact_name).write_bytes(make_png((1254, 1254)))
148
+
149
+ with pytest.raises(ImagegenError, match="exactly one"):
150
+ generate_image("prompt", "codex-oauth", ImageSize(2880, 2880), tmp_path / "artifact.png", "allow", False, runner=runner)
151
+
152
+
153
+ def test_mismatched_aspect_ratio_is_rejected(tmp_path: Path) -> None:
154
+ def runner(_arguments: Sequence[str], work_directory: Path, _environment: Mapping[str, str]) -> None:
155
+ (work_directory / "generated.png").write_bytes(make_png((1254, 1000)))
156
+
157
+ with pytest.raises(ImagegenError, match="square"):
158
+ generate_image("prompt", "codex-oauth", ImageSize(2880, 2880), tmp_path / "artifact.png", "allow", False, runner=runner)
159
+
160
+
161
+ def test_transparency_survives_native_publication(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
162
+ destination = BytesIO()
163
+ Image.new("RGBA", (16, 16), (10, 20, 30, 0)).save(destination, format="PNG")
164
+ monkeypatch.setenv("OPENAI_API_KEY", "present")
165
+ provider_payload = b'{"data":[{"b64_json":"' + base64.b64encode(destination.getvalue()) + b'"}]}'
166
+ generate_image("transparent", "openai-api", ImageSize(16, 16), tmp_path / "transparent.png", "allow", False, transport=lambda _url, _body, _headers: provider_payload)
167
+ with Image.open(tmp_path / "transparent.png") as image:
168
+ assert image.mode == "RGBA"
169
+ assert image.getpixel((0, 0)) == (10, 20, 30, 0)
170
+
171
+
172
+ def test_overwrite_refusal_and_cleanup(tmp_path: Path) -> None:
173
+ output_path = tmp_path / "existing.png"
174
+ output_path.write_bytes(make_png((16, 16)))
175
+ with pytest.raises(ImagegenError, match="already exists"):
176
+ publish_artifact(output_path, output_path.read_bytes(), {"transformation": "native"}, False)
177
+ assert not list(tmp_path.glob("*.tmp"))
178
+
179
+
180
+ def test_publication_rolls_back_when_receipt_replace_fails(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
181
+ destination_path = tmp_path / "artifact.png"
182
+ original_replace = imagegen_core.os.replace
183
+ replacement_count = 0
184
+
185
+ def replace_with_failure(source: Path, target: Path) -> None:
186
+ nonlocal replacement_count
187
+ replacement_count += 1
188
+ if replacement_count == 2:
189
+ raise OSError("receipt replacement failed")
190
+ original_replace(source, target)
191
+
192
+ monkeypatch.setattr(imagegen_core.os, "replace", replace_with_failure)
193
+ with pytest.raises(ImagegenError, match="publication failed"):
194
+ publish_artifact(destination_path, make_png((16, 16)), {"transformation": "native"}, False)
195
+ assert not destination_path.exists()
196
+ assert not destination_path.with_suffix(".json").exists()
197
+ assert not list(tmp_path.glob("*.tmp"))
198
+
199
+
200
+ def test_resize_and_decode_have_expected_hashes() -> None:
201
+ source_bytes = make_png((1254, 1254))
202
+ final_bytes = resize_image(source_bytes, ImageSize(2880, 2880))
203
+ assert decode_image(final_bytes) == ImageSize(2880, 2880)
204
+ assert hashlib.sha256(source_bytes).hexdigest() != hashlib.sha256(final_bytes).hexdigest()
@@ -232,7 +232,7 @@ workflow resume is available.
232
232
  | Work | Agent type | Model |
233
233
  |---|---|---|
234
234
  | Feature, bug, and refactor coding | `clean-coder` | `sonnet` on a Claude host; the sonnet-equivalent id the worker-model resolver prints on a third-party host |
235
- | Verification passes | `code-verifier` | `sonnet` on a Claude host; the sonnet-equivalent id the worker-model resolver prints on a third-party host |
235
+ | Review and verification | `code-quality-agent` | `sonnet` on a Claude host; the sonnet-equivalent id the worker-model resolver prints on a third-party host |
236
236
  | Script runs, GitHub posting, and backfill driving | `general-purpose` runner | `sonnet` on a Claude host; the sonnet-equivalent id the worker-model resolver prints on a third-party host |
237
237
  | PR descriptions | `pr-description-writer` | `haiku`, with file-list grounding check |
238
238
  | Fan-out searches and checklist verification reads | `Explore` | `haiku`; use `sonnet` when judgment-heavy |
@@ -265,8 +265,7 @@ Routing rules:
265
265
  system, with no second one.
266
266
  - Resume a warm workflow agent before creating a new workflow run when
267
267
  the warm agent holds the relevant context.
268
- - `clean-coder` owns code edits. `code-verifier` owns verification. The
269
- same workflow agent never grades work it wrote.
268
+ - Review and verification workflows apply the [review guide](../reviews/SKILL.md#review-workflow).
270
269
  - PR-description workflows include the actual changed-file list in the
271
270
  prompt and verify the final body against that file list before posting
272
271
  or returning it.
@@ -363,4 +362,3 @@ no open work remains, run `set --status done` before any re-arm attempt.
363
362
  ## File-backed run ledger
364
363
 
365
364
  When host task tools are absent, reconcile delegated work through `scripts/grok_run_ledger.py` under the run-state directory (stable task ids, one live owner, unique advisor sessions, dependency blocking, snapshot-drift reopening).
366
-
@@ -57,8 +57,7 @@ Refuse the first matching case and stop:
57
57
  - A requested change spans files outside the packet's allowed file set.
58
58
  - A task contains more than one deliverable, acceptance check, or commit.
59
59
  - A required task tool, verifier, model tier, advisor, review path, or gate is unavailable.
60
- - The user asks to bypass verification, `verified_commit_gate`, independent
61
- review, final validation, or a publication gate.
60
+ - The user asks to bypass verification, review under the [review guide](../reviews/SKILL.md#review-workflow), final validation, or a publication gate.
62
61
 
63
62
  Return exactly: `Plan-to-PR blocked: <missing input or capability>.`
64
63
 
@@ -105,8 +104,8 @@ For each seeded task:
105
104
  1. The orchestrator issues the standalone task ticket.
106
105
  2. One worker implements one deliverable within the ticket's allowed files.
107
106
  3. The worker runs the one acceptance check and reports exact output and blockers.
108
- 4. A fresh verifier checks the task diff, baseline, named gates, and ticket-to-diff
109
- scope. It must pass `verified_commit_gate` for the exact surface.
107
+ 4. Review and verify the task diff, baseline, named checks, and ticket-to-diff
108
+ scope under the [review guide](../reviews/SKILL.md#review-workflow).
110
109
  5. The orchestrator creates exactly one commit and records its hash.
111
110
 
112
111
  No task borrows files, acceptance checks, or commit history from another task.
@@ -114,7 +113,7 @@ Unrelated changes remain untouched and uncommitted.
114
113
 
115
114
  ## Review and repair
116
115
 
117
- Before every commit, require fresh verification and `verified_commit_gate`.
116
+ Before every commit, record fresh verification and review under the [review guide](../reviews/SKILL.md#review-workflow).
118
117
  After the commit, a separate fast low-effort Luna review worker runs native
119
118
  findings-only correctness review at `/e-code-review low`. The review returns
120
119
  findings only and has no repair flag.
@@ -128,8 +127,8 @@ gate after cleanup.
128
127
  ## Final validation and publication
129
128
 
130
129
  The Luna max final validator maps every commit to one packet task and checks
131
- cumulative behavior, the allowed-file ledger, acceptance checks, verifier
132
- output, `verified_commit_gate` evidence, and post-commit review records. Any
130
+ cumulative behavior, the allowed-file ledger, acceptance checks, verification
131
+ output, and post-commit review records. Any
133
132
  unmapped commit, missing record, failed gate, or unresolved finding blocks
134
133
  publication.
135
134
 
@@ -7,7 +7,7 @@ PASS, FAIL plus file and line evidence, or N/A plus a reason.
7
7
  2. Confirm every task record has required identity, files, acceptance, and commit fields.
8
8
  3. Confirm every committed task has native findings-only review and separate repair records.
9
9
  4. Confirm every repair has acceptance output, fresh exact-surface verification, and an amended commit before the next review.
10
- 5. Confirm all review records are clean and all verifier records pass.
10
+ 5. Confirm all review records are clean and all verification records are present.
11
11
  6. Confirm the complete allowed-file ledger contains no extra changed files.
12
12
  7. Confirm final validation and workflow self-audit evidence are present before release.
13
13
  8. Confirm the finalized PR branch receives Luna max `/e-simplify` cleanup with
@@ -11,10 +11,10 @@ judgment remains in the hub or companion reference.
11
11
  | Seed implementation work after packet approval | deterministic | `task-seed:reference/task-seeds.md` | Host task IDs, packet path, and evidence | task-tool |
12
12
  | Record task fields | deterministic | `reference/run-record.schema.json` and `scripts/validate_protocol.py` | Validator output and exit code | `scripts/test_validate_protocol.py` |
13
13
  | Implement one deliverable | judgment | `SKILL.md` | Worker report and diff | N/A: implementation choice |
14
- | Verify exact surface | deterministic | `task-seed:reference/task-seeds.md` | Verifier output and `verified_commit_gate` | task-tool |
14
+ | Verify exact surface | deterministic | `task-seed:reference/task-seeds.md` | Verification output and [review guide](../../reviews/SKILL.md#review-workflow) | task-tool |
15
15
  | Review committed task | deterministic | `reference/review-loop.md` | Findings-only native review record | `test_task_ticket_contract.py` |
16
16
  | Repair confirmed findings | deterministic | `reference/review-loop.md` | Separate repair record and confirmed findings | `test_task_ticket_contract.py` |
17
- | Decide whether a finding is confirmed | judgment | `reference/review-loop.md` | Repair record names the confirmed finding | N/A: review judgment |
17
+ | Decide whether a finding is confirmed | judgment | [review guide](../../reviews/SKILL.md#review-judgment) | Repair record names the confirmed finding | N/A: review judgment |
18
18
  | Repeat until clean | borderline | `reference/review-loop.md` | Amended commit and clean review record | `test_task_ticket_contract.py` |
19
19
  | Validate cumulative release | deterministic | `task-seed:reference/final-validation-tasks.md` | Final validator task evidence | task-tool |
20
20
  | Self-audit the workflow | deterministic | `task-seed:reference/self-audit-tasks.md` | Self-audit task evidence | task-tool |
@@ -6,7 +6,7 @@ Run this loop for every committed task. Store each pass in the task record.
6
6
  2. It invokes the native findings-only `/e-code-review low` correctness
7
7
  capability; the review has no repair flag.
8
8
  3. Record resolved model, effort, command, findings, repair status, and surface
9
- hash. Missing native review or verifier capability fails closed.
9
+ hash. Apply the [review guide](../../reviews/SKILL.md#review-workflow) to every finding.
10
10
  4. A separate fast low-effort Luna repair worker applies only confirmed findings.
11
11
  5. Rerun the task acceptance check and fresh exact-surface verification.
12
12
  6. Amend the task commit and repeat the native review until clean.
@@ -14,7 +14,7 @@ Run this loop for every committed task. Store each pass in the task record.
14
14
  ## Accepted records
15
15
 
16
16
  Clean means the native review reports no findings for the exact committed
17
- surface. A repaired pass is not accepted until acceptance and fresh verifier
17
+ surface. A repaired pass is not accepted until acceptance and fresh verification
18
18
  output are recorded against the amended commit.
19
19
 
20
20
  ## Example
@@ -22,6 +22,6 @@
22
22
  "finding": {"type": "object", "additionalProperties": false, "required": ["finding", "disposition"], "properties": {"finding": {"type": "string", "minLength": 1}, "disposition": {"enum": ["confirmed", "dismissed"]}}},
23
23
  "review_record": {"type": "object", "additionalProperties": false, "required": ["resolved_model", "effort", "command", "findings", "repair_status", "surface_hash", "findings_only", "has_repair_flag"], "properties": {"resolved_model": {"type": "string", "minLength": 1}, "effort": {"type": "string", "minLength": 1}, "command": {"type": "string", "minLength": 1}, "findings": {"type": "array", "items": {"$ref": "#/$defs/finding"}}, "repair_status": {"type": "string", "minLength": 1}, "surface_hash": {"type": "string", "minLength": 1}, "findings_only": {"const": true}, "has_repair_flag": {"const": false}}},
24
24
  "repair_record": {"type": "object", "additionalProperties": false, "required": ["resolved_model", "effort", "confirmed_findings", "repair_status", "surface_hash"], "properties": {"resolved_model": {"type": "string", "minLength": 1}, "effort": {"type": "string", "minLength": 1}, "confirmed_findings": {"type": "array", "items": {"type": "string", "minLength": 1}}, "repair_status": {"type": "string", "minLength": 1}, "surface_hash": {"type": "string", "minLength": 1}}},
25
- "verification_record": {"type": "object", "additionalProperties": false, "required": ["acceptance_output", "verifier_output", "verified_commit_gate", "surface_hash"], "properties": {"acceptance_output": {"type": "string", "minLength": 1}, "verifier_output": {"type": "string", "minLength": 1}, "verified_commit_gate": {"type": "string", "minLength": 1}, "surface_hash": {"type": "string", "minLength": 1}}}
25
+ "verification_record": {"type": "object", "additionalProperties": false, "required": ["acceptance_output", "verification_output", "surface_hash"], "properties": {"acceptance_output": {"type": "string", "minLength": 1}, "verification_output": {"type": "string", "minLength": 1}, "surface_hash": {"type": "string", "minLength": 1}}}
26
26
  }
27
27
  }
@@ -7,7 +7,7 @@ equivalent before implementation. Mark it complete only with evidence.
7
7
  2. Capture the baseline and create one task record from the run-record schema.
8
8
  3. Implement the single deliverable within the allowed file set.
9
9
  4. Run the acceptance check and record its exact output.
10
- 5. Run fresh exact-surface verification and `verified_commit_gate`.
10
+ 5. Run fresh exact-surface verification and apply the [review guide](../../reviews/SKILL.md#review-workflow).
11
11
  6. Create exactly one task commit and record its identity.
12
12
  7. Run the separate native review and record findings-only output.
13
13
  8. Run separate Luna repair only for confirmed findings; rerun acceptance and
@@ -17,13 +17,12 @@ commits.
17
17
  | Worker route | The model role and selected route from `reference/model-routing.md`; planner and final-validator records include Luna max plus Sol xhigh advisor evidence, and orchestrator records the max route |
18
18
  | Commit record | Exactly one commit hash for this ticket |
19
19
  | Review record | Separate fast low-effort Luna review output that invokes native `/e-code-review low` and returns findings only, followed by separate fast low-effort Luna repair output, including resolved model, effort, command, findings, repair status, and surface hash |
20
- | Verification record | Fresh verifier output and `verified_commit_gate` result for the exact surface |
20
+ | Verification record | Fresh verification output for the exact surface and review under the [review guide](../../reviews/SKILL.md#review-workflow) |
21
21
 
22
22
  ## Execution contract
23
23
 
24
24
  The worker changes only the allowed files, runs the acceptance check, and reports
25
- the exact output. A fresh verifier checks the ticket against the diff before one
26
- commit is created. The commit hash is recorded before the separate review pass.
25
+ the exact output. Review and verification follow the [review guide](../../reviews/SKILL.md#review-workflow) before one commit is created. The commit hash is recorded before the separate review pass.
27
26
 
28
27
  ## Review and completion contract
29
28
 
@@ -33,6 +32,6 @@ repair flag. A separate fast low-effort Luna repair worker applies only confirme
33
32
  the resolved model, effort, command, findings, repair status, and surface hash.
34
33
  Confirmed repairs require the acceptance check and fresh exact-surface verification
35
34
  again; amend the task commit and repeat the native review until clean. Missing
36
- native review or required verifier capability fails closed. The
35
+ native review fails closed. The
37
36
  final validator maps the commit to this ticket and rejects missing or extra
38
37
  records.
@@ -174,8 +174,7 @@ ALL_REPAIR_FIELDS: tuple[str, ...] = (
174
174
  )
175
175
  ALL_VERIFICATION_FIELDS: tuple[str, ...] = (
176
176
  "acceptance_output",
177
- "verifier_output",
178
- "verified_commit_gate",
177
+ "verification_output",
179
178
  "surface_hash",
180
179
  )
181
180
  ALL_REVIEW_TEXT_FIELDS: tuple[str, ...] = (
@@ -20,7 +20,7 @@ validate_record = VALIDATE_PROTOCOL_MODULE.validate_record
20
20
 
21
21
 
22
22
  def valid_record() -> dict[str, object]:
23
- verification = {"acceptance_output": "passed: pytest", "verifier_output": "passed: clean", "verified_commit_gate": "passed: exact surface", "surface_hash": "a" * 64}
23
+ verification = {"acceptance_output": "passed: pytest", "verification_output": "passed: clean", "surface_hash": "a" * 64}
24
24
  return {
25
25
  "task_identity": "task-3", "deliverable": "validator", "allowed_files": ["validator.py"],
26
26
  "acceptance_check": "pytest", "baseline": "clean", "worker_route": "implementation worker; effort=low", "commit": "a1b2c3d",
@@ -90,7 +90,7 @@ def test_skill_contract_enforces_task_commit_and_review_order() -> None:
90
90
  skill_text = read_skill_text()
91
91
 
92
92
  assert EXPECTED_TASK_PROTOCOL_HEADING in skill_text
93
- assert "fresh verification and `verified_commit_gate`" in skill_text
93
+ assert "fresh verification and review under the [review guide]" in skill_text
94
94
  assert (
95
95
  "native\nfindings-only correctness review at `/e-code-review low`" in skill_text
96
96
  )
@@ -163,7 +163,7 @@ def test_skill_contract_references_future_fixed_artifacts_without_copying_tables
163
163
 
164
164
  def test_skill_contract_companion_reference_paths_exist() -> None:
165
165
  skill_text = read_skill_text()
166
- local_reference_paths = re.findall(r"\]\(([^)]+)\)", skill_text)
166
+ local_reference_paths = [each_path.split("#", maxsplit=1)[0] for each_path in re.findall(r"\]\(([^)]+)\)", skill_text)]
167
167
  repository_paths = [
168
168
  each_path
169
169
  for each_path in local_reference_paths
@@ -168,9 +168,8 @@ when `$CLAUDE_JOB_DIR` is gone.
168
168
 
169
169
  Fields: `phase`, `tick_count`, `bugbot_clean_at`, `code_review_clean_at`,
170
170
  `bugteam_clean_at`, `copilot_clean_at`, `merge_state_status`, `current_head`,
171
- `bugbot_acknowledged_at`, `bugbot_down`, `copilot_down`,
172
- `bugteam_skill_invoked_at_head`, `bugteam_skill_invoked_at_tick`,
173
- `agents_session_id`, `persistent_agents`.
171
+ `bugbot_acknowledged_at`, `bugbot_down`, `copilot_down`, `agents_session_id`,
172
+ `persistent_agents`.
174
173
 
175
174
  ## Persistent per-step agents
176
175
 
@@ -190,19 +189,17 @@ the `persistent_agents` map
190
189
  resolver-supplied sonnet-equivalent on third-party hosts. The `name`
191
190
  makes the agent a persistent teammate
192
191
  that idles awaiting messages. Record `{agent_id, created_tick,
193
- last_used_tick}` under the step key. Keep the spawn prompt fix-shaped,
194
- never audit-shaped: the `pr_converge_bugteam_enforcer` hook blocks
195
- audit-shaped clean-coder spawns during the BUGTEAM phase.
192
+ last_used_tick}` under the step key. Keep the spawn prompt focused on the
193
+ current fix. Step 6 sends audit judgment through the
194
+ [review guide](../reviews/SKILL.md#review-workflow).
196
195
  - **Stale or dead id:** on a `SendMessage` failure, or no acknowledgment
197
196
  within one bounded wait, drop the map entry, spawn a fresh named agent,
198
197
  record it, and continue the tick. Never abort a tick on a stale id;
199
198
  never retry the same dead id.
200
199
  - **Fresh every round (never persisted):** the Step 5 host-aware
201
200
  `invoke_code_review.py` / `/code-review ultra --fix` pass and the Step 6
202
- bugteam audit (unbiased eyes each round; the enforcer needs the formal
203
- Skill call), and every `code-verifier` — a named code-verifier never fires
204
- `SubagentStop`, so no verdict mints (see the named-`code-verifier` entry
205
- in the Gotchas list below).
201
+ bugteam audit. Apply the [review guide](../reviews/SKILL.md#review-workflow)
202
+ to each review pass.
206
203
  - **Shutdown:** at loop end (convergence or a stop condition), send each
207
204
  persistent agent a shutdown request and clear `persistent_agents` before
208
205
  the `pr-loop-lifecycle` Close.
@@ -263,7 +260,6 @@ post a fresh PR in a fresh branch based on origin main to the user.
263
260
  [Step 1.5](reference/per-tick.md). Skipping this reviews and edits the
264
261
  wrong repo. The route is routine and automatic — never a material fork
265
262
  to pause on.
266
- - **A named/teammate `code-verifier` never mints a verdict** — In a background-job session, an `Agent`-tool `code-verifier` spawned with a `name` (or otherwise as a persistent teammate) goes idle/"available" awaiting messages rather than terminating, so its `SubagentStop` never fires. `verifier_verdict_minter.py` mints the verdict only on `SubagentStop`, so no verdict file is written and `verified_commit_gate` blocks the `git commit`/`git push` with "no passing verification verdict" even though the verifier emitted `all_pass`. A `shutdown_request` is ignored and `TaskStop` cannot resolve the teammate's id. Spawn the code-verifier as a one-shot agent with NO `name` (a plain async `Agent` call) so it runs to completion and fires `SubagentStop`, minting the verdict bound to the live surface. Keep the work tree frozen between verification and the commit so the minted surface hash still matches.
267
263
 
268
264
  ## Progress checklist
269
265
 
@@ -333,7 +329,7 @@ round as converged. This rule holds every tick, every loop, every PR.
333
329
  - [ ] **clean on `current_head`** → zero unresolved threads (else fix + resolve first)
334
330
  → `bugbot_clean_at = current_head` → Step 7
335
331
  - [ ] **no review / commit_id mismatch** → `reviewer-gates` Bugbot flow (Gate 3):
336
- silent pass → stamp + Step 7; queued/triggered → ScheduleWakeup 360s or portable in-session poll → Step 4;
332
+ silent pass → Step 7; queued/triggered → ScheduleWakeup 360s or portable in-session poll → Step 4;
337
333
  down → `bugbot_down = true` → Step 7
338
334
 
339
335
  - [ ] **Step 5: CODE-REVIEW — static sweep, review, fix, advance**
@@ -373,9 +369,10 @@ round as converged. This rule holds every tick, every loop, every PR.
373
369
  See: [`reference/per-tick.md` § Step 2 BUGTEAM](reference/per-tick.md);
374
370
  [`../bugteam/SKILL.md`](../bugteam/SKILL.md).
375
371
  Pre-condition: `code_review_clean_at == current_head`.
376
- Mandatory: `Skill({skill: "bugteam", args: "<PR URL>"})` this tick
377
- (enforcer-blocked otherwise; `qbug` is not a substitute). Scope: FULL
378
- `origin/main...HEAD` diff. Re-resolve HEAD after bugteam.
372
+ Mandatory: `Skill({skill: "bugteam", args: "<PR URL>"})` this tick.
373
+ Use the [review guide](../reviews/SKILL.md#review-workflow) for its
374
+ judgment. `qbug` is not a substitute. Scope: FULL `origin/main...HEAD`
375
+ diff. Re-resolve HEAD after bugteam.
379
376
 
380
377
  - [ ] **bugteam pushed** → verify threads replied + resolved → reset markers
381
378
  → `phase = CODE_REVIEW` → ScheduleWakeup 360s or portable in-session poll → Step 5
@@ -392,7 +389,7 @@ round as converged. This rule holds every tick, every loop, every PR.
392
389
  before these gates run. Count unresolved threads before each gate.
393
390
  Every gate records evidence; gate (f) cites evidence from (a)–(e).
394
391
 
395
- - [ ] **(a) Copilot findings** — fetch Copilot on `current_head`; dirty → fix + return to Step 5; clean → stamp `copilot_clean_at`; absent → continue; when `copilot_down`, skip
392
+ - [ ] **(a) Copilot findings** — fetch Copilot on `current_head`; dirty → fix + return to Step 5; clean → record `copilot_clean_at`; absent → continue; when `copilot_down`, skip
396
393
  - [ ] **(b) Claude reviewer** — fetch Claude on `current_head`; dirty → fix + return to Step 5; clean or absent → continue
397
394
  - [ ] **(c) Mergeability** — `mergeable_state == "clean"` and `mergeable == true`; dirty → rebase + return to Step 1; blocked/behind/unknown/unstable → hard blocker
398
395
  - [ ] **(d) Post-convergence Copilot request** — request Copilot when not pending and not `copilot_down`; enter `COPILOT_WAIT` (Step 7a); when `copilot_down`, skip to (e)
@@ -70,7 +70,7 @@ round as converged. This rule holds every tick, every loop, every PR.
70
70
  - [ ] **clean on `current_head`** → zero unresolved threads (else fix + resolve first)
71
71
  → `bugbot_clean_at = current_head` → Step 7
72
72
  - [ ] **no review / commit_id mismatch** → `reviewer-gates` Bugbot flow (Gate 3):
73
- silent pass → stamp + Step 7; queued/triggered → 360s wakeup → Step 4;
73
+ silent pass → Step 7; queued/triggered → 360s wakeup → Step 4;
74
74
  down → `bugbot_down = true` → Step 7
75
75
 
76
76
  - [ ] **Step 5: CODE-REVIEW — static sweep, review, fix, advance**
@@ -110,9 +110,10 @@ round as converged. This rule holds every tick, every loop, every PR.
110
110
  See: [`per-tick.md` § Step 2 BUGTEAM](per-tick.md);
111
111
  [`../../bugteam/SKILL.md`](../../bugteam/SKILL.md).
112
112
  Pre-condition: `code_review_clean_at == current_head`.
113
- Mandatory: `Skill({skill: "bugteam", args: "<PR URL>"})` this tick
114
- (enforcer-blocked otherwise; `qbug` is not a substitute). Scope: FULL
115
- `origin/main...HEAD` diff. Re-resolve HEAD after bugteam.
113
+ Mandatory: `Skill({skill: "bugteam", args: "<PR URL>"})` this tick.
114
+ Use the [review guide](../../reviews/SKILL.md#review-workflow) for its
115
+ judgment. `qbug` is not a substitute. Scope: FULL `origin/main...HEAD`
116
+ diff. Re-resolve HEAD after bugteam.
116
117
 
117
118
  - [ ] **bugteam pushed** → verify threads replied + resolved → reset markers
118
119
  → `phase = CODE_REVIEW` → 360s wakeup → Step 5
@@ -129,7 +130,7 @@ round as converged. This rule holds every tick, every loop, every PR.
129
130
  before these gates run. Count unresolved threads before each gate.
130
131
  Every gate records evidence; gate (f) cites evidence from (a)–(e).
131
132
 
132
- - [ ] **(a) Copilot findings** — fetch Copilot on `current_head`; dirty → fix + return to Step 5; clean → stamp `copilot_clean_at`; absent → continue; when `copilot_down`, skip
133
+ - [ ] **(a) Copilot findings** — fetch Copilot on `current_head`; dirty → fix + return to Step 5; clean → record `copilot_clean_at`; absent → continue; when `copilot_down`, skip
133
134
  - [ ] **(b) Claude reviewer** — fetch Claude on `current_head`; dirty → fix + return to Step 5; clean or absent → continue
134
135
  - [ ] **(c) Mergeability** — `mergeable_state == "clean"` and `mergeable == true`; dirty → rebase + return to Step 1; blocked/behind/unknown/unstable → hard blocker
135
136
  - [ ] **(d) Post-convergence Copilot request** — request Copilot when not pending and not `copilot_down`; enter `COPILOT_WAIT` (Step 7a); when `copilot_down`, skip to (e)
@@ -13,10 +13,7 @@ Single-PR `/pr-converge` writes loop state to
13
13
  `$CLAUDE_JOB_DIR/pr-converge-state.json`; that file is the source of truth
14
14
  for `phase`, heads, counters, status. Multi-PR mode additionally maintains
15
15
  `<TMPDIR>/pr-converge-<session_id>/state.json` for orchestrator coordination
16
- across PRs. Both files share most of the fields below; the
17
- `bugteam_skill_invoked_at_head` and `bugteam_skill_invoked_at_tick` fields
18
- live ONLY in the single-PR `$CLAUDE_JOB_DIR/pr-converge-state.json` file
19
- (see those field entries below for details).
16
+ across PRs. Both files share the fields below.
20
17
 
21
18
  ## Fields
22
19
 
@@ -94,29 +91,6 @@ live ONLY in the single-PR `$CLAUDE_JOB_DIR/pr-converge-state.json` file
94
91
  (c) reads this field to decide between "schedule next wakeup" and
95
92
  "escalate to bugbot-down".
96
93
  - `tick_count`: integer, init `0`. Increment every tick.
97
- - `bugteam_skill_invoked_at_head`: HEAD SHA (string) at which the formal
98
- `Skill({skill: "bugteam"})` was last invoked, or `null`. Stamped by the
99
- `pr_converge_bugteam_skill_tracker` hook on every formal bugteam Skill
100
- invocation. **On-disk location:** the tracker writes this field to
101
- `$CLAUDE_JOB_DIR/pr-converge-state.json` (single-PR mode); it is NOT
102
- mirrored into the multi-PR `<TMPDIR>/pr-converge-<session_id>/state.json`
103
- file. Operators inspecting these stamps must read the single-PR
104
- `pr-converge-state.json` under `$CLAUDE_JOB_DIR`. Reset by overwrite on
105
- the next bugteam Skill invocation; staleness is detected by the head/tick
106
- equality check rather than by explicit reset. The
107
- `pr_converge_bugteam_enforcer` hook reads this field together with
108
- `current_head` to confirm the formal Skill registered at the current HEAD
109
- before allowing follow-on clean-coder audit-shaped Agent spawns. `qbug`
110
- invocations deliberately do NOT update this field.
111
- - `bugteam_skill_invoked_at_tick`: integer tick number at which the formal
112
- bugteam Skill was last invoked, or `null`. Companion to
113
- `bugteam_skill_invoked_at_head` and persisted to the same
114
- `$CLAUDE_JOB_DIR/pr-converge-state.json` file (single-PR mode only).
115
- Reset by overwrite on the next bugteam Skill invocation; staleness is
116
- detected by the head/tick equality check rather than by explicit reset.
117
- The enforcer requires this value to equal the current `tick_count` so a
118
- Skill invocation from a prior tick cannot wave through clean-coder
119
- audit-shaped Agent spawns on a later tick at the same HEAD.
120
94
  - `agents_session_id`: string or `null`, init `null`. The session id that
121
95
  spawned the persistent per-step agents recorded in `persistent_agents`.
122
96
  On tick entry, compare it to the current session id: when they differ,
@@ -89,7 +89,7 @@ Skip the `gh api copilot_internal/user` quota read; treat Copilot quota as unkno
89
89
  The `gh`-text hooks read risk from literal `gh ...` command text, and an MCP post carries none, so those checks go quiet in cloud. Before each MCP post, check by hand:
90
90
 
91
91
  - No volatile scratch path in a post body (job dirs, temp roots, worktrees).
92
- - A proof-of-work comment carries all five parts the proof standard names.
92
+ - Review comments follow the [comment guide](../comments/SKILL.md#writing-useful-review-comments).
93
93
  - A PR title follows Conventional Commits.
94
94
  - Markdown bodies go through the structured `body` parameter so backticks show as formatting.
95
95
 
@@ -26,14 +26,14 @@ Author-keyed logic accounts for the split: a self-PR check keys on the login tha
26
26
 
27
27
  ## Hooks that still gate a cloud run
28
28
 
29
- The commit and push gates read git commands, which a cloud run still issues through Bash, so they fire normally and the run follows them: `verified_commit_gate`, `session_edit_stage_gate`, `block_main_commit`, `precommit_code_rules_gate`, `test_preflight_check`, and the pre-push base-ref check that needs `origin/HEAD` set (SKILL.md Step 2).
29
+ The commit and push gates read git commands, which a cloud run still issues through Bash, so they fire normally and the run follows them: `session_edit_stage_gate`, `block_main_commit`, `precommit_code_rules_gate`, `test_preflight_check`, and the pre-push base-ref check that needs `origin/HEAD` set (SKILL.md Step 2).
30
30
 
31
31
  ## Hooks that go quiet on the MCP path
32
32
 
33
- The `gh`-text hooks read risk from literal `gh ...` command text: `pr_description_enforcer`, `gh_body_arg_blocker`, `conventional_pr_title_gate`, `gh_pr_author_enforcer`, the `gh` branch of `volatile_path_in_post_blocker`, and the `gh pr ready` branch of `convergence_gate_blocker`. An MCP post carries no `gh` text, so these checks see nothing to gate while the settings matchers name only the local tool prefix. The run covers the gap by hand before each MCP post — the Step 6 self-check in SKILL.md:
33
+ The `gh`-text hooks read risk from literal `gh ...` command text: `gh_body_arg_blocker`, `conventional_pr_title_gate`, `gh_pr_author_enforcer`, and the `gh` branch of `volatile_path_in_post_blocker`. An MCP post carries no `gh` text, so these checks see nothing to gate while the settings matchers name only the local tool prefix. The run covers the gap by hand before each MCP post — the Step 6 self-check in SKILL.md:
34
34
 
35
35
  - No volatile scratch path in a post body (job dirs, temp roots, worktrees).
36
- - A proof-of-work comment carries all five parts the proof standard names.
36
+ - Review comments follow the [review guide](../../reviews/SKILL.md#review-workflow).
37
37
  - A PR title follows Conventional Commits.
38
38
  - Markdown bodies go through the structured `body` parameter so backticks show as formatting.
39
39
 
@@ -44,7 +44,7 @@ Run these in order from the lead session on EVERY exit — converged, cap reache
44
44
  ```
45
45
  Tolerates already-removed worktrees and missing directories; removal is Windows-safe per `~/.claude/rules/windows-filesystem-safe.md`.
46
46
  3. **Clean the working tree.** Return to the session worktree, remove run-scoped scratch files, and leave `git status` clean of run artifacts.
47
- 4. **Rewrite the PR description.** Follow [`reference/teardown-publish-permissions.md` § Publish the final PR description](reference/teardown-publish-permissions.md): capture the cumulative diff and original body, compose the new body directly against `docs/PR_DESCRIPTION_GUIDE.md`, publish via `update_pull_request`, remove the scratch files. On failure, report it and continue — the revoke still runs.
47
+ 4. **Update the PR description.** Follow [`reference/teardown-publish-permissions.md` § Publish the final PR description](reference/teardown-publish-permissions.md): capture the cumulative diff and original body, compose it against the [description guide](../descriptions/SKILL.md#required-content), publish via `update_pull_request`, then remove the scratch files.
48
48
  5. **Revoke project permissions (always):**
49
49
  `python "$HOME/.claude/_shared/pr-loop/scripts/revoke_project_claude_permissions.py"`
50
50
  Non-negotiable, including on error exits: leaving the grant in place lets future sessions inherit elevated `.claude/**` access without an explicit opt-in. Run revoke even when earlier close steps partially failed; log cleanup errors separately.
@@ -35,7 +35,7 @@ When the run exits (any reason), run these steps in order from **this** session
35
35
 
36
36
  After teardown and before permission revoke, the lead rewrites the PR body to the PR's **final cumulative state** — what the change delivers, not the loop's process. This is the **only** PR-write the lead performs (audit and fix comments stay with the agents that produced them).
37
37
 
38
- The lead composes the body text directly against `docs/PR_DESCRIPTION_GUIDE.md` no agent spawn — so the body passes the `pr_description_enforcer` hook's style audit on the `gh pr edit` that follows.
38
+ The lead composes the body against the [description guide](../../descriptions/SKILL.md#required-content) and publishes it through the GitHub client.
39
39
 
40
40
  **Steps:**
41
41