claude-dev-env 1.38.1 → 1.40.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 (282) hide show
  1. package/CLAUDE.md +10 -36
  2. package/_shared/pr-loop/audit-reply-template.md +147 -0
  3. package/_shared/pr-loop/fix-protocol.md +25 -4
  4. package/_shared/pr-loop/gh-payloads.md +37 -50
  5. package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
  6. package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +199 -0
  7. package/_shared/pr-loop/scripts/config/reviews_disabled_constants.py +8 -0
  8. package/_shared/pr-loop/scripts/post_audit_thread.py +1242 -0
  9. package/_shared/pr-loop/scripts/preflight.py +129 -2
  10. package/_shared/pr-loop/scripts/reviews_disabled.py +59 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
  12. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1116 -0
  13. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
  14. package/_shared/pr-loop/scripts/tests/test_preflight.py +41 -0
  15. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +36 -0
  16. package/_shared/pr-loop/state-schema.md +1 -1
  17. package/agents/clean-coder.md +2 -2
  18. package/agents/pr-description-writer.md +150 -52
  19. package/bin/install.mjs +6 -7
  20. package/bin/install.test.mjs +8 -0
  21. package/commands/doc-gist.md +16 -0
  22. package/commands/plan.md +0 -2
  23. package/commands/review-plan.md +1 -1
  24. package/docs/CODE_RULES.md +122 -2
  25. package/docs/PR_DESCRIPTION_GUIDE.md +127 -64
  26. package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
  27. package/hooks/blocking/code_rules_enforcer.py +1143 -129
  28. package/hooks/blocking/convergence_gate_blocker.py +130 -0
  29. package/hooks/blocking/destructive_command_blocker.py +74 -0
  30. package/hooks/blocking/gh_body_arg_blocker.py +30 -0
  31. package/hooks/blocking/md_to_html_blocker.py +119 -0
  32. package/hooks/blocking/pr_description_enforcer.py +57 -22
  33. package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
  34. package/hooks/blocking/test_code_rules_enforcer.py +21 -0
  35. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
  36. package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
  37. package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
  38. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
  39. package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
  40. package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
  41. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  42. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
  43. package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
  44. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
  45. package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
  46. package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
  47. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
  48. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
  49. package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
  50. package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
  51. package/hooks/blocking/test_destructive_command_blocker.py +146 -0
  52. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
  53. package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
  54. package/hooks/blocking/test_md_to_html_blocker.py +317 -0
  55. package/hooks/blocking/test_pr_description_enforcer.py +69 -8
  56. package/hooks/config/any_type_config.py +7 -0
  57. package/hooks/config/banned_identifiers_constants.py +11 -0
  58. package/hooks/config/blocking_check_limits.py +38 -0
  59. package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
  60. package/hooks/config/code_rules_enforcer_constants.py +53 -0
  61. package/hooks/config/convergence_branch_constants.py +9 -0
  62. package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
  63. package/hooks/config/html_companion_constants.py +20 -0
  64. package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
  65. package/hooks/config/pr_description_enforcer_constants.py +14 -0
  66. package/hooks/config/test_banned_identifiers_constants.py +17 -0
  67. package/hooks/hooks.json +28 -20
  68. package/hooks/pyproject.toml +69 -0
  69. package/hooks/validators/mypy_integration.py +47 -1
  70. package/hooks/validators/run_all_validators.py +3 -3
  71. package/hooks/validators/test_mypy_integration.py +50 -1
  72. package/hooks/workflow/doc_gist_auto_publish.py +144 -0
  73. package/hooks/workflow/md_to_html_companion.py +365 -0
  74. package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
  75. package/hooks/workflow/test_md_to_html_companion.py +452 -0
  76. package/package.json +1 -1
  77. package/rules/gh-body-file.md +2 -0
  78. package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
  79. package/scripts/check.ps1 +106 -0
  80. package/scripts/config/timing.py +11 -0
  81. package/scripts/sweep_empty_dirs.py +138 -0
  82. package/scripts/sync_to_cursor/rules.py +1 -1
  83. package/scripts/test_sweep_empty_dirs.py +183 -0
  84. package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
  85. package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
  86. package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
  87. package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
  88. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
  89. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
  90. package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
  91. package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
  92. package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
  93. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
  94. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
  95. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
  96. package/skills/bugteam/CONSTRAINTS.md +21 -22
  97. package/skills/bugteam/EXAMPLES.md +3 -3
  98. package/skills/bugteam/PROMPTS.md +227 -67
  99. package/skills/bugteam/SKILL.md +132 -455
  100. package/skills/bugteam/reference/README.md +1 -1
  101. package/skills/bugteam/reference/audit-and-teammates.md +112 -39
  102. package/skills/bugteam/reference/audit-contract.md +4 -22
  103. package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
  104. package/skills/bugteam/reference/design-rationale.md +2 -2
  105. package/skills/bugteam/reference/github-pr-reviews.md +50 -57
  106. package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
  107. package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
  108. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
  109. package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
  110. package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
  111. package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
  112. package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
  113. package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
  114. package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
  115. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
  116. package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
  117. package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
  118. package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
  119. package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
  120. package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
  121. package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
  122. package/skills/bugteam/reference/team-setup.md +111 -9
  123. package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
  124. package/skills/bugteam/scripts/README.md +60 -0
  125. package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
  126. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
  127. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
  128. package/skills/bugteam/scripts/bugteam_preflight.py +328 -0
  129. package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
  130. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
  131. package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
  132. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
  133. package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
  134. package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
  135. package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
  136. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
  137. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
  138. package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
  139. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
  140. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
  141. package/skills/bugteam/scripts/test_bugteam_preflight.py +309 -0
  142. package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
  143. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
  144. package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
  145. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
  146. package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
  147. package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
  148. package/skills/bugteam/test_skill_additions.py +1 -11
  149. package/skills/code/SKILL.md +176 -0
  150. package/skills/copilot-review/SKILL.md +16 -0
  151. package/skills/doc-gist/SKILL.md +99 -0
  152. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
  153. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
  154. package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
  155. package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
  156. package/skills/doc-gist/references/examples/05-design-system.html +629 -0
  157. package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
  158. package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
  159. package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
  160. package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
  161. package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
  162. package/skills/doc-gist/references/examples/11-status-report.html +528 -0
  163. package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
  164. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
  165. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
  166. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
  167. package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
  168. package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
  169. package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
  170. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
  171. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
  172. package/skills/doc-gist/references/examples/README.md +5 -0
  173. package/skills/doc-gist/scripts/config/__init__.py +0 -0
  174. package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
  175. package/skills/doc-gist/scripts/gist_upload.py +177 -0
  176. package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
  177. package/skills/findbugs/SKILL.md +96 -2
  178. package/skills/monitor-open-prs/SKILL.md +14 -32
  179. package/skills/monitor-open-prs/test_skill_contract.py +0 -11
  180. package/skills/pr-consistency-audit/SKILL.md +112 -0
  181. package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
  182. package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
  183. package/skills/pr-converge/SKILL.md +229 -23
  184. package/skills/pr-converge/config/__init__.py +0 -0
  185. package/skills/pr-converge/config/constants.py +63 -0
  186. package/skills/pr-converge/reference/convergence-gates.md +138 -44
  187. package/skills/pr-converge/reference/examples.md +43 -11
  188. package/skills/pr-converge/reference/fix-protocol.md +6 -5
  189. package/skills/pr-converge/reference/ground-rules.md +5 -3
  190. package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
  191. package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
  192. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
  193. package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
  194. package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
  195. package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
  196. package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
  197. package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
  198. package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
  199. package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
  200. package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
  201. package/skills/pr-converge/reference/per-tick.md +107 -31
  202. package/skills/pr-converge/reference/state-schema.md +22 -1
  203. package/skills/pr-converge/reference/stop-conditions.md +9 -7
  204. package/skills/pr-converge/scripts/README.md +34 -46
  205. package/skills/pr-converge/scripts/check_bugbot_ci.py +279 -0
  206. package/skills/pr-converge/scripts/check_convergence.py +497 -0
  207. package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
  208. package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
  209. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
  210. package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
  211. package/skills/pr-converge/scripts/test_check_bugbot_ci.py +312 -0
  212. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
  213. package/skills/qbug/SKILL.md +157 -27
  214. package/skills/session-log/SKILL.md +216 -114
  215. package/skills/session-tidy/SKILL.md +1 -1
  216. package/skills/skill-builder/SKILL.md +138 -56
  217. package/skills/skill-builder/references/delegation-map.md +72 -113
  218. package/skills/skill-builder/references/progressive-disclosure.md +122 -0
  219. package/skills/skill-builder/references/self-audit-checklist.md +92 -0
  220. package/skills/skill-builder/references/skill-types.md +228 -0
  221. package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
  222. package/skills/skill-builder/templates/gap-analysis.md +15 -8
  223. package/skills/skill-builder/workflows/improve-skill.md +86 -57
  224. package/skills/skill-builder/workflows/new-skill.md +80 -168
  225. package/skills/skill-builder/workflows/polish-skill.md +78 -54
  226. package/skills/structure-prompt/SKILL.md +50 -0
  227. package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
  228. package/skills/structure-prompt/reference/block-classification.md +27 -0
  229. package/skills/structure-prompt/reference/canonical-case.md +48 -0
  230. package/skills/structure-prompt/reference/citation-depth.md +70 -0
  231. package/skills/structure-prompt/reference/cleanup.md +33 -0
  232. package/skills/structure-prompt/reference/constraints.md +33 -0
  233. package/skills/structure-prompt/reference/directives.md +37 -0
  234. package/skills/structure-prompt/reference/examples.md +72 -0
  235. package/skills/structure-prompt/reference/instantiation.md +51 -0
  236. package/skills/structure-prompt/reference/output-contract.md +72 -0
  237. package/skills/structure-prompt/reference/per-category.md +23 -0
  238. package/skills/structure-prompt/reference/persona.md +38 -0
  239. package/skills/structure-prompt/reference/research.md +33 -0
  240. package/skills/structure-prompt/reference/structure.md +28 -0
  241. package/agents/code-standards-agent.md +0 -93
  242. package/agents/groq-coder.md +0 -113
  243. package/agents/plan-executor.md +0 -226
  244. package/agents/project-docs-analyzer.md +0 -53
  245. package/agents/project-structure-organizer-agent.md +0 -72
  246. package/agents/skill-to-agent-converter.md +0 -370
  247. package/agents/skill-writer-agent.md +0 -470
  248. package/agents/user-docs-writer.md +0 -67
  249. package/agents/workflow-visual-documenter.md +0 -82
  250. package/commands/readability-review.md +0 -20
  251. package/hooks/mypy.ini +0 -2
  252. package/hooks/notification/attention_needed_notify.py +0 -71
  253. package/hooks/notification/claude_notification_handler.py +0 -67
  254. package/hooks/notification/notification_utils.py +0 -267
  255. package/hooks/notification/subagent_complete_notify.py +0 -381
  256. package/hooks/notification/test_attention_needed_notify.py +0 -47
  257. package/hooks/notification/test_claude_notification_handler.py +0 -54
  258. package/hooks/notification/test_notification_utils.py +0 -91
  259. package/hooks/notification/test_subagent_complete_notify.py +0 -79
  260. package/scripts/config/groq_bugteam_config.py +0 -230
  261. package/scripts/config/test_groq_bugteam_config.py +0 -83
  262. package/scripts/config/test_spec_implementer_prompt.py +0 -32
  263. package/scripts/groq_bugteam.README.md +0 -131
  264. package/scripts/groq_bugteam.py +0 -647
  265. package/scripts/groq_bugteam_dotenv.py +0 -40
  266. package/scripts/groq_bugteam_spec.py +0 -226
  267. package/scripts/test_groq_bugteam.py +0 -529
  268. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
  269. package/scripts/test_groq_bugteam_dotenv.py +0 -66
  270. package/scripts/test_groq_bugteam_spec.py +0 -338
  271. package/skills/bugteam/SKILL_EVALS.md +0 -309
  272. package/skills/dream/SKILL.md +0 -118
  273. package/skills/ingest/SKILL.md +0 -40
  274. package/skills/npm-creator/SKILL.md +0 -187
  275. package/skills/readability-review/SKILL.md +0 -127
  276. package/skills/resume-review/SKILL.md +0 -261
  277. package/skills/rule-audit/SKILL.md +0 -307
  278. package/skills/rule-creator/SKILL.md +0 -150
  279. package/skills/searching-obsidian-vault/SKILL.md +0 -131
  280. package/skills/skill-writer/REFERENCE.md +0 -284
  281. package/skills/skill-writer/SKILL.md +0 -222
  282. package/skills/tdd-team/SKILL.md +0 -128
