claude-dev-env 1.82.0 → 1.84.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/CLAUDE.md +16 -13
- package/_shared/pr-loop/audit-contract.md +24 -12
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviewer_availability_constants.py +12 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +0 -2
- package/_shared/pr-loop/scripts/reviewer_availability.py +182 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +9 -33
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +159 -0
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability_constants.py +36 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +14 -4
- package/agents/clean-coder.md +3 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/bin/install.mjs +16 -8
- package/bin/install.test.mjs +17 -6
- package/commands/CLAUDE.md +0 -1
- package/docs/CODE_RULES.md +1 -1
- package/hooks/blocking/CLAUDE.md +2 -4
- package/hooks/blocking/code_rules_constants_config.py +164 -1
- package/hooks/blocking/code_rules_docstrings.py +428 -15
- package/hooks/blocking/code_rules_enforcer.py +33 -0
- package/hooks/blocking/code_rules_imports_logging.py +867 -1
- package/hooks/blocking/code_rules_magic_values.py +5 -0
- package/hooks/blocking/code_rules_naming_collection.py +152 -6
- package/hooks/blocking/code_rules_shared.py +34 -0
- package/hooks/blocking/code_rules_string_magic.py +68 -0
- package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/reviewer_spawn_gate.py +182 -0
- package/hooks/blocking/stale_comment_reference_blocker.py +267 -0
- package/hooks/blocking/state_description_blocker.py +96 -5
- package/hooks/blocking/test_code_rules_config_duplicate_path_anchor.py +132 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +2 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +385 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +67 -0
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +40 -0
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +213 -0
- package/hooks/blocking/test_code_rules_enforcer_referenced_underscore_loop.py +169 -0
- package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
- package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
- package/hooks/blocking/test_code_rules_js_bare_flag_return_directive.py +266 -0
- package/hooks/blocking/test_code_rules_js_sibling_return_object_key_drift.py +490 -0
- package/hooks/blocking/test_code_rules_logging_adjacent_literals.py +171 -0
- package/hooks/blocking/test_code_rules_magic_values.py +54 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
- package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +9 -3
- package/hooks/blocking/test_reviewer_spawn_gate.py +230 -0
- package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
- package/hooks/blocking/test_stale_comment_reference_blocker.py +236 -0
- package/hooks/blocking/test_state_description_blocker.py +135 -0
- package/hooks/hooks.json +5 -0
- package/hooks/hooks_constants/CLAUDE.md +3 -4
- package/hooks/hooks_constants/blocking_check_limits.py +46 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
- package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +41 -0
- package/hooks/hooks_constants/stale_comment_reference_blocker_constants.py +76 -0
- package/hooks/hooks_constants/state_description_blocker_constants.py +8 -0
- package/hooks/validation/post_tool_use_dispatcher.py +1 -1
- package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
- package/hooks/workflow/CLAUDE.md +2 -8
- package/package.json +1 -1
- package/rules/CLAUDE.md +5 -2
- package/rules/claude-md-orphan-file.md +5 -0
- package/rules/docstring-prose-matches-implementation.md +10 -1
- package/rules/env-var-table-code-drift.md +5 -0
- package/rules/es-exe-file-search.md +17 -0
- package/rules/no-historical-clutter.md +12 -1
- package/rules/orphan-css-class.md +5 -0
- package/rules/package-inventory-stale-entry.md +10 -0
- package/rules/paired-test-coverage.md +5 -0
- package/rules/plain-illustrative-docstrings.md +40 -1
- package/rules/verify-before-asking.md +7 -0
- package/rules/verify-runtime-state.md +40 -0
- package/rules/windows-filesystem-safe.md +8 -0
- package/rules/workers-done-before-complete.md +33 -0
- package/rules/workflow-substitution-slots.md +5 -0
- package/skills/CLAUDE.md +7 -2
- package/skills/autoconverge/SKILL.md +48 -61
- package/skills/autoconverge/reference/closing-report.md +6 -6
- package/skills/autoconverge/reference/convergence.md +17 -15
- package/skills/autoconverge/reference/gotchas.md +6 -3
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
- package/skills/autoconverge/workflow/converge.contract.test.mjs +153 -10
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +55 -19
- package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
- package/skills/autoconverge/workflow/converge.mjs +167 -106
- package/skills/autoconverge/workflow/render_report.py +7 -11
- package/skills/bugteam/CLAUDE.md +1 -1
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +25 -72
- package/skills/bugteam/reference/CLAUDE.md +1 -3
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +1 -1
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -7
- package/skills/bugteam/scripts/CLAUDE.md +0 -6
- package/skills/bugteam/scripts/README.md +0 -4
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
- package/skills/code/SKILL.md +2 -0
- package/skills/copilot-review/CLAUDE.md +1 -1
- package/skills/copilot-review/SKILL.md +25 -23
- package/skills/everything-search/SKILL.md +5 -0
- package/skills/findbugs/CLAUDE.md +2 -2
- package/skills/findbugs/SKILL.md +22 -83
- package/skills/fixbugs/SKILL.md +2 -4
- package/skills/log-audit/CLAUDE.md +20 -0
- package/skills/log-audit/SKILL.md +68 -0
- package/skills/log-audit/reference/CLAUDE.md +9 -0
- package/skills/log-audit/reference/charter.md +52 -0
- package/skills/log-audit/scripts/CLAUDE.md +27 -0
- package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
- package/skills/log-audit/scripts/collect_log_window.py +199 -0
- package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
- package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
- package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
- package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
- package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
- package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
- package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
- package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
- package/skills/monitor-open-prs/SKILL.md +2 -2
- package/skills/post-audit-findings/SKILL.md +84 -0
- package/skills/pr-converge/CLAUDE.md +2 -0
- package/skills/pr-converge/SKILL.md +72 -59
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +16 -19
- package/skills/pr-converge/reference/examples.md +5 -5
- package/skills/pr-converge/reference/fix-protocol.md +16 -43
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +24 -45
- package/skills/pr-converge/reference/state-schema.md +15 -0
- package/skills/pr-converge/scripts/README.md +3 -5
- package/skills/pr-fix-protocol/SKILL.md +70 -0
- package/skills/pr-loop-lifecycle/SKILL.md +73 -0
- package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
- package/skills/pr-scope-resolve/SKILL.md +48 -0
- package/skills/qbug/CLAUDE.md +4 -4
- package/skills/qbug/SKILL.md +46 -144
- package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
- package/skills/reviewer-gates/SKILL.md +96 -0
- package/skills/session-log/CLAUDE.md +7 -7
- package/skills/session-log/SKILL.md +27 -44
- package/skills/test_markdown_link_integrity.py +103 -0
- package/commands/doc-gist.md +0 -16
- package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
- package/hooks/blocking/conftest.py +0 -30
- package/hooks/blocking/md_path_exemptions.py +0 -224
- package/hooks/blocking/md_to_html_blocker.py +0 -155
- package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
- package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
- package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
- package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
- package/hooks/hooks_constants/html_companion_constants.py +0 -20
- package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
- package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
- package/hooks/workflow/doc_gist_auto_publish.py +0 -144
- package/hooks/workflow/md_to_html_companion.py +0 -358
- package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
- package/hooks/workflow/test_md_to_html_companion.py +0 -613
- package/skills/bugteam/reference/audit-contract.md +0 -163
- package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
- package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
- package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
- package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
- package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
- package/skills/doc-gist/CLAUDE.md +0 -25
- package/skills/doc-gist/SKILL.md +0 -97
- package/skills/doc-gist/references/CLAUDE.md +0 -9
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
- package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
- package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
- package/skills/doc-gist/references/examples/05-design-system.html +0 -629
- package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
- package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
- package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
- package/skills/doc-gist/references/examples/11-status-report.html +0 -528
- package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
- package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
- package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
- package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
- package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
- package/skills/doc-gist/references/examples/README.md +0 -5
- package/skills/doc-gist/scripts/CLAUDE.md +0 -27
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
- package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
- package/skills/doc-gist/scripts/gist_upload.py +0 -177
- package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
- /package/skills/{doc-gist/scripts/doc_gist_scripts_constants → log-audit/scripts/log_audit_constants}/__init__.py +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Tests for mine_copilot_findings — defect classification and clustering."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
_script_directory = str(Path(__file__).resolve().parent)
|
|
7
|
+
if _script_directory not in sys.path:
|
|
8
|
+
sys.path.insert(0, _script_directory)
|
|
9
|
+
|
|
10
|
+
from mine_copilot_findings import ( # noqa: E402
|
|
11
|
+
ReviewerComment,
|
|
12
|
+
_flatten_pages,
|
|
13
|
+
_pull_number_from_url,
|
|
14
|
+
_reviewer_comment_from_payload,
|
|
15
|
+
classify_defect,
|
|
16
|
+
cluster_defects,
|
|
17
|
+
proposal_for_defect_class,
|
|
18
|
+
)
|
|
19
|
+
from log_audit_constants.mine_copilot_findings_constants import ( # noqa: E402
|
|
20
|
+
PROPOSAL_BY_DEFECT_CLASS,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestClassifyDefect:
|
|
25
|
+
def test_recognizes_a_missing_type_hint_comment(self) -> None:
|
|
26
|
+
assert (
|
|
27
|
+
classify_defect("This function is missing a type hint on the return")
|
|
28
|
+
== "missing-type-hint"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def test_recognizes_a_broad_except_comment(self) -> None:
|
|
32
|
+
assert classify_defect("There is a broad except swallowing errors") == (
|
|
33
|
+
"broad-except"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def test_recognizes_a_magic_value_comment(self) -> None:
|
|
37
|
+
assert classify_defect("42 is a magic number, extract it") == "magic-value"
|
|
38
|
+
|
|
39
|
+
def test_returns_none_for_praise(self) -> None:
|
|
40
|
+
assert classify_defect("Looks great, nicely done!") is None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestClusterDefects:
|
|
44
|
+
def test_groups_comments_by_class_and_ranks_by_count(self) -> None:
|
|
45
|
+
comments = [
|
|
46
|
+
ReviewerComment(1, "cursor[bot]", "missing type hint here"),
|
|
47
|
+
ReviewerComment(2, "cursor[bot]", "another missing annotation"),
|
|
48
|
+
ReviewerComment(3, "cursor[bot]", "this is a broad except"),
|
|
49
|
+
ReviewerComment(4, "cursor[bot]", "nice work"),
|
|
50
|
+
]
|
|
51
|
+
clusters = cluster_defects(comments)
|
|
52
|
+
assert clusters[0].defect_class == "missing-type-hint"
|
|
53
|
+
assert clusters[0].count == 2
|
|
54
|
+
assert len(clusters) == 2
|
|
55
|
+
|
|
56
|
+
def test_caps_the_number_of_example_bodies(self) -> None:
|
|
57
|
+
comments = [
|
|
58
|
+
ReviewerComment(
|
|
59
|
+
each_index, "cursor[bot]", f"missing type hint number {each_index}"
|
|
60
|
+
)
|
|
61
|
+
for each_index in range(10)
|
|
62
|
+
]
|
|
63
|
+
clusters = cluster_defects(comments)
|
|
64
|
+
assert clusters[0].count == 10
|
|
65
|
+
assert len(clusters[0].example_bodies) == 3
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TestProposalForDefectClass:
|
|
69
|
+
def test_maps_a_class_to_its_skill_edit_proposal(self) -> None:
|
|
70
|
+
proposal = proposal_for_defect_class("broad-except")
|
|
71
|
+
assert proposal.defect_class == "broad-except"
|
|
72
|
+
assert proposal.proposal == PROPOSAL_BY_DEFECT_CLASS["broad-except"]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TestPullNumberFromUrl:
|
|
76
|
+
def test_reads_the_trailing_pull_number(self) -> None:
|
|
77
|
+
url = "https://api.github.com/repos/owner/name/pulls/867"
|
|
78
|
+
assert _pull_number_from_url(url) == 867
|
|
79
|
+
|
|
80
|
+
def test_returns_none_when_the_last_segment_is_not_a_number(self) -> None:
|
|
81
|
+
assert _pull_number_from_url("https://api.github.com/repos/owner/name") is None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TestReviewerCommentFromPayload:
|
|
85
|
+
def test_builds_a_comment_from_a_reviewer_bot_payload(self) -> None:
|
|
86
|
+
payload = {
|
|
87
|
+
"user": {"login": "cursor[bot]"},
|
|
88
|
+
"body": "missing type hint",
|
|
89
|
+
"pull_request_url": "https://api.github.com/repos/owner/name/pulls/12",
|
|
90
|
+
}
|
|
91
|
+
parsed_comment = _reviewer_comment_from_payload(payload)
|
|
92
|
+
assert parsed_comment == ReviewerComment(
|
|
93
|
+
pull_number=12, author="cursor[bot]", body="missing type hint"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def test_skips_a_comment_from_a_non_reviewer_login(self) -> None:
|
|
97
|
+
payload = {
|
|
98
|
+
"user": {"login": "some-human"},
|
|
99
|
+
"body": "looks good",
|
|
100
|
+
"pull_request_url": "https://api.github.com/repos/owner/name/pulls/12",
|
|
101
|
+
}
|
|
102
|
+
assert _reviewer_comment_from_payload(payload) is None
|
|
103
|
+
|
|
104
|
+
def test_skips_a_payload_that_is_not_a_dict(self) -> None:
|
|
105
|
+
assert _reviewer_comment_from_payload("not a payload") is None
|
|
106
|
+
|
|
107
|
+
def test_skips_a_payload_missing_the_pull_request_url(self) -> None:
|
|
108
|
+
payload = {"user": {"login": "cursor[bot]"}, "body": "missing type hint"}
|
|
109
|
+
assert _reviewer_comment_from_payload(payload) is None
|
|
110
|
+
|
|
111
|
+
def test_skips_a_payload_whose_url_has_no_numeric_pull_number(self) -> None:
|
|
112
|
+
payload = {
|
|
113
|
+
"user": {"login": "cursor[bot]"},
|
|
114
|
+
"body": "missing type hint",
|
|
115
|
+
"pull_request_url": "https://api.github.com/repos/owner/name/pulls",
|
|
116
|
+
}
|
|
117
|
+
assert _reviewer_comment_from_payload(payload) is None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class TestFlattenPages:
|
|
121
|
+
def test_flattens_multiple_pages_into_one_list(self) -> None:
|
|
122
|
+
slurped_payload = [[{"id": 1}, {"id": 2}], [{"id": 3}]]
|
|
123
|
+
assert _flatten_pages(slurped_payload) == [{"id": 1}, {"id": 2}, {"id": 3}]
|
|
124
|
+
|
|
125
|
+
def test_returns_empty_list_for_a_non_list_payload(self) -> None:
|
|
126
|
+
assert _flatten_pages({"message": "not a list"}) == []
|
|
@@ -27,7 +27,7 @@ description: >-
|
|
|
27
27
|
|
|
28
28
|
Refusals — first match wins; respond with the quoted line exactly and stop:
|
|
29
29
|
|
|
30
|
-
- **Disabled via environment.** When `CLAUDE_REVIEWS_DISABLED` (comma-separated, case-insensitive, whitespace-tolerant)
|
|
30
|
+
- **Disabled via environment.** When `CLAUDE_REVIEWS_DISABLED` (comma-separated, case-insensitive, whitespace-tolerant) has the token `bugteam`: `/monitor-open-prs is a /bugteam dispatcher and /bugteam is disabled via CLAUDE_REVIEWS_DISABLED.` Gate semantics live in the `reviewer-gates` skill ([../reviewer-gates/SKILL.md](../reviewer-gates/SKILL.md)).
|
|
31
31
|
- **GitHub API not accessible.** `get_me failed. /monitor-open-prs needs active GitHub MCP credentials.`
|
|
32
32
|
- **Dirty tree on the caller's repo.** `Uncommitted changes detected. Stash, commit, or revert before /monitor-open-prs.`
|
|
33
33
|
- **Required subagents missing.** Confirm `code-quality-agent` and `clean-coder` exist. Else: `Required subagent type <name> not installed.`
|
|
@@ -41,7 +41,7 @@ Call `scripts/discover_open_prs.discover_open_prs(all_owners=["jl-cmd", "JonEcho
|
|
|
41
41
|
For each discovered PR:
|
|
42
42
|
|
|
43
43
|
1. Resolve the PR's repo checkout (existing worktree or fresh `git clone`).
|
|
44
|
-
2. From that checkout, invoke `/bugteam --bugbot-retrigger <pr_number>`. When `
|
|
44
|
+
2. From that checkout, invoke `/bugteam --bugbot-retrigger <pr_number>`. When `python "$HOME/.claude/_shared/pr-loop/scripts/reviews_disabled.py" --reviewer bugbot` exits 0 (bugbot disabled), omit `--bugbot-retrigger` from the dispatched command so the bugbot leg sits out the run.
|
|
45
45
|
3. The `--bugbot-retrigger` flag tells bugteam to post `bugbot run` as an issue comment after every successful FIX push so Cursor's bugbot re-evaluates the new commit.
|
|
46
46
|
4. Bugteam runs its own 20-loop audit/fix cycle per PR; this skill waits for each bugteam invocation to return before dispatching the next (or fanning out — see below).
|
|
47
47
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: post-audit-findings
|
|
3
|
+
description: >-
|
|
4
|
+
Publishes an audit pass as one GitHub PR review via the shared
|
|
5
|
+
post_audit_thread.py: maps audit findings to the {path, line, side,
|
|
6
|
+
severity, description, fix_summary} JSON shape (splitting failure_mode at
|
|
7
|
+
the literal `Fix:` heading), partitions anchored vs unanchored findings so
|
|
8
|
+
one bad anchor cannot reject the whole POST, handles the self-PR reviewer
|
|
9
|
+
toggle via BUGTEAM_REVIEWER_ACCOUNT, and harvests finding comment ids and
|
|
10
|
+
PRRT thread node ids for the fix loop. Invoked by audit skills (bugteam,
|
|
11
|
+
qbug, findbugs) after each audit pass, CLEAN or DIRTY; not for ad-hoc PR
|
|
12
|
+
comments or review replies.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Post Audit Findings
|
|
16
|
+
|
|
17
|
+
**Core principle:** every audit pass — CLEAN or DIRTY — lands on the PR as exactly one review, so the unresolved-thread gate sees the pass and each finding becomes its own resolvable thread.
|
|
18
|
+
|
|
19
|
+
## How callers invoke this
|
|
20
|
+
|
|
21
|
+
- **Skill-capable contexts** (a lead session with the `Skill` tool): `Skill({skill: "post-audit-findings", args: "--skill <caller> --owner <O> --repo <R> --pr-number <N> --commit <head_sha> --state <CLEAN|DIRTY> --findings-json <path>"})`.
|
|
22
|
+
- **Fallback** (a subagent or teammate without the `Skill` tool): the caller's spawn prompt says "Read `~/.claude/skills/post-audit-findings/SKILL.md` and apply it with the parameters below."
|
|
23
|
+
|
|
24
|
+
`--skill <caller>` is required — the posted review names the audit skill that produced it. Capture `<head_sha>` once, at the start of the posting step, via `git rev-parse HEAD` in the worktree the diff was scoped against.
|
|
25
|
+
|
|
26
|
+
## Findings-JSON mapping
|
|
27
|
+
|
|
28
|
+
The findings file's root is a list of objects shaped `{path, line, side, severity, description, fix_summary}`. Map each audit finding:
|
|
29
|
+
|
|
30
|
+
- Finding `file` → `path`.
|
|
31
|
+
- The `failure_mode` field carries both the failure narrative AND the `Fix:` / `Validation:` text (per [`agents/code-quality-agent.md`](../../agents/code-quality-agent.md), "The `failure_mode` field is the audit-to-fix handoff"). Split it at the literal `Fix:` heading: the prefix becomes `description`; the suffix starting at `Fix:` (including the trailing `Validation:` clause) becomes `fix_summary`. When a finding omits the `Fix:` heading, write the full `failure_mode` text to BOTH fields so the script's `INLINE_COMMENT_BODY_TEMPLATE` still renders coherently.
|
|
32
|
+
- Set `side="RIGHT"` for every entry.
|
|
33
|
+
- On CLEAN, the file holds an empty array (`[]`).
|
|
34
|
+
|
|
35
|
+
## Anchored vs unanchored partition
|
|
36
|
+
|
|
37
|
+
Before serializing, partition findings into **anchored** (the line appears in the diff at `--commit`) and **unanchored** (it does not). Only anchored findings go into the JSON — the GitHub reviews API rejects the ENTIRE POST when any inline comment targets a line outside the diff, so a single unanchored entry breaks the whole review. Surface unanchored findings in the caller's user-facing report instead.
|
|
38
|
+
|
|
39
|
+
Zero anchored findings → `--state CLEAN`; one or more → `--state DIRTY`.
|
|
40
|
+
|
|
41
|
+
## Posting
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python "$HOME/.claude/_shared/pr-loop/scripts/post_audit_thread.py" \
|
|
45
|
+
--skill <caller> \
|
|
46
|
+
--owner <owner> \
|
|
47
|
+
--repo <repo> \
|
|
48
|
+
--pr-number <N> \
|
|
49
|
+
--commit <head_sha> \
|
|
50
|
+
--state <CLEAN|DIRTY> \
|
|
51
|
+
--findings-json <path>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The script POSTs one review to `/repos/{owner}/{repo}/pulls/{N}/reviews`: `event=APPROVE` on CLEAN (GitHub stores it as `state=APPROVED`; empty `comments[]`; body documents "no findings"), `event=REQUEST_CHANGES` on DIRTY (one inline anchored comment per finding; each becomes its own resolvable thread).
|
|
55
|
+
|
|
56
|
+
**Self-PR auto-toggle.** GitHub rejects both `APPROVE` and `REQUEST_CHANGES` with HTTP 422 when the authenticated identity matches the PR author. `post_audit_thread.py` detects this via `gh api user` + `gh api repos/<o>/<r>/pulls/<n>` and auto-resolves an alternate gh account's token for the reviews POST — the active `gh auth` account is not mutated; only the bearer token on the request changes, so no swap-back step exists.
|
|
57
|
+
|
|
58
|
+
Configuration:
|
|
59
|
+
|
|
60
|
+
- `GH_TOKEN` / `GITHUB_TOKEN` env vars take precedence over the toggle — set one to pin a reviewer identity by token.
|
|
61
|
+
- `BUGTEAM_REVIEWER_ACCOUNT` names which authenticated alternate to prefer (for example `BUGTEAM_REVIEWER_ACCOUNT=jl-cmd`). The env var name is shared across every skill that invokes `post_audit_thread.py`. When unset, the script falls back to the first alternate account `gh auth status` reports.
|
|
62
|
+
- The named alternate must be logged in (`gh auth login -h github.com -u <login>`) before the audit runs; on self-PR with no usable alternate the script exits 1 with a message pointing at `gh auth login`.
|
|
63
|
+
|
|
64
|
+
**Exit codes:** `0` — success; the new review's `html_url` prints to stdout (surface it in the caller's report). `1` — user input error. `2` — retry exhaustion (1s / 4s / 16s backoff across four attempts): a hard blocker. On exit 2 the caller halts the loop (`error: post_audit_thread retry exhausted`) or tells the user the review post failed and the unresolved-thread gate will not see this pass — never a silent retry.
|
|
65
|
+
|
|
66
|
+
## Harvest ids for the fix loop
|
|
67
|
+
|
|
68
|
+
After a DIRTY post, fetch the new review's threads once and record, per finding: the finding comment's URL, its numeric comment id (the reply anchor), and its thread node id (`PRRT_…`, the `resolve_thread` handle):
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
pull_request_read(method="get_review_comments", ...) → match comments to the new review id
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Hand the id triple to the fix step — `pr-fix-protocol` consumes it for the reply-and-resolve unit. Harvesting at post time saves the fix executor a second fetch and pins the mapping before any thread state changes.
|
|
75
|
+
|
|
76
|
+
## Gotchas
|
|
77
|
+
|
|
78
|
+
- **One unanchored comment voids the whole review.** The partition is not an optimization; skipping it makes every DIRTY post a coin flip.
|
|
79
|
+
- **CLEAN still posts.** A pass that finds nothing and posts nothing is invisible to the unresolved-thread gate and to anyone auditing the loop's history.
|
|
80
|
+
- **Exit 2 is a stop, not a retry.** The script already retried with backoff; a caller looping on top of it hammers the API to no effect.
|
|
81
|
+
|
|
82
|
+
## Folder map
|
|
83
|
+
|
|
84
|
+
- `SKILL.md` — this file. The posting script and its body template constants live in `_shared/pr-loop/scripts/` (`post_audit_thread.py`, `pr_loop_shared_constants/post_audit_thread_constants.py`); the review-payload reference is `_shared/pr-loop/gh-payloads.md`.
|
|
@@ -27,3 +27,5 @@ Drives a draft PR to convergence by looping Cursor Bugbot, a code-review pass, a
|
|
|
27
27
|
- The pre-flight gate requires `EnterWorktree` before any file read or API call, and confirms `ScheduleWakeup` is in the tool list.
|
|
28
28
|
- All findings and PR reports state verified facts only — no hedging language.
|
|
29
29
|
- The GitHub MCP (`pull_request_read`, `pull_request_review_write`) is the primary path for PR inspection; `gh api` is the fallback.
|
|
30
|
+
- Three step-scoped agents (`fix_executor`, `thread_sweep`, `copilot_watch`) persist across ticks via the `persistent_agents` map in loop state; each tick resumes them with `SendMessage` and spawns a fresh named agent when a stored id is dead.
|
|
31
|
+
- The tick delegates shared mechanics to five sibling sub-skills: `reviewer-gates` (opt-out, Copilot quota, Bugbot trigger), `pr-scope-resolve` (target resolution), `pr-fix-protocol` (fix sequence + unresolved-thread sweep), `post-audit-findings` (audit posting), and `pr-loop-lifecycle` (run open/close).
|
|
@@ -45,23 +45,15 @@ the session worktree before teardown. See
|
|
|
45
45
|
|
|
46
46
|
## Copilot quota pre-check (start of run)
|
|
47
47
|
|
|
48
|
-
On the first tick,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
the exact `.env` path and key to set.
|
|
58
|
-
|
|
59
|
-
Run the pre-check once, not per tick. Every tick reads `copilot_down` from
|
|
60
|
-
state. When `copilot_down` is true, skip the Copilot gate outright — no fetch,
|
|
61
|
-
no request, no poll, no agent — and export `CLAUDE_REVIEWS_DISABLED="copilot"`
|
|
62
|
-
in that tick's shell before the convergence check, so `check_convergence.py`
|
|
63
|
-
bypasses the Copilot review gate and the pending-requested-reviews gate and the
|
|
64
|
-
run still marks ready on the remaining signals.
|
|
48
|
+
On the first tick, apply the `reviewer-gates` skill's Copilot quota gate
|
|
49
|
+
(`../reviewer-gates/SKILL.md` § Gate 2) — once per run, never per tick. The
|
|
50
|
+
flag lives in `pr-converge-state.json` as `copilot_down`, and every tick reads
|
|
51
|
+
it. While `copilot_down` is true, the tick skips all Copilot work — no fetch,
|
|
52
|
+
no request, no poll, no agent — and exports
|
|
53
|
+
`CLAUDE_REVIEWS_DISABLED="copilot"` in that tick's shell before
|
|
54
|
+
`check_convergence.py`, so the check bypasses the Copilot review gate and the
|
|
55
|
+
pending-requested-reviews gate and the run still marks ready on the remaining
|
|
56
|
+
signals.
|
|
65
57
|
|
|
66
58
|
## Budget-aware tick boundaries
|
|
67
59
|
|
|
@@ -94,7 +86,39 @@ so the next tick resumes with accurate state.
|
|
|
94
86
|
Fields: `phase`, `tick_count`, `bugbot_clean_at`, `code_review_clean_at`,
|
|
95
87
|
`bugteam_clean_at`, `copilot_clean_at`, `current_head`,
|
|
96
88
|
`bugbot_acknowledged_at`, `bugbot_down`, `copilot_down`,
|
|
97
|
-
`bugteam_skill_invoked_at_head`, `bugteam_skill_invoked_at_tick
|
|
89
|
+
`bugteam_skill_invoked_at_head`, `bugteam_skill_invoked_at_tick`,
|
|
90
|
+
`agents_session_id`, `persistent_agents`.
|
|
91
|
+
|
|
92
|
+
## Persistent per-step agents
|
|
93
|
+
|
|
94
|
+
Three step-scoped agents persist across ticks so their context carries
|
|
95
|
+
forward: `fix_executor`, `thread_sweep`, and `copilot_watch`, recorded in
|
|
96
|
+
the `persistent_agents` map
|
|
97
|
+
([`reference/state-schema.md`](reference/state-schema.md)).
|
|
98
|
+
|
|
99
|
+
- **Resume:** read `persistent_agents.<key>`. When an entry exists,
|
|
100
|
+
`SendMessage` to the stored `agent_id` with a tick payload that restates
|
|
101
|
+
the PR scope, `current_head`, the PR worktree path, this tick's findings
|
|
102
|
+
or threads, and the report-back contract. Bump `last_used_tick`, then
|
|
103
|
+
await completion.
|
|
104
|
+
- **First spawn:** `Agent(subagent_type: "clean-coder", name:
|
|
105
|
+
"prc-fix-<PR#>")` — the `name` makes the agent a persistent teammate
|
|
106
|
+
that idles awaiting messages. Record `{agent_id, created_tick,
|
|
107
|
+
last_used_tick}` under the step key. Keep the spawn prompt fix-shaped,
|
|
108
|
+
never audit-shaped: the `pr_converge_bugteam_enforcer` hook blocks
|
|
109
|
+
audit-shaped clean-coder spawns during the BUGTEAM phase.
|
|
110
|
+
- **Stale or dead id:** on a `SendMessage` failure, or no acknowledgment
|
|
111
|
+
within one bounded wait, drop the map entry, spawn a fresh named agent,
|
|
112
|
+
record it, and continue the tick. Never abort a tick on a stale id;
|
|
113
|
+
never retry the same dead id.
|
|
114
|
+
- **Fresh every round (never persisted):** the Step 5 `/code-review --fix`
|
|
115
|
+
pass and the Step 6 bugteam audit (unbiased eyes each round; the
|
|
116
|
+
enforcer needs the formal Skill call), and every `code-verifier` — a
|
|
117
|
+
named code-verifier never fires `SubagentStop`, so no verdict mints (see
|
|
118
|
+
the named-`code-verifier` entry in the Gotchas list below).
|
|
119
|
+
- **Shutdown:** at loop end (convergence or a stop condition), send each
|
|
120
|
+
persistent agent a shutdown request and clear `persistent_agents` before
|
|
121
|
+
the `pr-loop-lifecycle` Close.
|
|
98
122
|
|
|
99
123
|
## Gotchas
|
|
100
124
|
|
|
@@ -159,13 +183,9 @@ script invocations, decision criteria. Every "return to Step N" means the next
|
|
|
159
183
|
tick starts fresh from that step.
|
|
160
184
|
|
|
161
185
|
**Hard gate: do not advance from any step while ANY unresolved review
|
|
162
|
-
thread exists on the PR.** The thread
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
resolve the thread via `pull_request_review_write(method="resolve_thread")`.
|
|
166
|
-
For each unresolved thread, verify the concern against current HEAD;
|
|
167
|
-
either fix-and-resolve, or reply-with-note-and-resolve when the concern
|
|
168
|
-
no longer applies.
|
|
186
|
+
thread exists on the PR.** The sweep semantics and per-thread handling live
|
|
187
|
+
in the `pr-fix-protocol` skill's unresolved-thread sweep
|
|
188
|
+
(`../pr-fix-protocol/SKILL.md`).
|
|
169
189
|
|
|
170
190
|
**Full-PR-diff rule: every CODE-REVIEW round (Step 5) and every BUGTEAM
|
|
171
191
|
round (Step 6) covers the FULL `origin/main...HEAD` diff — every file
|
|
@@ -176,11 +196,13 @@ against a partial diff is not a valid clean. Re-run the round against the
|
|
|
176
196
|
full diff before recording `code_review_clean_at` or treating the bugteam
|
|
177
197
|
round as converged. This rule holds every tick, every loop, every PR.
|
|
178
198
|
|
|
179
|
-
- [ ] **Step 0:
|
|
180
|
-
`
|
|
199
|
+
- [ ] **Step 0: Open the run**
|
|
200
|
+
Apply the `pr-loop-lifecycle` skill's Open section
|
|
201
|
+
(`../pr-loop-lifecycle/SKILL.md`): permission grant + worktree preflight.
|
|
181
202
|
|
|
182
203
|
- [ ] **Step 1: Resolve PR scope + PR worktree**
|
|
183
|
-
|
|
204
|
+
Apply the `pr-scope-resolve` skill (`../pr-scope-resolve/SKILL.md`)
|
|
205
|
+
with caller `pr-converge`. Resolve the **PR
|
|
184
206
|
worktree** — the local checkout every local step this tick targets:
|
|
185
207
|
the `EnterWorktree` checkout when the PR shares the session's repo,
|
|
186
208
|
else a checkout of the PR's repo that the working directory routes
|
|
@@ -205,6 +227,8 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
205
227
|
See: [`reference/per-tick.md` § Step 2 BUGBOT + Step 3](reference/per-tick.md)
|
|
206
228
|
|
|
207
229
|
- [ ] **Opt-out gate (runs first, every BUGBOT entry).**
|
|
230
|
+
Gate semantics live in the `reviewer-gates` skill
|
|
231
|
+
(`../reviewer-gates/SKILL.md` § Gate 1).
|
|
208
232
|
`python "$HOME/.claude/_shared/pr-loop/scripts/reviews_disabled.py" --reviewer bugbot`
|
|
209
233
|
- [ ] Exit 0 (`CLAUDE_REVIEWS_DISABLED` lists `bugbot`) → set `bugbot_down = true`, `phase = CODE_REVIEW`, advance to Step 5 (bypass). Cursor Bugbot is skipped for the entire run.
|
|
210
234
|
- [ ] Exit 1 → continue below.
|
|
@@ -212,9 +236,7 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
212
236
|
Fetch bugbot reviews + inline comments on `current_head`.
|
|
213
237
|
|
|
214
238
|
- [ ] **dirty** (findings on `current_head`) →
|
|
215
|
-
- [ ]
|
|
216
|
-
- [ ] Reply to each finding comment via `python ~/.claude/skills/pr-converge/scripts/post_fix_reply.py`
|
|
217
|
-
- [ ] Resolve each addressed thread via `pull_request_review_write(method="resolve_thread")`
|
|
239
|
+
- [ ] Apply the `pr-fix-protocol` skill (`../pr-fix-protocol/SKILL.md`) to this tick's findings
|
|
218
240
|
- [ ] Push → return to Step 4
|
|
219
241
|
- [ ] **clean** (no findings on `current_head`) →
|
|
220
242
|
- [ ] Count ALL unresolved threads on PR (`is_resolved == false`) → zero? advance; >0? fix + resolve first
|
|
@@ -222,14 +244,12 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
222
244
|
- [ ] `phase = CODE_REVIEW`
|
|
223
245
|
- [ ] Advance to Step 5
|
|
224
246
|
- [ ] **no review yet / commit_id mismatch** →
|
|
225
|
-
|
|
226
|
-
-
|
|
227
|
-
|
|
228
|
-
- [ ]
|
|
229
|
-
- [ ]
|
|
230
|
-
- [ ]
|
|
231
|
-
- [ ] Exit non-zero → `bugbot_down = true` → `phase = CODE_REVIEW` → advance to Step 5 (bypass)
|
|
232
|
-
- [ ] Exit 0 → record `bugbot_acknowledged_at`, schedule 360s wakeup → return to Step 4
|
|
247
|
+
Apply the `reviewer-gates` skill's Bugbot flow
|
|
248
|
+
(`../reviewer-gates/SKILL.md` § Gate 3) against `current_head`,
|
|
249
|
+
then map its outcomes:
|
|
250
|
+
- [ ] Silent pass → `bugbot_clean_at = current_head` → `phase = CODE_REVIEW` → advance to Step 5
|
|
251
|
+
- [ ] Already queued, or trigger acknowledged → schedule 360s wakeup → return to Step 4 next tick
|
|
252
|
+
- [ ] Bugbot down → `bugbot_down = true` → `phase = CODE_REVIEW` → advance to Step 5 (bypass)
|
|
233
253
|
- [ ] **Step 5: CODE-REVIEW — run, fix, reset, advance**
|
|
234
254
|
See: [`reference/per-tick.md` § Step 2 CODE_REVIEW](reference/per-tick.md)
|
|
235
255
|
|
|
@@ -264,13 +284,13 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
264
284
|
- [ ] Re-trigger bugbot (Step 4 "no review yet" checklist)
|
|
265
285
|
- [ ] `phase = BUGBOT` → schedule 360s wakeup → return to Step 4
|
|
266
286
|
- [ ] **clean** (no changes applied) →
|
|
267
|
-
- [ ]
|
|
287
|
+
- [ ] Zero unresolved threads per the `pr-fix-protocol` sweep (`../pr-fix-protocol/SKILL.md`) → advance; else fix + resolve first (same skill)
|
|
268
288
|
- [ ] `code_review_clean_at = current_head`
|
|
269
289
|
- [ ] `phase = BUGTEAM` → advance to Step 6
|
|
270
290
|
|
|
271
291
|
- [ ] **Step 6: BUGTEAM — run, decide, fix, reply, resolve**
|
|
272
292
|
See: [`reference/per-tick.md` § Step 2 BUGTEAM](reference/per-tick.md);
|
|
273
|
-
[
|
|
293
|
+
[`../bugteam/SKILL.md`](../bugteam/SKILL.md)
|
|
274
294
|
|
|
275
295
|
Pre-condition: `code_review_clean_at == current_head`.
|
|
276
296
|
|
|
@@ -300,9 +320,7 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
300
320
|
- [ ] **converged + `bugbot_clean_at ≠ current_head`** →
|
|
301
321
|
`phase = BUGBOT` → schedule 360s wakeup → return to Step 4
|
|
302
322
|
- [ ] **findings without committed fixes** →
|
|
303
|
-
- [ ]
|
|
304
|
-
- [ ] Reply to each finding comment via `python ~/.claude/skills/pr-converge/scripts/post_fix_reply.py`
|
|
305
|
-
- [ ] Resolve each addressed thread via `pull_request_review_write(method="resolve_thread")`
|
|
323
|
+
- [ ] Apply the `pr-fix-protocol` skill (`../pr-fix-protocol/SKILL.md`) to the findings
|
|
306
324
|
- [ ] Push → `phase = BUGBOT` → return to Step 4
|
|
307
325
|
|
|
308
326
|
- [ ] **Step 7: Convergence gates**
|
|
@@ -317,15 +335,13 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
317
335
|
pull_request_read(method="get_review_comments")
|
|
318
336
|
→ filter threads where is_resolved == false
|
|
319
337
|
```
|
|
320
|
-
- [ ] Any unresolved? →
|
|
321
|
-
if still applies → Fix (spawn `clean-coder`) → reply → resolve;
|
|
322
|
-
if no longer applies → reply-with-note → resolve. Push if any code changed → return to Step 4
|
|
338
|
+
- [ ] Any unresolved? → apply the `pr-fix-protocol` skill's unresolved-thread sweep (`../pr-fix-protocol/SKILL.md`). Push if any code changed → return to Step 4
|
|
323
339
|
- [ ] When `copilot_down == true` (start-of-run quota pre-check), skip the Copilot fetch below — no request, no poll, no agent — and continue to gate (b); the Copilot gate is bypassed for the whole run.
|
|
324
340
|
- [ ] Fetch Copilot review on `current_head` (top-level review state — uses get_reviews, identifies by reviewer):
|
|
325
341
|
```
|
|
326
342
|
python ~/.claude/skills/pr-converge/scripts/fetch_copilot_reviews.py --owner <O> --repo <R> --pr-number <N>
|
|
327
343
|
```
|
|
328
|
-
- [ ] dirty →
|
|
344
|
+
- [ ] dirty → apply the `pr-fix-protocol` skill (`../pr-fix-protocol/SKILL.md`) → push → return to Step 4
|
|
329
345
|
- [ ] clean (no findings) → `copilot_clean_at = current_head` → gate (b)
|
|
330
346
|
- [ ] no review yet → gate (b)
|
|
331
347
|
|
|
@@ -357,10 +373,7 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
357
373
|
→ count threads where is_resolved == false
|
|
358
374
|
```
|
|
359
375
|
- [ ] zero unresolved → gate (e)
|
|
360
|
-
- [ ] unresolved →
|
|
361
|
-
still applies → Fix (spawn `clean-coder`) → reply → resolve;
|
|
362
|
-
no longer applies → reply-with-note → resolve.
|
|
363
|
-
Push if code changed → return to Step 4
|
|
376
|
+
- [ ] unresolved → apply the `pr-fix-protocol` skill's unresolved-thread sweep (`../pr-fix-protocol/SKILL.md`). Push if code changed → return to Step 4
|
|
364
377
|
|
|
365
378
|
**(e) Mark ready**
|
|
366
379
|
- [ ] When `copilot_down == true`, export `CLAUDE_REVIEWS_DISABLED="copilot"` in this tick's shell before the check below, so it bypasses the Copilot review gate and the pending-requested-reviews gate.
|
|
@@ -384,22 +397,22 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
384
397
|
- [ ] **clean (no findings)** →
|
|
385
398
|
`copilot_clean_at = current_head` → return to Step 7 (re-validate gates b, d, e)
|
|
386
399
|
- [ ] **dirty (findings present)** →
|
|
387
|
-
- [ ]
|
|
388
|
-
- [ ] Reply to each finding comment via `python ~/.claude/skills/pr-converge/scripts/post_fix_reply.py`
|
|
389
|
-
- [ ] Resolve each addressed thread via `pull_request_review_write(method="resolve_thread")`
|
|
400
|
+
- [ ] Apply the `pr-fix-protocol` skill (`../pr-fix-protocol/SKILL.md`) to the findings
|
|
390
401
|
- [ ] Push → `phase = BUGBOT` → return to Step 4
|
|
391
402
|
- [ ] **no review yet** →
|
|
392
403
|
increment `copilot_wait_count` → ≥ 3 = hard blocker → stop
|
|
393
404
|
schedule 360s wakeup → return to Step 7a next tick
|
|
394
405
|
|
|
395
406
|
- [ ] **Step 8: Clean working tree**
|
|
396
|
-
|
|
407
|
+
`pr-loop-lifecycle` Close (`../pr-loop-lifecycle/SKILL.md`).
|
|
408
|
+
See: [`pr-loop-lifecycle/reference/teardown-publish-permissions.md` § Clean working tree](../pr-loop-lifecycle/reference/teardown-publish-permissions.md)
|
|
397
409
|
|
|
398
410
|
- [ ] **Step 9: Rewrite PR description**
|
|
399
|
-
|
|
411
|
+
`pr-loop-lifecycle` Close.
|
|
412
|
+
See: [`pr-loop-lifecycle/reference/teardown-publish-permissions.md` § Publish the final PR description](../pr-loop-lifecycle/reference/teardown-publish-permissions.md)
|
|
400
413
|
|
|
401
|
-
- [ ] **Step 10: Revoke project permissions**
|
|
402
|
-
`
|
|
414
|
+
- [ ] **Step 10: Revoke project permissions (always)**
|
|
415
|
+
`pr-loop-lifecycle` Close (`../pr-loop-lifecycle/SKILL.md` § Close).
|
|
403
416
|
|
|
404
417
|
- [ ] **Step 11: Print final report**
|
|
405
418
|
Print this block verbatim — no paraphrase, no extra commentary:
|
|
@@ -8,7 +8,7 @@ Reference documents for the `pr-converge` skill. These files define the per-tick
|
|
|
8
8
|
|---|---|
|
|
9
9
|
| `per-tick.md` | Step-by-step procedure for one tick (resolve HEAD, run BUGBOT, CODE_REVIEW, BUGTEAM, COPILOT_WAIT phases, schedule next wakeup) |
|
|
10
10
|
| `convergence-gates.md` | Six gates that must all pass before the PR is marked ready for review |
|
|
11
|
-
| `fix-protocol.md` |
|
|
11
|
+
| `fix-protocol.md` | pr-converge deltas on the `pr-fix-protocol` skill: multi-PR teammate obligations and the same-tick bugbot re-trigger rule |
|
|
12
12
|
| `ground-rules.md` | Non-negotiable constraints for the convergence loop |
|
|
13
13
|
| `stop-conditions.md` | Conditions that end the loop without convergence (cap reached, stuck, user stop) |
|
|
14
14
|
| `state-schema.md` | Fields in `pr-converge-state.json` and their meanings |
|
|
@@ -35,22 +35,21 @@ Decide (four branches; match first whose predicate holds):
|
|
|
35
35
|
|
|
36
36
|
- **`classification == "dirty"` with non-empty inline comments matching
|
|
37
37
|
`pull_request_review_id`:** Fix protocol input (same shape as bugbot
|
|
38
|
-
dirty).
|
|
39
|
-
|
|
40
|
-
[Single-PR fix workflow](fix-protocol.md#single-pr-fix-workflow) for
|
|
41
|
-
full contract).
|
|
38
|
+
dirty). Apply the `pr-fix-protocol` skill
|
|
39
|
+
(`../../pr-fix-protocol/SKILL.md`) → Step 3 in same tick.
|
|
42
40
|
Reset `bugbot_clean_at = null` AND `copilot_clean_at = null`, `phase =
|
|
43
41
|
BUGBOT`, schedule next wakeup, return. Full back-to-back-clean cycle
|
|
44
42
|
plus all six gates must hold again on new HEAD.
|
|
45
43
|
- **`classification == "dirty"` with empty inline comments matching
|
|
46
44
|
`pull_request_review_id`:** Copilot posted findings only in review body
|
|
47
45
|
(`CHANGES_REQUESTED` or `COMMENTED` with non-empty body, no inline
|
|
48
|
-
threads). Parse body for actionable findings.
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
threads). Parse body for actionable findings. Apply the
|
|
47
|
+
`pr-fix-protocol` skill (`../../pr-fix-protocol/SKILL.md`), whose reply
|
|
48
|
+
step for body-only findings posts a top-level review reply citing the
|
|
49
|
+
new HEAD SHA → Step 3 in same tick. Reset
|
|
51
50
|
`bugbot_clean_at = null` AND
|
|
52
51
|
`copilot_clean_at = null`, `phase = BUGBOT`, Step 3 on new HEAD,
|
|
53
|
-
schedule next wakeup, return. Convergence
|
|
52
|
+
schedule next wakeup, return. Convergence needs full
|
|
54
53
|
back-to-back-clean on new HEAD.
|
|
55
54
|
- **`classification == "clean"` (state `APPROVED`):** Set
|
|
56
55
|
`copilot_clean_at = current_head`. Record evidence: "Copilot APPROVED at <SHA>".
|
|
@@ -77,14 +76,15 @@ Decide (four branches; match first whose predicate holds):
|
|
|
77
76
|
|
|
78
77
|
- **`classification == "dirty"` with non-empty inline comments matching
|
|
79
78
|
`pull_request_review_id`:** Treat identically to gate (a) dirty+inline
|
|
80
|
-
path —
|
|
79
|
+
path — apply the `pr-fix-protocol` skill
|
|
80
|
+
(`../../pr-fix-protocol/SKILL.md`). Reset
|
|
81
81
|
`bugbot_clean_at = null` AND `copilot_clean_at = null`, `phase = BUGBOT`,
|
|
82
82
|
schedule next wakeup, return.
|
|
83
83
|
- **`classification == "dirty"` with empty inline comments matching
|
|
84
84
|
`pull_request_review_id`:** Claude posted findings only in review body
|
|
85
85
|
(`CHANGES_REQUESTED` or `COMMENTED` with non-empty body, no inline
|
|
86
|
-
threads). Treat identically to gate (a) dirty+body path —
|
|
87
|
-
|
|
86
|
+
threads). Treat identically to gate (a) dirty+body path — apply the
|
|
87
|
+
`pr-fix-protocol` skill (`../../pr-fix-protocol/SKILL.md`). Reset
|
|
88
88
|
`bugbot_clean_at = null` AND `copilot_clean_at = null`, `phase = BUGBOT`,
|
|
89
89
|
schedule next wakeup, return.
|
|
90
90
|
- **`classification == "clean"` (state `APPROVED`):** Record evidence:
|
|
@@ -176,19 +176,16 @@ pull_request_read(owner=OWNER, repo=REPO, pullNumber=NUMBER, method="get_review_
|
|
|
176
176
|
→ count
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
filter on those fields.
|
|
179
|
+
This count is the `pr-fix-protocol` unresolved-thread sweep
|
|
180
|
+
(`../../pr-fix-protocol/SKILL.md`).
|
|
182
181
|
|
|
183
182
|
Decide:
|
|
184
183
|
|
|
185
184
|
- **Zero unresolved threads:** Record evidence:
|
|
186
185
|
"0 unresolved threads across PR at <SHA>". Continue to gate (f).
|
|
187
|
-
- **One or more unresolved threads:** Do **not** mark ready.
|
|
188
|
-
unresolved
|
|
189
|
-
|
|
190
|
-
no longer applies (e.g. code already changed), reply-with-note
|
|
191
|
-
explaining why and resolve. Push if any code changed → reset
|
|
186
|
+
- **One or more unresolved threads:** Do **not** mark ready. Apply the
|
|
187
|
+
`pr-fix-protocol` skill's unresolved-thread sweep
|
|
188
|
+
(`../../pr-fix-protocol/SKILL.md`). Push if any code changed → reset
|
|
192
189
|
`bugbot_clean_at = null` AND `copilot_clean_at = null`,
|
|
193
190
|
`phase = BUGBOT`, schedule next wakeup, return. If only resolutions
|
|
194
191
|
(no code changes), re-check this gate without resetting.
|
|
@@ -54,9 +54,8 @@ sufficient. Runs `convergence-gates.md` gates in order:
|
|
|
54
54
|
Next tick: re-run gate (a) fetch → Copilot `APPROVED` at `current_head`
|
|
55
55
|
→ set `copilot_clean_at = current_head`, record evidence: "Copilot
|
|
56
56
|
APPROVED at <SHA>", set `phase = BUGTEAM`, re-validate gates (b) and (c).
|
|
57
|
-
Gate (e): `
|
|
58
|
-
|
|
59
|
-
filter) → zero across PR → record evidence.
|
|
57
|
+
Gate (e): the `pr-fix-protocol` unresolved-thread sweep
|
|
58
|
+
(`../../pr-fix-protocol/SKILL.md`) → zero across PR → record evidence.
|
|
60
59
|
Gate (f): all six gates pass → `update_pull_request(pullNumber=NUMBER,
|
|
61
60
|
owner=OWNER, repo=REPO, draft=false)`.
|
|
62
61
|
Reports "PR #N converged: bugbot CLEAN at <SHA>, bugteam CLEAN at <SHA>,
|
|
@@ -100,8 +99,9 @@ Copilot review `state: APPROVED` at `current_head`. Claude: [re-runs
|
|
|
100
99
|
gate (a) fetch → APPROVED → sets `copilot_clean_at = current_head`,
|
|
101
100
|
records evidence: "Copilot APPROVED at <SHA>", sets `phase = BUGTEAM`;
|
|
102
101
|
re-validates gates (b) Claude absent and (c) mergeability clean,
|
|
103
|
-
records evidence for both; gate (e)
|
|
104
|
-
trivially, record evidence:
|
|
102
|
+
records evidence for both; gate (e) — the `pr-fix-protocol`
|
|
103
|
+
unresolved-thread sweep — passes trivially, record evidence:
|
|
104
|
+
"0 unresolved threads across PR at <SHA>";
|
|
105
105
|
runs `update_pull_request(pullNumber=NUMBER,
|
|
106
106
|
owner=OWNER, repo=REPO, draft=false)`; reports "PR #N converged: bugbot CLEAN
|
|
107
107
|
at <SHA>, bugteam CLEAN at <SHA>, mergeable_state clean, copilot CLEAN at
|