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.
Files changed (217) hide show
  1. package/CLAUDE.md +16 -13
  2. package/_shared/pr-loop/audit-contract.md +24 -12
  3. package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  4. package/_shared/pr-loop/scripts/README.md +1 -0
  5. package/_shared/pr-loop/scripts/_claude_permissions_common.py +16 -5
  6. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -3
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
  8. package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviewer_availability_constants.py +12 -0
  9. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +0 -2
  10. package/_shared/pr-loop/scripts/reviewer_availability.py +182 -0
  11. package/_shared/pr-loop/scripts/reviews_disabled.py +2 -0
  12. package/_shared/pr-loop/scripts/terminology_sweep.py +9 -33
  13. package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
  14. package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +212 -0
  15. package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +18 -0
  16. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +18 -0
  17. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +37 -0
  18. package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +159 -0
  19. package/_shared/pr-loop/scripts/tests/test_reviewer_availability_constants.py +36 -0
  20. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +14 -0
  21. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +14 -4
  22. package/agents/clean-coder.md +3 -0
  23. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
  24. package/bin/install.mjs +16 -8
  25. package/bin/install.test.mjs +17 -6
  26. package/commands/CLAUDE.md +0 -1
  27. package/docs/CODE_RULES.md +1 -1
  28. package/hooks/blocking/CLAUDE.md +2 -4
  29. package/hooks/blocking/code_rules_constants_config.py +164 -1
  30. package/hooks/blocking/code_rules_docstrings.py +428 -15
  31. package/hooks/blocking/code_rules_enforcer.py +33 -0
  32. package/hooks/blocking/code_rules_imports_logging.py +867 -1
  33. package/hooks/blocking/code_rules_magic_values.py +5 -0
  34. package/hooks/blocking/code_rules_naming_collection.py +152 -6
  35. package/hooks/blocking/code_rules_shared.py +34 -0
  36. package/hooks/blocking/code_rules_string_magic.py +68 -0
  37. package/hooks/blocking/duplicate_rmtree_helper_blocker.py +4 -4
  38. package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
  39. package/hooks/blocking/reviewer_spawn_gate.py +182 -0
  40. package/hooks/blocking/stale_comment_reference_blocker.py +267 -0
  41. package/hooks/blocking/state_description_blocker.py +96 -5
  42. package/hooks/blocking/test_code_rules_config_duplicate_path_anchor.py +132 -0
  43. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +2 -0
  44. package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +385 -0
  45. package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +197 -0
  46. package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +27 -0
  47. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +67 -0
  48. package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +40 -0
  49. package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +213 -0
  50. package/hooks/blocking/test_code_rules_enforcer_referenced_underscore_loop.py +169 -0
  51. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +33 -0
  52. package/hooks/blocking/test_code_rules_enforcer_split_shared.py +23 -0
  53. package/hooks/blocking/test_code_rules_js_bare_flag_return_directive.py +266 -0
  54. package/hooks/blocking/test_code_rules_js_sibling_return_object_key_drift.py +490 -0
  55. package/hooks/blocking/test_code_rules_logging_adjacent_literals.py +171 -0
  56. package/hooks/blocking/test_code_rules_magic_values.py +54 -0
  57. package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +0 -6
  58. package/hooks/blocking/test_pr_converge_bugteam_enforcer_state_tolerance.py +184 -0
  59. package/hooks/blocking/test_pre_tool_use_dispatcher.py +9 -3
  60. package/hooks/blocking/test_reviewer_spawn_gate.py +230 -0
  61. package/hooks/blocking/test_shared_stdin_adoption.py +5 -30
  62. package/hooks/blocking/test_stale_comment_reference_blocker.py +236 -0
  63. package/hooks/blocking/test_state_description_blocker.py +135 -0
  64. package/hooks/hooks.json +5 -0
  65. package/hooks/hooks_constants/CLAUDE.md +3 -4
  66. package/hooks/hooks_constants/blocking_check_limits.py +46 -0
  67. package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -0
  68. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
  69. package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +0 -1
  70. package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -5
  71. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  72. package/hooks/hooks_constants/reviewer_spawn_gate_constants.py +41 -0
  73. package/hooks/hooks_constants/stale_comment_reference_blocker_constants.py +76 -0
  74. package/hooks/hooks_constants/state_description_blocker_constants.py +8 -0
  75. package/hooks/validation/post_tool_use_dispatcher.py +1 -1
  76. package/hooks/validation/test_post_tool_use_dispatcher.py +51 -35
  77. package/hooks/workflow/CLAUDE.md +2 -8
  78. package/package.json +1 -1
  79. package/rules/CLAUDE.md +5 -2
  80. package/rules/claude-md-orphan-file.md +5 -0
  81. package/rules/docstring-prose-matches-implementation.md +10 -1
  82. package/rules/env-var-table-code-drift.md +5 -0
  83. package/rules/es-exe-file-search.md +17 -0
  84. package/rules/no-historical-clutter.md +12 -1
  85. package/rules/orphan-css-class.md +5 -0
  86. package/rules/package-inventory-stale-entry.md +10 -0
  87. package/rules/paired-test-coverage.md +5 -0
  88. package/rules/plain-illustrative-docstrings.md +40 -1
  89. package/rules/verify-before-asking.md +7 -0
  90. package/rules/verify-runtime-state.md +40 -0
  91. package/rules/windows-filesystem-safe.md +8 -0
  92. package/rules/workers-done-before-complete.md +33 -0
  93. package/rules/workflow-substitution-slots.md +5 -0
  94. package/skills/CLAUDE.md +7 -2
  95. package/skills/autoconverge/SKILL.md +48 -61
  96. package/skills/autoconverge/reference/closing-report.md +6 -6
  97. package/skills/autoconverge/reference/convergence.md +17 -15
  98. package/skills/autoconverge/reference/gotchas.md +6 -3
  99. package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +2 -9
  100. package/skills/autoconverge/workflow/converge.contract.test.mjs +153 -10
  101. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +55 -19
  102. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +16 -9
  103. package/skills/autoconverge/workflow/converge.mjs +167 -106
  104. package/skills/autoconverge/workflow/render_report.py +7 -11
  105. package/skills/bugteam/CLAUDE.md +1 -1
  106. package/skills/bugteam/PROMPTS.md +7 -6
  107. package/skills/bugteam/SKILL.md +25 -72
  108. package/skills/bugteam/reference/CLAUDE.md +1 -3
  109. package/skills/bugteam/reference/README.md +1 -1
  110. package/skills/bugteam/reference/audit-and-teammates.md +1 -1
  111. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +1 -1
  112. package/skills/bugteam/reference/team-setup.md +8 -7
  113. package/skills/bugteam/scripts/CLAUDE.md +0 -6
  114. package/skills/bugteam/scripts/README.md +0 -4
  115. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +0 -1
  116. package/skills/code/SKILL.md +2 -0
  117. package/skills/copilot-review/CLAUDE.md +1 -1
  118. package/skills/copilot-review/SKILL.md +25 -23
  119. package/skills/everything-search/SKILL.md +5 -0
  120. package/skills/findbugs/CLAUDE.md +2 -2
  121. package/skills/findbugs/SKILL.md +22 -83
  122. package/skills/fixbugs/SKILL.md +2 -4
  123. package/skills/log-audit/CLAUDE.md +20 -0
  124. package/skills/log-audit/SKILL.md +68 -0
  125. package/skills/log-audit/reference/CLAUDE.md +9 -0
  126. package/skills/log-audit/reference/charter.md +52 -0
  127. package/skills/log-audit/scripts/CLAUDE.md +27 -0
  128. package/skills/log-audit/scripts/cluster_recurrences.py +261 -0
  129. package/skills/log-audit/scripts/collect_log_window.py +199 -0
  130. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +12 -0
  131. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +23 -0
  132. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +24 -0
  133. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +49 -0
  134. package/skills/log-audit/scripts/mine_copilot_findings.py +302 -0
  135. package/skills/log-audit/scripts/test_cluster_recurrences.py +160 -0
  136. package/skills/log-audit/scripts/test_collect_log_window.py +111 -0
  137. package/skills/log-audit/scripts/test_mine_copilot_findings.py +126 -0
  138. package/skills/monitor-open-prs/SKILL.md +2 -2
  139. package/skills/post-audit-findings/SKILL.md +84 -0
  140. package/skills/pr-converge/CLAUDE.md +2 -0
  141. package/skills/pr-converge/SKILL.md +72 -59
  142. package/skills/pr-converge/reference/CLAUDE.md +1 -1
  143. package/skills/pr-converge/reference/convergence-gates.md +16 -19
  144. package/skills/pr-converge/reference/examples.md +5 -5
  145. package/skills/pr-converge/reference/fix-protocol.md +16 -43
  146. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +1 -1
  147. package/skills/pr-converge/reference/per-tick.md +24 -45
  148. package/skills/pr-converge/reference/state-schema.md +15 -0
  149. package/skills/pr-converge/scripts/README.md +3 -5
  150. package/skills/pr-fix-protocol/SKILL.md +70 -0
  151. package/skills/pr-loop-lifecycle/SKILL.md +73 -0
  152. package/skills/{bugteam → pr-loop-lifecycle}/reference/teardown-publish-permissions.md +22 -24
  153. package/skills/pr-scope-resolve/SKILL.md +48 -0
  154. package/skills/qbug/CLAUDE.md +4 -4
  155. package/skills/qbug/SKILL.md +46 -144
  156. package/skills/qbug/test_qbug_skill_audit_schema.py +2 -2
  157. package/skills/qbug/test_qbug_skill_post_fix_audit.py +1 -1
  158. package/skills/reviewer-gates/SKILL.md +96 -0
  159. package/skills/session-log/CLAUDE.md +7 -7
  160. package/skills/session-log/SKILL.md +27 -44
  161. package/skills/test_markdown_link_integrity.py +103 -0
  162. package/commands/doc-gist.md +0 -16
  163. package/hooks/blocking/_md_to_html_blocker_test_support.py +0 -65
  164. package/hooks/blocking/conftest.py +0 -30
  165. package/hooks/blocking/md_path_exemptions.py +0 -224
  166. package/hooks/blocking/md_to_html_blocker.py +0 -155
  167. package/hooks/blocking/test_md_to_html_blocker_exemptions.py +0 -434
  168. package/hooks/blocking/test_md_to_html_blocker_extensions.py +0 -157
  169. package/hooks/blocking/test_md_to_html_blocker_path_resolution.py +0 -336
  170. package/hooks/hooks_constants/doc_gist_auto_publish_constants.py +0 -18
  171. package/hooks/hooks_constants/html_companion_constants.py +0 -20
  172. package/hooks/hooks_constants/md_to_html_blocker_constants.py +0 -76
  173. package/hooks/hooks_constants/test_md_to_html_blocker_constants.py +0 -125
  174. package/hooks/workflow/doc_gist_auto_publish.py +0 -144
  175. package/hooks/workflow/md_to_html_companion.py +0 -358
  176. package/hooks/workflow/test_doc_gist_auto_publish.py +0 -117
  177. package/hooks/workflow/test_md_to_html_companion.py +0 -613
  178. package/skills/bugteam/reference/audit-contract.md +0 -163
  179. package/skills/bugteam/scripts/_bugteam_permissions_common.py +0 -455
  180. package/skills/bugteam/scripts/bugteam_scripts_constants/claude_permissions_common_constants.py +0 -69
  181. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -280
  182. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -266
  183. package/skills/bugteam/scripts/test__bugteam_permissions_common.py +0 -160
  184. package/skills/bugteam/scripts/test_agent_config_carveout.py +0 -356
  185. package/skills/bugteam/scripts/test_bugteam_permissions_common.py +0 -140
  186. package/skills/doc-gist/CLAUDE.md +0 -25
  187. package/skills/doc-gist/SKILL.md +0 -97
  188. package/skills/doc-gist/references/CLAUDE.md +0 -9
  189. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +0 -453
  190. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +0 -515
  191. package/skills/doc-gist/references/examples/03-code-review-pr.html +0 -638
  192. package/skills/doc-gist/references/examples/04-code-understanding.html +0 -491
  193. package/skills/doc-gist/references/examples/05-design-system.html +0 -629
  194. package/skills/doc-gist/references/examples/06-component-variants.html +0 -605
  195. package/skills/doc-gist/references/examples/07-prototype-animation.html +0 -455
  196. package/skills/doc-gist/references/examples/08-prototype-interaction.html +0 -396
  197. package/skills/doc-gist/references/examples/09-slide-deck.html +0 -592
  198. package/skills/doc-gist/references/examples/10-svg-illustrations.html +0 -492
  199. package/skills/doc-gist/references/examples/11-status-report.html +0 -528
  200. package/skills/doc-gist/references/examples/12-incident-report.html +0 -596
  201. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +0 -395
  202. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +0 -381
  203. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +0 -368
  204. package/skills/doc-gist/references/examples/16-implementation-plan.html +0 -702
  205. package/skills/doc-gist/references/examples/17-pr-writeup.html +0 -595
  206. package/skills/doc-gist/references/examples/18-editor-triage-board.html +0 -573
  207. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +0 -663
  208. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +0 -722
  209. package/skills/doc-gist/references/examples/21-decision-signoff.html +0 -546
  210. package/skills/doc-gist/references/examples/CLAUDE.md +0 -25
  211. package/skills/doc-gist/references/examples/README.md +0 -5
  212. package/skills/doc-gist/scripts/CLAUDE.md +0 -27
  213. package/skills/doc-gist/scripts/doc_gist_scripts_constants/CLAUDE.md +0 -10
  214. package/skills/doc-gist/scripts/doc_gist_scripts_constants/gist_upload_constants.py +0 -16
  215. package/skills/doc-gist/scripts/gist_upload.py +0 -177
  216. package/skills/doc-gist/scripts/test_gist_upload.py +0 -51
  217. /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) contains the token `bugteam`: `/monitor-open-prs is a /bugteam dispatcher and /bugteam is disabled via CLAUDE_REVIEWS_DISABLED.`
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 `CLAUDE_REVIEWS_DISABLED` (comma-separated, case-insensitive, whitespace-tolerant) contains the token `bugbot`, omit `--bugbot-retrigger` from the dispatched command so the bugbot leg sits out the run.
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, before Step 4, run the Copilot quota pre-check once:
49
- `python "$HOME/.claude/_shared/pr-loop/scripts/copilot_quota.py"`. It reads the
50
- account's remaining Copilot premium-request quota via `gh api
51
- copilot_internal/user` and prints one line log that line. Exit 0 means Copilot
52
- has quota, so leave `copilot_down` false. Any non-zero exit means skip Copilot
53
- for the whole run the account is out of quota, the quota API or account access
54
- is down, or no account is set so set `copilot_down = true` in
55
- `pr-converge-state.json`. The account comes from the `COPILOT_QUOTA_ACCOUNT`
56
- environment variable or a git-ignored `.env` file, and the no-account line names
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-count filter is purely
163
- `is_resolved == false` author, commit anchor, and `is_outdated` are
164
- all irrelevant. After every fix, reply to each finding comment and
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: Grant project permissions**
180
- `python "$HOME/.claude/skills/bugteam/scripts/grant_project_claude_permissions.py"`
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
- Capture owner, repo, number, head SHA, branch. Resolve the **PR
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
- - [ ] Fix each finding (spawn `clean-coder`)
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
- - [ ] Run `python ~/.claude/skills/pr-converge/scripts/check_bugbot_ci.py --owner <O> --repo <R> --check-clean --sha <current_head>`
226
- - [ ] Exit 0 (bugbot CI completed with success/neutral conclusion and no review = silent pass) `bugbot_clean_at = current_head` → `phase = CODE_REVIEW` → advance to Step 5
227
- - [ ] Exit 1 (not a silent pass) or Exit 2 (gh CLI error — silent pass not confirmable) → Run `python ~/.claude/skills/pr-converge/scripts/check_bugbot_ci.py --owner <O> --repo <R> --check-active --sha <current_head>`
228
- - [ ] Exit 0 (already queued)schedule 360s wakeupreturn to Step 4 next tick
229
- - [ ] Exit 1 post exactly `bugbot run` via `add_issue_comment` (no `@cursor[bot]` mention, no other text), wait 8s
230
- - [ ] Run `python ~/.claude/skills/pr-converge/scripts/check_bugbot_ci.py --owner <O> --repo <R> --sha <current_head>`
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
- - [ ] Count ALL unresolved threads on PR (`is_resolved == false`) → zero? advance; >0? fix + resolve first
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
- [`../../bugteam/SKILL.md`](../../bugteam/SKILL.md)
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
- - [ ] Fix each finding (spawn `clean-coder`)
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? → For each: verify concern against current HEAD;
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 → Fix (spawn `clean-coder`) replyresolve threads → push → return to Step 4
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 → For each: verify against HEAD;
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
- - [ ] Fix each finding (spawn `clean-coder`)
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
- See: [`bugteam/reference/teardown-publish-permissions.md` § Step 4](../../bugteam/reference/teardown-publish-permissions.md)
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
- See: [`bugteam/reference/teardown-publish-permissions.md` § Step 4.5](../../bugteam/reference/teardown-publish-permissions.md)
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
- `python "$HOME/.claude/skills/bugteam/scripts/revoke_project_claude_permissions.py"`
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` | How to apply fixes: TDD first, one commit per fix round, reply to each finding inline |
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). Spawn Agent (subagent_type: clean-coder) to implement → push → reply inline on each thread via
39
- `python ~/.claude/skills/pr-converge/scripts/post_fix_reply.py` → Step 3 in same tick (see
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. Spawn Agent (subagent_type: clean-coder) to implement → push → post
49
- top-level review reply via `python ~/.claude/skills/pr-converge/scripts/post_fix_reply.py` citing new HEAD SHA → Step 3 in same tick.
50
- Reset
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 requires full
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 — spawn Agent (subagent_type: clean-coder) to fix → push → reply inline on each thread. Reset
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 — spawn Agent
87
- (subagent_type: clean-coder) to implement → push → post top-level review reply via `python ~/.claude/skills/pr-converge/scripts/post_fix_reply.py`. Reset
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
- When you address an unresolved thread, you still need its author and
180
- anchor commit so you can decide how to fix it — but the gate doesn't
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. For each
188
- unresolved thread, verify the concern against current HEAD. If still
189
- applies, apply Fix protocol (test fix pushreply → resolve). If
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 changedreset
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): `pull_request_read(method="get_review_comments")` count
58
- threads where `is_resolved == false` (no author/commit/outdated
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) zero unresolved threads passes
104
- trivially, record evidence: "0 unresolved threads across PR at <SHA>";
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