@@ -0,0 +1,312 @@
1
+ """Tests for check_bugbot_ci silent-pass detection.
2
+
3
+ Covers:
4
+ - is_bugbot_run_clean returns True for completed success / completed neutral
5
+ - is_bugbot_run_clean returns False for completed failure, in_progress, missing
6
+ - is_bugbot_run_clean returns None when the gh CLI fails
7
+ - main(--check-clean) returns 0 on clean, 1 on not-clean, and
8
+ EXIT_CODE_GH_ERROR on gh CLI failure (with stderr diagnostics)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import importlib.util
14
+ import json
15
+ import subprocess
16
+ import sys
17
+ from collections.abc import Iterator
18
+ from pathlib import Path
19
+ from types import ModuleType
20
+ from unittest.mock import MagicMock, patch
21
+
22
+ import pytest
23
+
24
+ _SCRIPTS_DIRECTORY = Path(__file__).resolve().parent
25
+
26
+
27
+ @pytest.fixture(scope="session")
28
+ def check_bugbot_ci_module() -> Iterator[ModuleType]:
29
+ """Load check_bugbot_ci with full sys.path and sys.modules isolation.
30
+
31
+ Snapshots sys.path and sys.modules at session start; restores both
32
+ unconditionally at session teardown so sibling tests inherit a clean
33
+ loading environment. The production script performs its own
34
+ membership-guarded sys.path.insert during exec_module so its config
35
+ dependency resolves; that insert and any config.* modules it loads
36
+ are reverted on teardown.
37
+ """
38
+ module_path = _SCRIPTS_DIRECTORY / "check_bugbot_ci.py"
39
+ spec = importlib.util.spec_from_file_location("check_bugbot_ci", module_path)
40
+ assert spec is not None
41
+ assert spec.loader is not None
42
+ module = importlib.util.module_from_spec(spec)
43
+ sys_path_snapshot = list(sys.path)
44
+ sys_modules_snapshot = dict(sys.modules)
45
+ evicted_config_modules = {
46
+ each_module_name: sys.modules.pop(each_module_name)
47
+ for each_module_name in list(sys.modules)
48
+ if each_module_name == "config" or each_module_name.startswith("config.")
49
+ }
50
+ sys_modules_snapshot.update(evicted_config_modules)
51
+ spec.loader.exec_module(module)
52
+ try:
53
+ yield module
54
+ finally:
55
+ sys.path[:] = sys_path_snapshot
56
+ for each_new_module_name in list(sys.modules):
57
+ if each_new_module_name not in sys_modules_snapshot:
58
+ del sys.modules[each_new_module_name]
59
+ for each_module_name, each_module_value in sys_modules_snapshot.items():
60
+ sys.modules[each_module_name] = each_module_value
61
+
62
+
63
+ def _make_completed_process(
64
+ stdout: str, returncode: int = 0
65
+ ) -> subprocess.CompletedProcess[str]:
66
+ process = MagicMock(spec=subprocess.CompletedProcess)
67
+ process.stdout = stdout
68
+ process.stderr = ""
69
+ process.returncode = returncode
70
+ return process
71
+
72
+
73
+ def _build_stdout(*all_check_entries: dict[str, object]) -> str:
74
+ return "\n".join(json.dumps(each_entry) for each_entry in all_check_entries) + "\n"
75
+
76
+
77
+ def test_should_return_true_when_bugbot_completed_with_success_conclusion(
78
+ check_bugbot_ci_module: ModuleType,
79
+ ) -> None:
80
+ stdout = _build_stdout(
81
+ {"name": "Cursor Bugbot", "status": "completed", "conclusion": "success"}
82
+ )
83
+ with patch.object(
84
+ check_bugbot_ci_module,
85
+ "_run_check_runs_api",
86
+ return_value=_make_completed_process(stdout),
87
+ ):
88
+ is_clean = check_bugbot_ci_module.is_bugbot_run_clean(
89
+ owner="acme", repo="repo", sha="abc"
90
+ )
91
+ assert is_clean is True
92
+
93
+
94
+ def test_should_return_true_when_bugbot_completed_with_neutral_conclusion(
95
+ check_bugbot_ci_module: ModuleType,
96
+ ) -> None:
97
+ stdout = _build_stdout(
98
+ {"name": "bugbot", "status": "completed", "conclusion": "neutral"}
99
+ )
100
+ with patch.object(
101
+ check_bugbot_ci_module,
102
+ "_run_check_runs_api",
103
+ return_value=_make_completed_process(stdout),
104
+ ):
105
+ is_clean = check_bugbot_ci_module.is_bugbot_run_clean(
106
+ owner="acme", repo="repo", sha="abc"
107
+ )
108
+ assert is_clean is True
109
+
110
+
111
+ def test_should_return_false_when_bugbot_completed_with_failure_conclusion(
112
+ check_bugbot_ci_module: ModuleType,
113
+ ) -> None:
114
+ stdout = _build_stdout(
115
+ {"name": "Cursor Bugbot", "status": "completed", "conclusion": "failure"}
116
+ )
117
+ with patch.object(
118
+ check_bugbot_ci_module,
119
+ "_run_check_runs_api",
120
+ return_value=_make_completed_process(stdout),
121
+ ):
122
+ is_clean = check_bugbot_ci_module.is_bugbot_run_clean(
123
+ owner="acme", repo="repo", sha="abc"
124
+ )
125
+ assert is_clean is False
126
+
127
+
128
+ def test_should_return_false_when_bugbot_still_in_progress(
129
+ check_bugbot_ci_module: ModuleType,
130
+ ) -> None:
131
+ stdout = _build_stdout(
132
+ {"name": "Cursor Bugbot", "status": "in_progress", "conclusion": None}
133
+ )
134
+ with patch.object(
135
+ check_bugbot_ci_module,
136
+ "_run_check_runs_api",
137
+ return_value=_make_completed_process(stdout),
138
+ ):
139
+ is_clean = check_bugbot_ci_module.is_bugbot_run_clean(
140
+ owner="acme", repo="repo", sha="abc"
141
+ )
142
+ assert is_clean is False
143
+
144
+
145
+ def test_should_return_false_when_no_bugbot_check_run_present(
146
+ check_bugbot_ci_module: ModuleType,
147
+ ) -> None:
148
+ stdout = _build_stdout(
149
+ {"name": "ci-other", "status": "completed", "conclusion": "success"}
150
+ )
151
+ with patch.object(
152
+ check_bugbot_ci_module,
153
+ "_run_check_runs_api",
154
+ return_value=_make_completed_process(stdout),
155
+ ):
156
+ is_clean = check_bugbot_ci_module.is_bugbot_run_clean(
157
+ owner="acme", repo="repo", sha="abc"
158
+ )
159
+ assert is_clean is False
160
+
161
+
162
+ def test_should_return_false_when_first_bugbot_run_is_in_progress_even_if_later_one_clean(
163
+ check_bugbot_ci_module: ModuleType,
164
+ ) -> None:
165
+ stdout = _build_stdout(
166
+ {"name": "Cursor Bugbot", "status": "in_progress", "conclusion": None},
167
+ {"name": "Cursor Bugbot", "status": "completed", "conclusion": "success"},
168
+ )
169
+ with patch.object(
170
+ check_bugbot_ci_module,
171
+ "_run_check_runs_api",
172
+ return_value=_make_completed_process(stdout),
173
+ ):
174
+ is_clean = check_bugbot_ci_module.is_bugbot_run_clean(
175
+ owner="acme", repo="repo", sha="abc"
176
+ )
177
+ assert is_clean is False
178
+
179
+
180
+ def test_should_return_false_when_first_bugbot_run_failed_even_if_later_one_clean(
181
+ check_bugbot_ci_module: ModuleType,
182
+ ) -> None:
183
+ stdout = _build_stdout(
184
+ {"name": "Cursor Bugbot", "status": "completed", "conclusion": "failure"},
185
+ {"name": "Cursor Bugbot", "status": "completed", "conclusion": "success"},
186
+ )
187
+ with patch.object(
188
+ check_bugbot_ci_module,
189
+ "_run_check_runs_api",
190
+ return_value=_make_completed_process(stdout),
191
+ ):
192
+ is_clean = check_bugbot_ci_module.is_bugbot_run_clean(
193
+ owner="acme", repo="repo", sha="abc"
194
+ )
195
+ assert is_clean is False
196
+
197
+
198
+ def test_should_return_none_when_gh_cli_fails(
199
+ check_bugbot_ci_module: ModuleType,
200
+ ) -> None:
201
+ failing_process = MagicMock(spec=subprocess.CompletedProcess)
202
+ failing_process.stdout = ""
203
+ failing_process.stderr = "boom"
204
+ failing_process.returncode = 1
205
+ with patch.object(
206
+ check_bugbot_ci_module,
207
+ "_run_check_runs_api",
208
+ return_value=failing_process,
209
+ ):
210
+ is_clean = check_bugbot_ci_module.is_bugbot_run_clean(
211
+ owner="acme", repo="repo", sha="abc"
212
+ )
213
+ assert is_clean is None
214
+
215
+
216
+ def test_main_check_clean_should_return_gh_error_code_when_gh_cli_fails(
217
+ check_bugbot_ci_module: ModuleType,
218
+ capsys: pytest.CaptureFixture[str],
219
+ ) -> None:
220
+ failing_process = MagicMock(spec=subprocess.CompletedProcess)
221
+ failing_process.stdout = ""
222
+ failing_process.stderr = "boom"
223
+ failing_process.returncode = 1
224
+ with patch.object(
225
+ check_bugbot_ci_module,
226
+ "_run_check_runs_api",
227
+ return_value=failing_process,
228
+ ):
229
+ exit_code = check_bugbot_ci_module.main(
230
+ ["--check-clean", "--owner", "acme", "--repo", "repo", "--sha", "abc"]
231
+ )
232
+ assert exit_code == check_bugbot_ci_module.EXIT_CODE_GH_ERROR
233
+ captured = capsys.readouterr()
234
+ assert "gh api error: boom" in captured.err
235
+
236
+
237
+ def test_main_check_clean_should_return_zero_when_bugbot_clean(
238
+ check_bugbot_ci_module: ModuleType,
239
+ capsys: pytest.CaptureFixture[str],
240
+ ) -> None:
241
+ stdout = _build_stdout(
242
+ {"name": "Cursor Bugbot", "status": "completed", "conclusion": "success"}
243
+ )
244
+ with patch.object(
245
+ check_bugbot_ci_module,
246
+ "_run_check_runs_api",
247
+ return_value=_make_completed_process(stdout),
248
+ ):
249
+ exit_code = check_bugbot_ci_module.main(
250
+ ["--check-clean", "--owner", "acme", "--repo", "repo", "--sha", "abc"]
251
+ )
252
+ assert exit_code == 0
253
+ captured = capsys.readouterr()
254
+ assert "not clean" not in captured.out
255
+
256
+
257
+ def test_main_check_clean_should_return_one_when_bugbot_not_clean(
258
+ check_bugbot_ci_module: ModuleType,
259
+ capsys: pytest.CaptureFixture[str],
260
+ ) -> None:
261
+ stdout = _build_stdout(
262
+ {"name": "Cursor Bugbot", "status": "completed", "conclusion": "failure"}
263
+ )
264
+ with patch.object(
265
+ check_bugbot_ci_module,
266
+ "_run_check_runs_api",
267
+ return_value=_make_completed_process(stdout),
268
+ ):
269
+ exit_code = check_bugbot_ci_module.main(
270
+ ["--check-clean", "--owner", "acme", "--repo", "repo", "--sha", "abc"]
271
+ )
272
+ assert exit_code == 1
273
+ captured = capsys.readouterr()
274
+ assert "not clean" in captured.out
275
+
276
+
277
+ def test_main_check_active_should_return_zero_when_bugbot_in_progress(
278
+ check_bugbot_ci_module: ModuleType,
279
+ ) -> None:
280
+ stdout = _build_stdout(
281
+ {"name": "Cursor Bugbot", "status": "in_progress", "conclusion": None}
282
+ )
283
+ with patch.object(
284
+ check_bugbot_ci_module,
285
+ "_run_check_runs_api",
286
+ return_value=_make_completed_process(stdout),
287
+ ):
288
+ exit_code = check_bugbot_ci_module.main(
289
+ ["--check-active", "--owner", "acme", "--repo", "repo", "--sha", "abc"]
290
+ )
291
+ assert exit_code == 0
292
+
293
+
294
+ def test_main_should_reject_check_clean_and_check_active_together(
295
+ check_bugbot_ci_module: ModuleType,
296
+ capsys: pytest.CaptureFixture[str],
297
+ ) -> None:
298
+ with pytest.raises(SystemExit):
299
+ check_bugbot_ci_module.main(
300
+ [
301
+ "--check-clean",
302
+ "--check-active",
303
+ "--owner",
304
+ "acme",
305
+ "--repo",
306
+ "repo",
307
+ "--sha",
308
+ "abc",
309
+ ]
310
+ )
311
+ captured = capsys.readouterr()
312
+ assert "not allowed with" in captured.err or "mutually exclusive" in captured.err
@@ -4,21 +4,14 @@ Load this document for converge **loop pacing**. The pre-flight in `SKILL.md`
4
4
  guarantees `ScheduleWakeup` is available before any tick runs. Shared bugbot
5
5
  / bugteam / Fix protocol steps stay in the main `SKILL.md`.
6
6
 
7
- ## Session behavior
8
-
9
- Call `ScheduleWakeup` from this same session so the next tick fires back into **this** transcript with the prior tick's state line and PR context still addressable.
10
-
11
7
  ## Calling ScheduleWakeup
12
8
 
13
- At end of tick (unless convergence or another stop condition already
14
- omitted pacing), call `ScheduleWakeup` with:
9
+ At end of every tick across all phases (BUGBOT, BUGTEAM, COPILOT_WAIT)
10
+ without distinction call `ScheduleWakeup` unless convergence or another
11
+ stop condition already omitted pacing:
15
12
 
16
- - `delaySeconds: 270` whenever bugbot was just re-triggered (by the
17
- bugbot re-trigger in `../reference/per-tick.md`, by Fix protocol's
18
- mandatory re-trigger, or by BUGTEAM's same-tick re-trigger). Bugbot
19
- finishes a review in 1–4 minutes, so 270s stays under the 5-minute
20
- prompt-cache TTL with margin past bugbot's typical upper bound. The
21
- exception is the BUGBOT inline-lag branch (see below).
13
+ - `delaySeconds: 360` default wakeup interval. Keeps the loop advancing
14
+ through all phases. Exception: BUGBOT inline-lag branch (see below).
22
15
  - `reason`: one short sentence on what is being awaited, including the
23
16
  current `phase` and `bugbot_clean_at` SHA when set.
24
17
  - `prompt: "/pr-converge"` — re-enters this skill on the next firing.
@@ -23,7 +23,7 @@ Shared artifacts with /bugteam are referenced below by path, using the `${CLAUDE
23
23
  - Code-rules gate script: `${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/code_rules_gate.py`
24
24
  - Bug category rubric A–J: [`bugteam/PROMPTS.md`](../bugteam/PROMPTS.md#audit-spawn-prompt-xml-bugfind-teammate)
25
25
  - **Audit contract** (finding schema, proof-of-absence, adversarial pass, Haiku secondary, post-fix self-audit, diagnostics JSON): [`bugteam/reference/audit-contract.md`](../bugteam/reference/audit-contract.md)
26
- - PR comment lifecycle shape: [`bugteam/SKILL.md`](../bugteam/SKILL.md#step-25-pr-comments-one-review-per-loop)
26
+ - PR comment lifecycle shape: [`bugteam/SKILL.md`](../bugteam/SKILL.md#audit-posting)
27
27
 
28
28
  ## When this skill applies
29
29
 
@@ -31,6 +31,12 @@ Shared artifacts with /bugteam are referenced below by path, using the `${CLAUDE
31
31
 
32
32
  Refusals — first match wins; respond with the quoted line exactly and stop:
33
33
 
34
+ - **Disabled via environment.** When `CLAUDE_REVIEWS_DISABLED` contains the
35
+ token `bugteam` (comma-separated, case-insensitive, whitespace-tolerant):
36
+ `/qbug is disabled via CLAUDE_REVIEWS_DISABLED.` `/qbug` is the bugteam
37
+ baseline review and shares the `bugteam` token with `/bugteam`; the shared
38
+ pre-flight script also exits 7 in this case so any caller invoking it
39
+ directly halts on the same signal.
34
40
  - **No PR or upstream diff.** `No PR or upstream diff. /qbug needs a target.`
35
41
  - **Dirty tree.** `Uncommitted changes detected. Stash, commit, or revert before /qbug.`
36
42
  - **Missing subagent.** Before Step 2, confirm `clean-coder` exists. Else: `Required subagent type clean-coder not installed. /qbug needs clean-coder available.`
@@ -198,17 +204,97 @@ The subagent receives this prompt and loops internally — the lead does not re-
198
204
  <qbug_temp_dir>/loop-<loop_count>-audit.json per the contract's
199
205
  persistence schema.
200
206
 
201
- Post ONE review per loop. Use the payload shape from
202
- <categories_file>'s sibling SKILL.md § "PR comments" pass
203
- the review body as a direct string parameter (not jq/piped files) to
204
- `pull_request_review_write(method="create", event="COMMENT", body=<body>, owner=<owner>, repo=<repo>, pullNumber=<pr_number>, commitID=<head_sha>)`.
205
- Review body first line: `## /qbug loop <N> audit: <P0>P0 / <P1>P1 / <P2>P2`.
206
- If the review POST fails, fall back to one issue comment on
207
- `add_issue_comment(owner=<owner>, repo=<repo>, issueNumber=<pr_number>, body=<body>)` carrying the full body; mark every
208
- finding `used_fallback=true`.
209
-
210
- Harvest `html_url` for the parent review and each child comment
211
- into loop_comment_index[finding_id].
207
+ Post ONE review per loop via `post_audit_thread.py`. Before
208
+ serializing, partition the merged findings into anchored (line
209
+ appears in the captured diff) and unanchored (line is not in the
210
+ diff) buckets. Only anchored findings are written to
211
+ `<qbug_temp_dir>/loop-<loop_count>-findings.json` the GitHub
212
+ reviews API rejects the entire POST if any inline comment in
213
+ `comments[]` targets a line not in the diff at `--commit`, so a
214
+ single unanchored entry breaks the whole review. Unanchored
215
+ findings surface in the loop's user-facing summary rather than
216
+ as inline anchored comments. The JSON root is a list of objects
217
+ shaped `{path, line, side, severity, description, fix_summary}`.
218
+ For each anchored finding, map `file` → `path`; split the
219
+ finding's `failure_mode` at the literal `Fix:` heading so the
220
+ failure narrative becomes `description` and the suffix beginning
221
+ at `Fix:` (including the trailing `Validation:` clause) becomes
222
+ `fix_summary` (the `failure_mode` field carries the full
223
+ audit-to-fix handoff per
224
+ [`agents/code-quality-agent.md`](../../agents/code-quality-agent.md)).
225
+ When a finding's `failure_mode` omits the `Fix:` heading, write
226
+ the full text to BOTH `description` and `fix_summary`. Set
227
+ `side="RIGHT"` for every entry. Zero anchored findings → write
228
+ `[]` and pass `--state CLEAN`; one or more anchored findings →
229
+ pass `--state DIRTY` with the full list.
230
+
231
+ **Self-PR auto-toggle.** GitHub rejects both `APPROVE` and
232
+ `REQUEST_CHANGES` reviews with HTTP 422 when the authenticated
233
+ identity matches the PR author ("Cannot approve/request changes
234
+ on your own pull request"). `post_audit_thread.py` detects this
235
+ case via `gh api user` + `gh api repos/<o>/<r>/pulls/<n>` and
236
+ auto-resolves an alternate gh account's token for the reviews
237
+ POST — the active `gh auth` account is not mutated; only the
238
+ bearer token sent on the request changes. After the POST the
239
+ active account is still whoever it was before, so no "swap back"
240
+ step is needed.
241
+
242
+ Configuration:
243
+
244
+ - `GH_TOKEN` / `GITHUB_TOKEN` env vars take precedence over the
245
+ toggle. Set them when you need to pin a specific reviewer
246
+ identity by token rather than by account login.
247
+ - `BUGTEAM_REVIEWER_ACCOUNT` env var names which authenticated
248
+ alternate to prefer when a toggle is needed (for example,
249
+ `BUGTEAM_REVIEWER_ACCOUNT=jl-cmd`). The env var name is shared
250
+ across every skill that invokes `post_audit_thread.py`. When
251
+ unset, the script falls back to the first alternate account
252
+ `gh auth status` reports.
253
+ - The named alternate must be logged in (`gh auth login -h
254
+ github.com -u <login>`) before the audit skill runs. The
255
+ script exits 1 with a pointing-at-`gh auth login` message
256
+ when self-PR is detected and no usable alternate is
257
+ authenticated.
258
+
259
+ ```
260
+ python "${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/post_audit_thread.py" \
261
+ --skill qbug \
262
+ --owner <owner> \
263
+ --repo <repo> \
264
+ --pr-number <pr_number> \
265
+ --commit <head_sha> \
266
+ --state <CLEAN|DIRTY> \
267
+ --findings-json <qbug_temp_dir>/loop-<loop_count>-findings.json
268
+ ```
269
+
270
+ The script POSTs a single review with `event=APPROVE` on CLEAN
271
+ (the request event; GitHub stores it as `state=APPROVED`; empty
272
+ `comments[]`, body documents "no findings") or
273
+ `event=REQUEST_CHANGES` on DIRTY (one inline anchored comment per
274
+ finding; each becomes its own resolvable thread on the PR). It
275
+ handles retries internally (1s / 4s / 16s backoff across four
276
+ attempts). Exit 0 emits the new review's `html_url` to stdout;
277
+ extract the numeric review id from the URL's
278
+ `#pullrequestreview-<id>` suffix (the trailing URL fragment of
279
+ `html_url`, the part after `#`). Then harvest child-comment URLs **and PR review
280
+ thread node ids** via
281
+ `pull_request_read(method="get_review_comments",
282
+ owner=<owner>, repo=<repo>, pullNumber=<pr_number>)` filtered to
283
+ that review id. The same response carries each
284
+ comment's PR review thread node id (e.g. `PRRT_kwDOxxx`) — match
285
+ children to findings in the order they appear in the findings
286
+ JSON. Each `loop_comment_index[finding_id]` entry must carry
287
+ `finding_comment_id` (numeric, used by
288
+ `add_reply_to_pull_request_comment`), `finding_comment_url`, and
289
+ `thread_node_id` (`PRRT_kwDOxxx`, used by `resolve_thread`) so
290
+ the FIX step can reply against the comment and resolve the
291
+ thread.
292
+
293
+ Exit 2 means retry exhaustion — exit
294
+ `error: post_audit_thread retry exhausted` without retrying and
295
+ without falling back to a flat issue comment. A hard blocker on
296
+ the audit-posting path is a halt condition, not a fallback
297
+ opportunity.
212
298
 
213
299
  Update state: last_action="audited", last_findings=counts.
214
300
  Append `<N> audit: <P0>P0 / <P1>P1 / <P2>P2` to audit_log.
@@ -245,12 +331,54 @@ The subagent receives this prompt and loops internally — the lead does not re-
245
331
  Write <qbug_temp_dir>/loop-<loop_count>-diagnostics.json per the
246
332
  contract's diagnostics schema (all eight keys required).
247
333
 
248
- Reply to each finding at loop_comment_index[finding_id].finding_comment_id
249
- using `add_reply_to_pull_request_comment(commentId=<id>, body=<body>, owner=<owner>, repo=<repo>, pullNumber=<pr_number>)`.
250
- Reply body is one of:
251
- - `Fixed in <short_sha>`
252
- - `Could not address this loop: <one-line reason>`
253
- - `Hook blocked the fix commit: <one-line summary>`
334
+ For each finding, atomically (a) post the fix reply and
335
+ (b) call `resolve_thread`. The two calls form one logical action
336
+ per thread do not yield to the lead between them, and do not
337
+ batch all replies before any resolves.
338
+
339
+ (a) Reply via
340
+ `add_reply_to_pull_request_comment(commentId=<loop_comment_index[finding_id].finding_comment_id>,
341
+ body=<reply_body>, owner=<owner>, repo=<repo>,
342
+ pullNumber=<pr_number>)`. The reply body uses the unified template
343
+ at [`../../_shared/pr-loop/audit-reply-template.md`](../../_shared/pr-loop/audit-reply-template.md).
344
+ Skeleton (identical across all paths):
345
+
346
+ ```
347
+ **Claude finished @<reviewer>'s task** —— <status_line>
348
+
349
+ ---
350
+ ### <action_heading> ✅
351
+
352
+ <1–2 paragraph plain-language explanation>
353
+
354
+ **`<file>:<line>`:**
355
+ - <bullet describing change or rationale>
356
+ - <bullet describing change or rationale>
357
+
358
+ <closing paragraph>
359
+ ```
360
+
361
+ Per-path `<status_line>` / `<action_heading>`:
362
+ - `status=fixed`: `Fixed in <short_sha>` (first 7 chars) /
363
+ finding-specific action verb (e.g.,
364
+ `Replaced Any with concrete type`).
365
+ - `status=could_not_address`: `Could not address this loop` /
366
+ one-line reason text.
367
+ - `status=hook_blocked`: `Hook blocked the fix commit` /
368
+ one-line hook summary.
369
+
370
+ Body text is passed directly as string parameters — no temp
371
+ files, no jq, no shell pipes.
372
+
373
+ (b) Immediately call
374
+ `pull_request_review_write(method="resolve_thread",
375
+ threadId=<loop_comment_index[finding_id].thread_node_id>,
376
+ owner=<owner>, repo=<repo>, pullNumber=<pr_number>)` for the
377
+ same thread (this is the PR review thread node ID —
378
+ `PRRT_kwDOxxx` — distinct from the numeric comment ID; the
379
+ AUDIT step captures it at audit time when calling
380
+ `get_review_comments` and stores it on each
381
+ `loop_comment_index` entry alongside `finding_comment_id`).
254
382
 
255
383
  Update state: last_action="fixed". Append
256
384
  `<N> fix: <short_sha> — <fixed>/<could_not_address>/<hook_blocked>`
@@ -260,14 +388,16 @@ The subagent receives this prompt and loops internally — the lead does not re-
260
388
  </cycle>
261
389
 
262
390
  <example_finding_body>
263
- **[P1] race condition on shared cache write**
264
- Category: I (concurrency hazards)
265
- Two writers can both pass the existence check at line 88 before either
266
- commits the write at line 91 — whichever writes second overwrites the
267
- first under contention. Either hold the cache lock across the check
268
- and the write, or use a compare-and-swap primitive.
269
-
270
- _From /qbug audit loop 2._
391
+ Populate these two fields on each finding entry of the JSON payload
392
+ consumed by `post_audit_thread.py` (the script renders the inline
393
+ comment body via `INLINE_COMMENT_BODY_TEMPLATE`):
394
+
395
+ ```json
396
+ {
397
+ "description": "Two writers can both pass the existence check at line 88 before either commits the write at line 91 — whichever writes second overwrites the first under contention.",
398
+ "fix_summary": "Hold the cache lock across the check and the write, or use a compare-and-swap primitive. Validation: pytest -k cache_concurrent with the threaded-writer fixture."
399
+ }
400
+ ```
271
401
  </example_finding_body>
272
402
 
273
403
  <constraints>
@@ -315,7 +445,7 @@ Delete the resolved `<qbug_temp_dir>` tree and any `.qbug-*.md` temp files in th
315
445
  - **Code rules gate before every AUDIT.** Same `validate_content` logic as /bugteam.
316
446
  - **One commit per FIX action.** Linear branch, fast-forward push only.
317
447
  - **Categories A–J.** Same rubric as [`bugteam/PROMPTS.md`](../bugteam/PROMPTS.md).
318
- - **One review per loop.** Anchored findings as `comments[]`; unanchored listed under "Findings without a diff anchor" in the review body.
448
+ - **One review per loop.** Anchored findings as `comments[]`; unanchored findings surface in the calling skill's user-facing output (chat reply to the user) rather than in the PR review body.
319
449
  - **PR description rewrite on every exit**, same as /bugteam Step 4.5.
320
450
  - **Temp file cleanup on every exit path.**
321
451
  - **No per-loop clean-room.** The single subagent's context accumulates across loops — that is the explicit trade vs /bugteam. For convergence-critical audits where bias isolation matters, use /bugteam.