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.
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +0 -26
- package/agents/CLAUDE.md +1 -2
- package/agents/pr-description-writer.md +11 -81
- package/bin/install.mjs +48 -9
- package/bin/install.prune.test.mjs +26 -0
- package/bin/install.settings-defaults.test.mjs +60 -33
- package/bin/install.test.mjs +2 -4
- package/commands/sr-loop.md +9 -5
- package/docs/CLAUDE.md +0 -1
- package/docs/references/CLAUDE.md +0 -1
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -27
- package/hooks/blocking/config/CLAUDE.md +1 -13
- package/hooks/blocking/conventional_pr_title_gate.py +1 -2
- package/hooks/blocking/send_user_file_open_locally_blocker.py +1 -1
- package/hooks/blocking/stop_dispatcher.py +4 -4
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -3
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +6 -7
- package/hooks/blocking/test_send_user_file_open_locally_blocker.py +4 -3
- package/hooks/blocking/test_shared_stdin_adoption.py +0 -2
- package/hooks/diagnostic/CLAUDE.md +3 -3
- package/hooks/diagnostic/hook_log_extractor.py +2 -36
- package/hooks/diagnostic/hook_log_stop_wrapper.py +6 -155
- package/hooks/diagnostic/test_hook_log_extractor.py +8 -21
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +3 -331
- package/hooks/git-hooks/git_hooks_constants/__init__.py +0 -6
- package/hooks/git-hooks/pre_push.py +3 -85
- package/hooks/git-hooks/test_pre_push.py +0 -130
- package/hooks/hooks.json +0 -35
- package/hooks/hooks_constants/CLAUDE.md +0 -9
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -11
- package/hooks/hooks_constants/convergence_branch_constants.py +0 -1
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +3 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +0 -8
- package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +1 -6
- package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -1
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -26
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -1
- package/hooks/lifecycle/CLAUDE.md +1 -3
- package/package.json +1 -1
- package/rules/CLAUDE.md +0 -2
- package/rules/gh-cli-conventions.md +0 -1
- package/rules/git-workflow.md +1 -3
- package/scripts/CLAUDE.md +0 -1
- package/scripts/_code_review_test_support.py +0 -95
- package/scripts/dev_env_scripts_constants/code_review_constants.py +9 -98
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +1 -44
- package/scripts/invoke_code_review.py +8 -451
- package/scripts/test_invoke_code_review.py +5 -61
- package/scripts/test_invoke_code_review_cli.py +1 -45
- package/scripts/test_invoke_code_review_contract.py +1 -118
- package/settings.json +1 -10
- package/skills/CLAUDE.md +8 -0
- package/skills/autoconverge/reference/CLAUDE.md +1 -1
- package/skills/autoconverge/reference/convergence.md +7 -7
- package/skills/autoconverge/reference/gotchas.md +3 -10
- package/skills/autoconverge/workflow/converge.contract.test.mjs +53 -1388
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +4 -3
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +33 -493
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +15 -97
- package/skills/autoconverge/workflow/converge.mjs +201 -238
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +1 -1
- package/skills/bugteam/CONSTRAINTS.md +1 -1
- package/skills/bugteam/SKILL.md +9 -0
- package/skills/bugteam/reference/audit-and-teammates.md +10 -6
- package/skills/bugteam/test_skill_additions.py +9 -0
- package/skills/comments/SKILL.md +65 -0
- package/skills/descriptions/SKILL.md +51 -0
- package/skills/emergencies/SKILL.md +42 -0
- package/skills/imagegen/SKILL.md +21 -0
- package/skills/imagegen/scripts/config/__init__.py +1 -0
- package/skills/imagegen/scripts/config/constants.py +28 -0
- package/skills/imagegen/scripts/imagegen.py +45 -0
- package/skills/imagegen/scripts/imagegen_core.py +528 -0
- package/skills/imagegen/scripts/test_imagegen.py +204 -0
- package/skills/orchestrator/SKILL.md +2 -4
- package/skills/plan-to-pr/SKILL.md +6 -7
- package/skills/plan-to-pr/reference/final-validation-tasks.md +1 -1
- package/skills/plan-to-pr/reference/process-inventory.md +2 -2
- package/skills/plan-to-pr/reference/review-loop.md +2 -2
- package/skills/plan-to-pr/reference/run-record.schema.json +1 -1
- package/skills/plan-to-pr/reference/task-seeds.md +1 -1
- package/skills/plan-to-pr/reference/task-ticket.md +3 -4
- package/skills/plan-to-pr/scripts/config/constants.py +1 -2
- package/skills/plan-to-pr/scripts/test_validate_protocol.py +1 -1
- package/skills/plan-to-pr/test_skill_contract.py +2 -2
- package/skills/pr-converge/SKILL.md +13 -16
- package/skills/pr-converge/reference/progress-checklist.md +6 -5
- package/skills/pr-converge/reference/state-schema.md +1 -27
- package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +3 -3
- package/skills/pr-loop-lifecycle/SKILL.md +1 -1
- package/skills/pr-loop-lifecycle/reference/teardown-publish-permissions.md +1 -1
- package/skills/prototype/SKILL.md +4 -4
- package/skills/prototype/reference/honest-limitations.md +4 -4
- package/skills/prototype/reference/promotion-tasks.md +2 -2
- package/skills/prototype/workflows/promotion.md +4 -6
- package/skills/reviews/SKILL.md +85 -0
- package/skills/small-cl/SKILL.md +52 -0
- package/agents/code-verifier.md +0 -74
- package/docs/PR_DESCRIPTION_GUIDE.md +0 -157
- package/docs/references/code-review-enforcement.md +0 -132
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +0 -53
- package/hooks/blocking/code_review_gate_deny.py +0 -74
- package/hooks/blocking/code_review_pr_create_gate.py +0 -198
- package/hooks/blocking/code_review_push_gate.py +0 -145
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +0 -348
- package/hooks/blocking/code_review_stamp_store.py +0 -233
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +0 -7
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +0 -15
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +0 -212
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +0 -138
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +0 -49
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +0 -38
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +0 -483
- package/hooks/blocking/config/code_review_enforcement_constants.py +0 -143
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +0 -166
- package/hooks/blocking/config/verified_commit_constants.py +0 -160
- package/hooks/blocking/config/verified_commit_context_constants.py +0 -21
- package/hooks/blocking/config/verified_commit_gate_output_constants.py +0 -14
- package/hooks/blocking/conftest.py +0 -123
- package/hooks/blocking/convergence_gate_blocker.py +0 -310
- package/hooks/blocking/pr_converge_bugteam_enforcer.py +0 -170
- package/hooks/blocking/pr_description_body_audit.py +0 -148
- package/hooks/blocking/pr_description_command_parser.py +0 -237
- package/hooks/blocking/pr_description_enforcer.py +0 -227
- package/hooks/blocking/pr_description_pr_number.py +0 -155
- package/hooks/blocking/pr_description_proof_of_work.py +0 -385
- package/hooks/blocking/pr_description_readability.py +0 -364
- package/hooks/blocking/reviewer_spawn_gate.py +0 -182
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +0 -62
- package/hooks/blocking/test_code_review_gate_deny.py +0 -54
- package/hooks/blocking/test_code_review_pr_create_gate.py +0 -199
- package/hooks/blocking/test_code_review_push_gate.py +0 -205
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +0 -199
- package/hooks/blocking/test_code_review_stamp_store.py +0 -205
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +0 -710
- package/hooks/blocking/test_code_verifier_tools_contract.py +0 -28
- package/hooks/blocking/test_convergence_gate_blocker.py +0 -428
- package/hooks/blocking/test_pr_converge_bugteam_enforcer.py +0 -311
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +0 -184
- package/hooks/blocking/test_pr_description_enforcer.py +0 -221
- package/hooks/blocking/test_pr_description_enforcer_body_audit.py +0 -247
- package/hooks/blocking/test_pr_description_enforcer_body_rules.py +0 -493
- package/hooks/blocking/test_pr_description_enforcer_command_parser.py +0 -366
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +0 -159
- package/hooks/blocking/test_pr_description_enforcer_proof_gate.py +0 -175
- package/hooks/blocking/test_pr_description_enforcer_readability.py +0 -443
- package/hooks/blocking/test_pr_description_proof_of_work.py +0 -313
- package/hooks/blocking/test_reviewer_spawn_gate.py +0 -230
- package/hooks/blocking/test_verdict_directory_write_blocker.py +0 -804
- package/hooks/blocking/test_verification_verdict_store.py +0 -974
- package/hooks/blocking/test_verified_commit_config_bootstrap.py +0 -67
- package/hooks/blocking/test_verified_commit_docs_delta.py +0 -176
- package/hooks/blocking/test_verified_commit_gate.py +0 -581
- package/hooks/blocking/test_verified_commit_gate_additional_context.py +0 -134
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +0 -131
- package/hooks/blocking/test_verifier_verdict_minter.py +0 -299
- package/hooks/blocking/tests/test_verified_commit_gate.py +0 -41
- package/hooks/blocking/verdict_directory_write_blocker.py +0 -687
- package/hooks/blocking/verification_verdict_store.py +0 -1039
- package/hooks/blocking/verified_commit_config_bootstrap.py +0 -63
- package/hooks/blocking/verified_commit_gate.py +0 -113
- package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +0 -28
- package/hooks/blocking/verified_commit_gate_parts/__init__.py +0 -1
- package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +0 -174
- package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +0 -53
- package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +0 -80
- package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +0 -170
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +0 -217
- package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +0 -10
- package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +0 -94
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +0 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +0 -38
- package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +0 -71
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +0 -96
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +0 -167
- package/hooks/blocking/verifier_verdict_minter.py +0 -280
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +0 -64
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +0 -53
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_constants.py +0 -55
- package/hooks/hooks_constants/pr_converge_bugteam_enforcer_state.py +0 -67
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +0 -167
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -107
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +0 -41
- package/hooks/hooks_constants/test_pr_description_enforcer_constants.py +0 -292
- package/hooks/lifecycle/pr_converge_bugteam_skill_tracker.py +0 -198
- package/hooks/lifecycle/test_pr_converge_bugteam_skill_tracker.py +0 -283
- package/rules/proof-of-work-pr-comments.md +0 -22
- package/rules/verified-commit-gate-skip.md +0 -4
- 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
|
-
|
|
|
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
|
-
-
|
|
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,
|
|
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.
|
|
109
|
-
scope
|
|
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,
|
|
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,
|
|
132
|
-
output,
|
|
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
|
|
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` |
|
|
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 |
|
|
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.
|
|
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
|
|
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", "
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
"
|
|
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", "
|
|
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
|
|
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
|
-
`
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
203
|
-
|
|
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 →
|
|
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
|
-
|
|
378
|
-
`origin/main...HEAD`
|
|
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 →
|
|
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 →
|
|
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
|
-
|
|
115
|
-
`origin/main...HEAD`
|
|
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 →
|
|
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
|
|
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
|
-
-
|
|
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: `
|
|
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: `
|
|
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
|
-
-
|
|
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. **
|
|
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
|
|
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
|
|