claude-dev-env 8.46.13 → 8.46.15
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.
|
@@ -5,9 +5,9 @@ hex color codes (``"#FFFFFF"``), URL fragments
|
|
|
5
5
|
(``"https://x#section"``), and f-string interpolation patterns. None of
|
|
6
6
|
those ``#`` characters belong to a comment token. ``check_comments_python``
|
|
7
7
|
and the Python branch of ``extract_comment_texts`` route their ``#``
|
|
8
|
-
detection through ``tokenize.generate_tokens`` so only
|
|
8
|
+
detection through ``tokenize.generate_tokens`` so only ``COMMENT``
|
|
9
9
|
tokens are considered. These tests pin both halves of that contract:
|
|
10
|
-
``#``-in-strings is exempt;
|
|
10
|
+
``#``-in-strings is exempt; inline comments that land AFTER such
|
|
11
11
|
a string still flag.
|
|
12
12
|
"""
|
|
13
13
|
|
package/package.json
CHANGED
|
@@ -82,6 +82,7 @@ from dev_env_scripts_constants.agent_merge_check_constants import (
|
|
|
82
82
|
JSON_CONTENT_TYPE,
|
|
83
83
|
MERGE_EXIT_CODE,
|
|
84
84
|
MERGE_QUEUE_EJECTION_HOLD_TEMPLATE,
|
|
85
|
+
MERGE_QUEUE_GRAPHQL_FAILURE_TEMPLATE,
|
|
85
86
|
MERGE_QUEUE_REMOVAL_PAGE_SIZE,
|
|
86
87
|
MERGE_QUEUE_REMOVAL_QUERY,
|
|
87
88
|
MERGE_QUEUE_RULE_TYPE,
|
|
@@ -877,36 +878,60 @@ def _field_at(document: object, all_fields: object, key: str) -> object:
|
|
|
877
878
|
return all_fields.get(key)
|
|
878
879
|
|
|
879
880
|
|
|
880
|
-
def read_merge_queue_ejection(
|
|
881
|
+
def read_merge_queue_ejection(
|
|
882
|
+
slug: str, number: int, base_ref: str, token: str
|
|
883
|
+
) -> bool:
|
|
881
884
|
"""Read whether the merge queue ejected the pull request's current head.
|
|
882
885
|
|
|
883
|
-
Only GraphQL reports
|
|
884
|
-
|
|
886
|
+
Only GraphQL reports a merge queue removal, and a Claude Code session's
|
|
887
|
+
proxy refuses GraphQL. So the base branch rules come first, over REST.
|
|
888
|
+
Rules that show no merge queue skip the GraphQL query.
|
|
885
889
|
|
|
886
890
|
Args:
|
|
887
891
|
slug: The repository as ``owner/name``.
|
|
888
892
|
number: The pull request number.
|
|
893
|
+
base_ref: The branch the pull request merges into.
|
|
889
894
|
token: The GitHub token the request authenticates with.
|
|
890
895
|
|
|
891
896
|
Returns:
|
|
892
897
|
True when a ``failed_checks`` removal is newer than the head commit.
|
|
898
|
+
False when the base branch rules show no merge queue.
|
|
893
899
|
|
|
894
900
|
Raises:
|
|
895
|
-
MergeCheckError:
|
|
901
|
+
MergeCheckError: A read failed, or answered with another shape. A
|
|
902
|
+
refused GraphQL query names the merge queue that needed it.
|
|
896
903
|
"""
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
_pull_request_query_payload(
|
|
901
|
-
MERGE_QUEUE_REMOVAL_QUERY, MERGE_QUEUE_REMOVAL_PAGE_SIZE, slug, number
|
|
902
|
-
),
|
|
903
|
-
)
|
|
904
|
+
if not _may_merge_through_a_queue(slug, base_ref, token):
|
|
905
|
+
return False
|
|
906
|
+
document = _merge_queue_removal_document(slug, number, base_ref, token)
|
|
904
907
|
return _is_ejected_on_head(
|
|
905
908
|
_list_along(document, ALL_MERGE_QUEUE_REMOVAL_NODE_KEYS),
|
|
906
909
|
_head_committed_at(document),
|
|
907
910
|
)
|
|
908
911
|
|
|
909
912
|
|
|
913
|
+
def _merge_queue_removal_document(
|
|
914
|
+
slug: str, number: int, base_ref: str, token: str
|
|
915
|
+
) -> object:
|
|
916
|
+
payload = _pull_request_query_payload(
|
|
917
|
+
MERGE_QUEUE_REMOVAL_QUERY, MERGE_QUEUE_REMOVAL_PAGE_SIZE, slug, number
|
|
918
|
+
)
|
|
919
|
+
try:
|
|
920
|
+
return _request_json(GITHUB_GRAPHQL_ENDPOINT, token, payload)
|
|
921
|
+
except MergeCheckError as failure:
|
|
922
|
+
raise MergeCheckError(
|
|
923
|
+
MERGE_QUEUE_GRAPHQL_FAILURE_TEMPLATE.format(base=base_ref, failure=failure)
|
|
924
|
+
) from failure
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
def _may_merge_through_a_queue(slug: str, base_ref: str, token: str) -> bool:
|
|
928
|
+
try:
|
|
929
|
+
all_rules = _read_branch_rules(slug, urllib.parse.quote(base_ref), token)
|
|
930
|
+
except MergeCheckError:
|
|
931
|
+
return True
|
|
932
|
+
return has_merge_queue_rule(all_rules)
|
|
933
|
+
|
|
934
|
+
|
|
910
935
|
def _list_along(document: object, all_keys: Sequence[str]) -> list[object]:
|
|
911
936
|
all_records = functools.reduce(
|
|
912
937
|
functools.partial(_field_at, document), all_keys, document
|
|
@@ -985,7 +1010,10 @@ def main(all_arguments: Sequence[str]) -> int:
|
|
|
985
1010
|
parsed.slug, parsed.number, token
|
|
986
1011
|
)
|
|
987
1012
|
is_ejected_from_merge_queue = read_merge_queue_ejection(
|
|
988
|
-
parsed.slug,
|
|
1013
|
+
parsed.slug,
|
|
1014
|
+
parsed.number,
|
|
1015
|
+
str(_nested_field(all_pull_request_fields, BASE_KEY, REF_KEY)),
|
|
1016
|
+
token,
|
|
989
1017
|
)
|
|
990
1018
|
except MergeCheckError as failure:
|
|
991
1019
|
print(failure, file=sys.stderr)
|
|
@@ -142,6 +142,10 @@ HOLD_EXIT_CODE = 1
|
|
|
142
142
|
ERROR_EXIT_CODE = 2
|
|
143
143
|
|
|
144
144
|
NO_SIGN_IN_MESSAGE = "No GitHub token in the environment. Set GH_TOKEN or GITHUB_TOKEN."
|
|
145
|
+
MERGE_QUEUE_GRAPHQL_FAILURE_TEMPLATE = (
|
|
146
|
+
"The {base} branch merges through a merge queue, and only the GitHub GraphQL"
|
|
147
|
+
" API reports a merge queue ejection. That GraphQL read failed: {failure}"
|
|
148
|
+
)
|
|
145
149
|
SLUG_SEPARATOR = "/"
|
|
146
150
|
SLUG_ARGUMENT_HELP = "Repository as owner/name, such as jl-cmd/claude-dev-env."
|
|
147
151
|
NUMBER_ARGUMENT_HELP = "Pull request number."
|
|
@@ -29,6 +29,7 @@ CLEAN_PULL_REQUEST = {
|
|
|
29
29
|
"number": 1442,
|
|
30
30
|
"draft": False,
|
|
31
31
|
"mergeable_state": "clean",
|
|
32
|
+
"base": {"ref": "main"},
|
|
32
33
|
"head": {"sha": "ab845eb6945650055ebe852312a9057b9f067d6a"},
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -293,7 +294,7 @@ def test_a_ready_pull_request_exits_zero(monkeypatch: pytest.MonkeyPatch) -> Non
|
|
|
293
294
|
monkeypatch.setattr(
|
|
294
295
|
agent_merge_check,
|
|
295
296
|
"read_merge_queue_ejection",
|
|
296
|
-
lambda slug, number, token: False,
|
|
297
|
+
lambda slug, number, base_ref, token: False,
|
|
297
298
|
)
|
|
298
299
|
assert agent_merge_check.main(["jl-cmd/claude-dev-env", "1442"]) == 0
|
|
299
300
|
|
|
@@ -313,7 +314,7 @@ def test_a_held_pull_request_exits_one(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
313
314
|
monkeypatch.setattr(
|
|
314
315
|
agent_merge_check,
|
|
315
316
|
"read_merge_queue_ejection",
|
|
316
|
-
lambda slug, number, token: False,
|
|
317
|
+
lambda slug, number, base_ref, token: False,
|
|
317
318
|
)
|
|
318
319
|
assert agent_merge_check.main(["jl-cmd/claude-dev-env", "1442"]) == 1
|
|
319
320
|
|
|
@@ -655,12 +656,15 @@ def _removal(created_at: str, reason: str) -> dict[str, object]:
|
|
|
655
656
|
|
|
656
657
|
def _merge_queue_answers(
|
|
657
658
|
all_removals: list[dict[str, object]],
|
|
659
|
+
all_rules: list[object] = MAIN_BRANCH_RULES,
|
|
658
660
|
) -> object:
|
|
659
661
|
def _answer(url: str, token: str, all_payload_fields: object) -> object:
|
|
660
662
|
if url.endswith("/pulls/1442"):
|
|
661
663
|
return QUEUED_PULL_REQUEST
|
|
662
664
|
if url.endswith("/ccr/review_threads"):
|
|
663
665
|
return []
|
|
666
|
+
if "/rules/branches/" in url:
|
|
667
|
+
return all_rules
|
|
664
668
|
if url.endswith("/graphql"):
|
|
665
669
|
return _merge_queue_document(all_removals)
|
|
666
670
|
raise AssertionError(url)
|
|
@@ -668,6 +672,47 @@ def _merge_queue_answers(
|
|
|
668
672
|
return _answer
|
|
669
673
|
|
|
670
674
|
|
|
675
|
+
def _refuse_graphql(answer: object) -> object:
|
|
676
|
+
def _answer(url: str, token: str, all_payload_fields: object) -> object:
|
|
677
|
+
if url.endswith("/graphql"):
|
|
678
|
+
raise agent_merge_check.MergeCheckError("HTTP Error 403: Forbidden")
|
|
679
|
+
return answer(url, token, all_payload_fields)
|
|
680
|
+
|
|
681
|
+
return _answer
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def test_a_base_without_a_merge_queue_may_merge_when_graphql_is_refused(
|
|
685
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
686
|
+
capsys: pytest.CaptureFixture[str],
|
|
687
|
+
) -> None:
|
|
688
|
+
monkeypatch.setenv("GH_TOKEN", "token")
|
|
689
|
+
monkeypatch.setattr(
|
|
690
|
+
agent_merge_check,
|
|
691
|
+
"_request_json",
|
|
692
|
+
_refuse_graphql(_merge_queue_answers([], all_rules=[])),
|
|
693
|
+
)
|
|
694
|
+
exit_code = agent_merge_check.main(["jl-cmd/claude-dev-env", "1442"])
|
|
695
|
+
assert exit_code == 0
|
|
696
|
+
assert capsys.readouterr().out.startswith(MERGE_VERDICT_LABEL)
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def test_a_merge_queue_base_names_the_refused_graphql_read(
|
|
700
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
701
|
+
capsys: pytest.CaptureFixture[str],
|
|
702
|
+
) -> None:
|
|
703
|
+
monkeypatch.setenv("GH_TOKEN", "token")
|
|
704
|
+
monkeypatch.setattr(
|
|
705
|
+
agent_merge_check, "_request_json", _refuse_graphql(_merge_queue_answers([]))
|
|
706
|
+
)
|
|
707
|
+
exit_code = agent_merge_check.main(["jl-cmd/claude-dev-env", "1442"])
|
|
708
|
+
captured = capsys.readouterr()
|
|
709
|
+
assert exit_code == 2
|
|
710
|
+
assert captured.out == ""
|
|
711
|
+
assert "merge queue" in captured.err
|
|
712
|
+
assert "GraphQL" in captured.err
|
|
713
|
+
assert "HTTP Error 403: Forbidden" in captured.err
|
|
714
|
+
|
|
715
|
+
|
|
671
716
|
def _run_queued_main(
|
|
672
717
|
monkeypatch: pytest.MonkeyPatch,
|
|
673
718
|
capsys: pytest.CaptureFixture[str],
|
|
@@ -772,13 +817,11 @@ def test_read_merge_queue_ejection_compares_failed_checks_removals_to_the_head(
|
|
|
772
817
|
expected_ejection: bool,
|
|
773
818
|
) -> None:
|
|
774
819
|
monkeypatch.setattr(
|
|
775
|
-
agent_merge_check,
|
|
776
|
-
"_request_json",
|
|
777
|
-
lambda url, token, all_payload_fields: _merge_queue_document(all_removals),
|
|
820
|
+
agent_merge_check, "_request_json", _merge_queue_answers(all_removals)
|
|
778
821
|
)
|
|
779
822
|
assert (
|
|
780
823
|
agent_merge_check.read_merge_queue_ejection(
|
|
781
|
-
"jl-cmd/claude-dev-env", 1442, "token"
|
|
824
|
+
"jl-cmd/claude-dev-env", 1442, "main", "token"
|
|
782
825
|
)
|
|
783
826
|
is expected_ejection
|
|
784
827
|
)
|
|
@@ -915,8 +958,8 @@ def _unstable_answers(
|
|
|
915
958
|
return PULL_REQUEST_5330
|
|
916
959
|
if parsed.path.endswith("/ccr/review_threads"):
|
|
917
960
|
return all_threads or []
|
|
918
|
-
if parsed.path
|
|
919
|
-
return
|
|
961
|
+
if "/rules/branches/" in parsed.path:
|
|
962
|
+
return []
|
|
920
963
|
if parsed.path.endswith("/check-runs"):
|
|
921
964
|
return {"check_runs": _requested_page(parsed.query, all_check_runs)}
|
|
922
965
|
if parsed.path.endswith("/status"):
|