claude-dev-env 1.38.0 → 1.39.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 (271) 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 +189 -0
  7. package/_shared/pr-loop/scripts/post_audit_thread.py +947 -0
  8. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
  9. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +923 -0
  10. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
  11. package/_shared/pr-loop/state-schema.md +1 -1
  12. package/agents/clean-coder.md +2 -2
  13. package/bin/install.mjs +6 -7
  14. package/bin/install.test.mjs +8 -0
  15. package/commands/doc-gist.md +16 -0
  16. package/commands/plan.md +0 -2
  17. package/commands/review-plan.md +1 -1
  18. package/docs/CODE_RULES.md +122 -2
  19. package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
  20. package/hooks/blocking/code_rules_enforcer.py +1236 -161
  21. package/hooks/blocking/convergence_gate_blocker.py +130 -0
  22. package/hooks/blocking/destructive_command_blocker.py +74 -0
  23. package/hooks/blocking/gh_body_arg_blocker.py +30 -0
  24. package/hooks/blocking/md_to_html_blocker.py +119 -0
  25. package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
  26. package/hooks/blocking/test_code_rules_enforcer.py +21 -0
  27. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
  28. package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
  29. package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
  30. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
  31. package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
  32. package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
  33. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  34. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
  35. package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
  36. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
  37. package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
  38. package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
  39. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
  40. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
  41. package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
  42. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +158 -0
  43. package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
  44. package/hooks/blocking/test_destructive_command_blocker.py +146 -0
  45. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
  46. package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
  47. package/hooks/blocking/test_md_to_html_blocker.py +317 -0
  48. package/hooks/config/any_type_config.py +7 -0
  49. package/hooks/config/banned_identifiers_constants.py +11 -0
  50. package/hooks/config/blocking_check_limits.py +38 -0
  51. package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
  52. package/hooks/config/code_rules_enforcer_constants.py +53 -0
  53. package/hooks/config/convergence_branch_constants.py +9 -0
  54. package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
  55. package/hooks/config/html_companion_constants.py +20 -0
  56. package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
  57. package/hooks/config/test_banned_identifiers_constants.py +17 -0
  58. package/hooks/hooks.json +28 -20
  59. package/hooks/pyproject.toml +69 -0
  60. package/hooks/validators/mypy_integration.py +47 -1
  61. package/hooks/validators/run_all_validators.py +3 -3
  62. package/hooks/validators/test_mypy_integration.py +50 -1
  63. package/hooks/workflow/doc_gist_auto_publish.py +144 -0
  64. package/hooks/workflow/md_to_html_companion.py +365 -0
  65. package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
  66. package/hooks/workflow/test_md_to_html_companion.py +452 -0
  67. package/package.json +1 -1
  68. package/rules/gh-body-file.md +2 -0
  69. package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
  70. package/scripts/check.ps1 +106 -0
  71. package/scripts/config/timing.py +11 -0
  72. package/scripts/sweep_empty_dirs.py +138 -0
  73. package/scripts/sync_to_cursor/rules.py +1 -1
  74. package/scripts/test_sweep_empty_dirs.py +183 -0
  75. package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
  76. package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
  77. package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
  78. package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
  79. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
  80. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
  81. package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
  82. package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
  83. package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
  84. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
  85. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
  86. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
  87. package/skills/bugteam/CONSTRAINTS.md +21 -22
  88. package/skills/bugteam/EXAMPLES.md +3 -3
  89. package/skills/bugteam/PROMPTS.md +227 -67
  90. package/skills/bugteam/SKILL.md +114 -455
  91. package/skills/bugteam/reference/README.md +1 -1
  92. package/skills/bugteam/reference/audit-and-teammates.md +112 -39
  93. package/skills/bugteam/reference/audit-contract.md +4 -22
  94. package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
  95. package/skills/bugteam/reference/design-rationale.md +2 -2
  96. package/skills/bugteam/reference/github-pr-reviews.md +50 -57
  97. package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
  98. package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
  99. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
  100. package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
  101. package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
  102. package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
  103. package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
  104. package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
  105. package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
  106. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
  107. package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
  108. package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
  109. package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
  110. package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
  111. package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
  112. package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
  113. package/skills/bugteam/reference/team-setup.md +106 -9
  114. package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
  115. package/skills/bugteam/scripts/README.md +60 -0
  116. package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
  117. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
  118. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
  119. package/skills/bugteam/scripts/bugteam_preflight.py +294 -0
  120. package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
  121. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
  122. package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
  123. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
  124. package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
  125. package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
  126. package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
  127. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
  128. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
  129. package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
  130. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
  131. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
  132. package/skills/bugteam/scripts/test_bugteam_preflight.py +268 -0
  133. package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
  134. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
  135. package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
  136. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
  137. package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
  138. package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
  139. package/skills/bugteam/test_skill_additions.py +1 -11
  140. package/skills/code/SKILL.md +176 -0
  141. package/skills/doc-gist/SKILL.md +99 -0
  142. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
  143. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
  144. package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
  145. package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
  146. package/skills/doc-gist/references/examples/05-design-system.html +629 -0
  147. package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
  148. package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
  149. package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
  150. package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
  151. package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
  152. package/skills/doc-gist/references/examples/11-status-report.html +528 -0
  153. package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
  154. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
  155. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
  156. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
  157. package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
  158. package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
  159. package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
  160. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
  161. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
  162. package/skills/doc-gist/references/examples/README.md +5 -0
  163. package/skills/doc-gist/scripts/config/__init__.py +0 -0
  164. package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
  165. package/skills/doc-gist/scripts/gist_upload.py +177 -0
  166. package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
  167. package/skills/findbugs/SKILL.md +68 -2
  168. package/skills/monitor-open-prs/SKILL.md +13 -32
  169. package/skills/monitor-open-prs/test_skill_contract.py +0 -11
  170. package/skills/pr-consistency-audit/SKILL.md +112 -0
  171. package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
  172. package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
  173. package/skills/pr-converge/SKILL.md +227 -23
  174. package/skills/pr-converge/config/__init__.py +0 -0
  175. package/skills/pr-converge/config/constants.py +62 -0
  176. package/skills/pr-converge/reference/convergence-gates.md +138 -44
  177. package/skills/pr-converge/reference/examples.md +43 -11
  178. package/skills/pr-converge/reference/fix-protocol.md +6 -5
  179. package/skills/pr-converge/reference/ground-rules.md +5 -3
  180. package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
  181. package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
  182. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
  183. package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
  184. package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
  185. package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
  186. package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
  187. package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
  188. package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
  189. package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
  190. package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
  191. package/skills/pr-converge/reference/per-tick.md +90 -31
  192. package/skills/pr-converge/reference/state-schema.md +22 -1
  193. package/skills/pr-converge/reference/stop-conditions.md +9 -7
  194. package/skills/pr-converge/scripts/README.md +34 -46
  195. package/skills/pr-converge/scripts/check_bugbot_ci.py +174 -0
  196. package/skills/pr-converge/scripts/check_convergence.py +497 -0
  197. package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
  198. package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
  199. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
  200. package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
  201. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
  202. package/skills/qbug/SKILL.md +132 -27
  203. package/skills/session-log/SKILL.md +216 -114
  204. package/skills/session-tidy/SKILL.md +1 -1
  205. package/skills/skill-builder/SKILL.md +138 -56
  206. package/skills/skill-builder/references/delegation-map.md +72 -113
  207. package/skills/skill-builder/references/progressive-disclosure.md +122 -0
  208. package/skills/skill-builder/references/self-audit-checklist.md +92 -0
  209. package/skills/skill-builder/references/skill-types.md +228 -0
  210. package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
  211. package/skills/skill-builder/templates/gap-analysis.md +15 -8
  212. package/skills/skill-builder/workflows/improve-skill.md +86 -57
  213. package/skills/skill-builder/workflows/new-skill.md +80 -168
  214. package/skills/skill-builder/workflows/polish-skill.md +78 -54
  215. package/skills/structure-prompt/SKILL.md +50 -0
  216. package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
  217. package/skills/structure-prompt/reference/block-classification.md +27 -0
  218. package/skills/structure-prompt/reference/canonical-case.md +48 -0
  219. package/skills/structure-prompt/reference/citation-depth.md +70 -0
  220. package/skills/structure-prompt/reference/cleanup.md +33 -0
  221. package/skills/structure-prompt/reference/constraints.md +33 -0
  222. package/skills/structure-prompt/reference/directives.md +37 -0
  223. package/skills/structure-prompt/reference/examples.md +72 -0
  224. package/skills/structure-prompt/reference/instantiation.md +51 -0
  225. package/skills/structure-prompt/reference/output-contract.md +72 -0
  226. package/skills/structure-prompt/reference/per-category.md +23 -0
  227. package/skills/structure-prompt/reference/persona.md +38 -0
  228. package/skills/structure-prompt/reference/research.md +33 -0
  229. package/skills/structure-prompt/reference/structure.md +28 -0
  230. package/agents/code-standards-agent.md +0 -93
  231. package/agents/groq-coder.md +0 -113
  232. package/agents/plan-executor.md +0 -226
  233. package/agents/project-docs-analyzer.md +0 -53
  234. package/agents/project-structure-organizer-agent.md +0 -72
  235. package/agents/skill-to-agent-converter.md +0 -370
  236. package/agents/skill-writer-agent.md +0 -470
  237. package/agents/user-docs-writer.md +0 -67
  238. package/agents/workflow-visual-documenter.md +0 -82
  239. package/commands/readability-review.md +0 -20
  240. package/hooks/mypy.ini +0 -2
  241. package/hooks/notification/attention_needed_notify.py +0 -71
  242. package/hooks/notification/claude_notification_handler.py +0 -67
  243. package/hooks/notification/notification_utils.py +0 -267
  244. package/hooks/notification/subagent_complete_notify.py +0 -381
  245. package/hooks/notification/test_attention_needed_notify.py +0 -47
  246. package/hooks/notification/test_claude_notification_handler.py +0 -54
  247. package/hooks/notification/test_notification_utils.py +0 -91
  248. package/hooks/notification/test_subagent_complete_notify.py +0 -79
  249. package/scripts/config/groq_bugteam_config.py +0 -230
  250. package/scripts/config/test_groq_bugteam_config.py +0 -83
  251. package/scripts/config/test_spec_implementer_prompt.py +0 -32
  252. package/scripts/groq_bugteam.README.md +0 -131
  253. package/scripts/groq_bugteam.py +0 -647
  254. package/scripts/groq_bugteam_dotenv.py +0 -40
  255. package/scripts/groq_bugteam_spec.py +0 -226
  256. package/scripts/test_groq_bugteam.py +0 -529
  257. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
  258. package/scripts/test_groq_bugteam_dotenv.py +0 -66
  259. package/scripts/test_groq_bugteam_spec.py +0 -338
  260. package/skills/bugteam/SKILL_EVALS.md +0 -309
  261. package/skills/dream/SKILL.md +0 -118
  262. package/skills/ingest/SKILL.md +0 -40
  263. package/skills/npm-creator/SKILL.md +0 -187
  264. package/skills/readability-review/SKILL.md +0 -127
  265. package/skills/resume-review/SKILL.md +0 -261
  266. package/skills/rule-audit/SKILL.md +0 -307
  267. package/skills/rule-creator/SKILL.md +0 -150
  268. package/skills/searching-obsidian-vault/SKILL.md +0 -131
  269. package/skills/skill-writer/REFERENCE.md +0 -284
  270. package/skills/skill-writer/SKILL.md +0 -222
  271. package/skills/tdd-team/SKILL.md +0 -128
@@ -0,0 +1,118 @@
1
+ """Configuration constants for the pr-converge skill scripts.
2
+
3
+ Path templates accept ``str.format(**kwargs)`` substitution; bugbot strings
4
+ match the literal phrasing the Cursor Bugbot reviewer emits.
5
+
6
+ All runtime and API constants are imported from ``config.constants``.
7
+ Only script-specific constants (CLI args, markdown patterns, reflow
8
+ settings) live here.
9
+ """
10
+
11
+ import re
12
+ from pathlib import Path
13
+
14
+ from config.constants import ( # noqa: F401
15
+ ALL_BUGBOT_CHECK_RUN_ACTIVE_STATUSES,
16
+ ALL_CLAUDE_DIRTY_REVIEW_STATES,
17
+ ALL_COPILOT_DIRTY_REVIEW_STATES,
18
+ ALL_BUGBOT_CHECK_RUN_COMPLETE_CONCLUSIONS,
19
+ BUGBOT_CHECK_RUN_NAME_SUBSTRING,
20
+ BUGBOT_DIRTY_BODY_REGEX,
21
+ BUGBOT_RUN_TRIGGER_PHRASE,
22
+ BUGBOT_RUN_TRIGGER_WAIT_SECONDS,
23
+ CHECK_RUNS_PER_PAGE,
24
+ ALL_CLAUDE_CLEAN_REVIEW_STATES,
25
+ CLAUDE_LOGIN_FILTER_SUBSTRING,
26
+ CLAUDE_REVIEWER_LOGIN,
27
+ CLAUDE_REVIEWER_REQUEST_ID,
28
+ CLAUDE_SOFT_DIRTY_REVIEW_STATE,
29
+ ALL_COPILOT_CLEAN_REVIEW_STATES,
30
+ COPILOT_LOGIN_FILTER_SUBSTRING,
31
+ COPILOT_REVIEWER_LOGIN,
32
+ COPILOT_REVIEWER_REQUEST_ID,
33
+ COPILOT_SOFT_DIRTY_REVIEW_STATE,
34
+ CURSOR_BOT_LOGIN,
35
+ CURSOR_LOGIN_FILTER_SUBSTRING,
36
+ EXIT_CODE_GH_ERROR,
37
+ EXIT_CODE_NOT_FOUND,
38
+ GH_CHECK_RUNS_PATH_TEMPLATE,
39
+ GH_INLINE_COMMENT_CREATE_PATH_TEMPLATE,
40
+ GH_INLINE_COMMENTS_PATH_TEMPLATE,
41
+ GH_ISSUE_COMMENT_CREATE_PATH_TEMPLATE,
42
+ GH_PR_OBJECT_PATH_TEMPLATE,
43
+ GH_REQUESTED_REVIEWERS_FIELD_TEMPLATE,
44
+ GH_REVIEW_COMMENTS_PATH_TEMPLATE,
45
+ GH_REVIEWS_PATH_TEMPLATE,
46
+ )
47
+
48
+ BUGBOT_RUN_TEMPFILE_SUFFIX: str = ".md"
49
+
50
+ BUGBOT_RUN_TEMPFILE_PREFIX: str = "pr-converge-bugbot-run-"
51
+
52
+ PR_CONTEXT_FIELDS: str = "number,url,headRefOid,baseRefName,headRefName,isDraft"
53
+
54
+ PR_DETACHED_HEAD_ARGS_ERROR: str = "--owner and --repo require --number; all three must be provided together for detached-HEAD PR resolution"
55
+
56
+ PR_NUMBER_ARG_FLAG: str = "--number"
57
+
58
+ PR_NUMBER_ARG_HELP: str = "PR number"
59
+
60
+ PR_OWNER_ARG_FLAG: str = "--owner"
61
+
62
+ PR_OWNER_ARG_HELP: str = "GitHub repository owner"
63
+
64
+ PR_REPO_ARG_FLAG: str = "--repo"
65
+
66
+ PR_REPO_ARG_HELP: str = "GitHub repository name"
67
+
68
+ GH_REPO_FLAG: str = "--repo"
69
+
70
+ MERGEABILITY_FIELDS: str = "mergeable,mergeStateStatus,headRefOid"
71
+
72
+ GH_FIELD_BODY_AT_PREFIX: str = "body=@"
73
+
74
+ GH_REPO_ARG_TEMPLATE: str = "{owner}/{repo}"
75
+
76
+ SKILL_REFLOW_MAXIMUM_WIDTH: int = 80
77
+
78
+ PR_CONVERGE_SKILL_PATH: Path = Path(__file__).resolve().parent.parent.parent / "SKILL.md"
79
+
80
+ MARKDOWN_CODE_FENCE_MARKER: str = "```"
81
+
82
+ YAML_FRONT_MATTER_DELIMITER: str = "---"
83
+
84
+ YAML_DESCRIPTION_PREFIX: str = "description: >-"
85
+
86
+ EXAMPLE_OPEN_TAG: str = "<example>"
87
+
88
+ EXAMPLE_CLOSE_TAG: str = "</example>"
89
+
90
+ BASH_FENCE_LANGUAGE: str = "bash"
91
+
92
+ BASH_LINE_CONTINUATION_SUFFIX: str = " \\"
93
+
94
+ BASH_CONTINUATION_INDENT: str = " "
95
+
96
+ REFLOW_FRONT_MATTER_ERROR: str = "expected YAML front matter starting with ---"
97
+
98
+ ORDERED_MARKDOWN_LIST_PATTERN: re.Pattern[str] = re.compile(
99
+ r"^(?P<leading_whitespace>\s*)(?P<marker>\d+\.\s)(?P<body>.*)$"
100
+ )
101
+
102
+ BULLET_MARKDOWN_LIST_PATTERN: re.Pattern[str] = re.compile(
103
+ r"^(?P<leading_whitespace>\s*)(?P<marker>[-*]\s)(?P<body>.*)$"
104
+ )
105
+
106
+ UNFINISHED_MARKDOWN_LINK_TARGET_PATTERN: re.Pattern[str] = re.compile(r"\]\([^)]*$")
107
+
108
+ MARKDOWN_HEADING_PATTERN: re.Pattern[str] = re.compile(r"^#{1,6}\s+.+$")
109
+
110
+ MARKDOWN_REFERENCE_DEFINITION_PATTERN: re.Pattern[str] = re.compile(r"^\[[^\]]+\]:\s+\S+")
111
+
112
+ BASH_LINE_CONTINUATION_MARKER_WIDTH: int = 2
113
+
114
+ CODE_FENCE_MARKER_LENGTH: int = 3
115
+
116
+ BASH_MINIMUM_SEGMENT_WIDTH: int = 1
117
+
118
+ LONG_ROW_PREVIEW_LIMIT: int = 20
@@ -0,0 +1,134 @@
1
+ """Fetch GitHub Copilot reviews for a pull request, newest first.
2
+
3
+ Usage:
4
+ python scripts/fetch_copilot_reviews.py --owner <O> --repo <R> --pr-number <N>
5
+
6
+ Output: JSON array of Copilot reviews to stdout, each with id, state, body,
7
+ commit_id, submitted_at, and html_url.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import subprocess
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ _pr_converge_dir = Path(__file__).resolve().parent.parent
19
+ if str(_pr_converge_dir) not in sys.path:
20
+ sys.path.insert(0, str(_pr_converge_dir))
21
+
22
+ from config.constants import (
23
+ COPILOT_LOGIN_FILTER_SUBSTRING,
24
+ GH_REVIEWS_PATH_TEMPLATE,
25
+ REVIEWS_PER_PAGE,
26
+ )
27
+
28
+
29
+ def fetch_copilot_reviews(
30
+ *, owner: str, repo: str, number: int
31
+ ) -> list[dict[str, object]]:
32
+ """Return Copilot reviews for a PR, newest first.
33
+
34
+ Args:
35
+ owner: GitHub repository owner.
36
+ repo: GitHub repository name.
37
+ number: Pull request number.
38
+
39
+ Returns:
40
+ List of Copilot review entries sorted by submitted_at descending.
41
+
42
+ Raises:
43
+ SystemExit: When the gh CLI call fails.
44
+ """
45
+ endpoint_path = GH_REVIEWS_PATH_TEMPLATE.format(
46
+ owner=owner, repo=repo, number=number
47
+ )
48
+ completed_process = subprocess.run(
49
+ [
50
+ "gh", "api",
51
+ f"{endpoint_path}?per_page={REVIEWS_PER_PAGE}",
52
+ "--paginate", "--slurp",
53
+ ],
54
+ capture_output=True,
55
+ text=True,
56
+ encoding="utf-8",
57
+ errors="replace",
58
+ check=False,
59
+ )
60
+ if completed_process.returncode != 0:
61
+ print(f"gh api error: {completed_process.stderr}", file=sys.stderr)
62
+ raise SystemExit(1)
63
+ raw_output: object = json.loads(completed_process.stdout)
64
+ if not isinstance(raw_output, list):
65
+ return []
66
+ all_pages: list[list[dict[str, object]]] = [
67
+ each_page for each_page in raw_output if isinstance(each_page, list)
68
+ ]
69
+ all_flat: list[dict[str, object]] = [
70
+ each_item for each_page in all_pages for each_item in each_page
71
+ ]
72
+ copilot_reviews: list[dict[str, object]] = []
73
+ for each_review in all_flat:
74
+ user_object: object = each_review.get("user")
75
+ if not isinstance(user_object, dict):
76
+ continue
77
+ raw_login: object = user_object.get("login")
78
+ if not isinstance(raw_login, str):
79
+ continue
80
+ if COPILOT_LOGIN_FILTER_SUBSTRING not in raw_login.lower():
81
+ continue
82
+ copilot_reviews.append({
83
+ "id": each_review.get("id"),
84
+ "state": each_review.get("state"),
85
+ "body": each_review.get("body"),
86
+ "commit_id": each_review.get("commit_id"),
87
+ "submitted_at": each_review.get("submitted_at"),
88
+ "html_url": each_review.get("html_url"),
89
+ })
90
+ copilot_reviews.sort(
91
+ key=lambda each_review: str(each_review.get("submitted_at", "")),
92
+ reverse=True,
93
+ )
94
+ return copilot_reviews
95
+
96
+
97
+ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
98
+ """Parse command-line arguments.
99
+
100
+ Args:
101
+ all_argv: Command-line argument list.
102
+
103
+ Returns:
104
+ Parsed namespace with owner, repo, and number.
105
+ """
106
+ parser = argparse.ArgumentParser(description=__doc__)
107
+ parser.add_argument("--owner", required=True, help="GitHub repository owner")
108
+ parser.add_argument("--repo", required=True, help="GitHub repository name")
109
+ parser.add_argument("--pr-number", required=True, type=int, help="Pull request number")
110
+ return parser.parse_args(all_argv)
111
+
112
+
113
+ def main(all_arguments: list[str]) -> int:
114
+ """Entry point for fetch_copilot_reviews.
115
+
116
+ Args:
117
+ all_arguments: Command-line arguments.
118
+
119
+ Returns:
120
+ 0 on success, 1 on error.
121
+ """
122
+ arguments = parse_arguments(all_arguments)
123
+ all_reviews = fetch_copilot_reviews(
124
+ owner=arguments.owner,
125
+ repo=arguments.repo,
126
+ number=getattr(arguments, "pr_number"),
127
+ )
128
+ json.dump(all_reviews, sys.stdout)
129
+ sys.stdout.write("\n")
130
+ return 0
131
+
132
+
133
+ if __name__ == "__main__":
134
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,168 @@
1
+ """Post a fix reply to a pull request comment thread or as a general PR comment.
2
+
3
+ Usage:
4
+ # Reply to an inline comment thread
5
+ python scripts/post_fix_reply.py --owner <O> --repo <R> --pr-number <N> \
6
+ --body "Fixed in <sha>" --in-reply-to <COMMENT_ID>
7
+
8
+ python scripts/post_fix_reply.py --owner <O> --repo <R> --pr-number <N> \
9
+ --body "Your reply text"
10
+
11
+ Exit codes:
12
+ 0 — reply posted successfully
13
+ 2 — gh CLI error
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import subprocess
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ _pr_converge_dir = Path(__file__).resolve().parent.parent
24
+ if str(_pr_converge_dir) not in sys.path:
25
+ sys.path.insert(0, str(_pr_converge_dir))
26
+
27
+ from config.constants import (
28
+ EXIT_CODE_GH_ERROR,
29
+ GH_INLINE_COMMENT_REPLY_PATH_TEMPLATE,
30
+ GH_ISSUE_COMMENT_CREATE_PATH_TEMPLATE,
31
+ )
32
+
33
+
34
+ def post_inline_reply(
35
+ *,
36
+ owner: str,
37
+ repo: str,
38
+ number: int,
39
+ body: str,
40
+ in_reply_to: int,
41
+ ) -> int:
42
+ """Post a reply to an inline pull request comment thread.
43
+
44
+ Args:
45
+ owner: GitHub repository owner.
46
+ repo: GitHub repository name.
47
+ number: Pull request number.
48
+ body: Reply body text.
49
+ in_reply_to: The comment ID to reply to.
50
+
51
+ Returns:
52
+ 0 on success, EXIT_CODE_GH_ERROR on failure.
53
+ """
54
+ endpoint_path = GH_INLINE_COMMENT_REPLY_PATH_TEMPLATE.format(
55
+ owner=owner, repo=repo, number=number, comment_id=in_reply_to
56
+ )
57
+ completed_process = subprocess.run(
58
+ [
59
+ "gh",
60
+ "api",
61
+ endpoint_path,
62
+ "-f",
63
+ f"body={body}",
64
+ ],
65
+ capture_output=True,
66
+ text=True,
67
+ encoding="utf-8",
68
+ errors="replace",
69
+ check=False,
70
+ )
71
+ if completed_process.returncode != 0:
72
+ print(f"gh api error: {completed_process.stderr}", file=sys.stderr)
73
+ return EXIT_CODE_GH_ERROR
74
+ return 0
75
+
76
+
77
+ def post_pr_comment(
78
+ *,
79
+ owner: str,
80
+ repo: str,
81
+ number: int,
82
+ body: str,
83
+ ) -> int:
84
+ """Post a general PR comment.
85
+
86
+ Args:
87
+ owner: GitHub repository owner.
88
+ repo: GitHub repository name.
89
+ number: Pull request number (issue_number for PR comments).
90
+ body: Comment body text.
91
+
92
+ Returns:
93
+ 0 on success, EXIT_CODE_GH_ERROR on failure.
94
+ """
95
+ endpoint_path = GH_ISSUE_COMMENT_CREATE_PATH_TEMPLATE.format(
96
+ owner=owner, repo=repo, number=number
97
+ )
98
+ completed_process = subprocess.run(
99
+ [
100
+ "gh",
101
+ "api",
102
+ endpoint_path,
103
+ "-f",
104
+ f"body={body}",
105
+ ],
106
+ capture_output=True,
107
+ text=True,
108
+ encoding="utf-8",
109
+ errors="replace",
110
+ check=False,
111
+ )
112
+ if completed_process.returncode != 0:
113
+ print(f"gh api error: {completed_process.stderr}", file=sys.stderr)
114
+ return EXIT_CODE_GH_ERROR
115
+ return 0
116
+
117
+
118
+ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
119
+ """Parse command-line arguments.
120
+
121
+ Args:
122
+ all_argv: Command-line argument list.
123
+
124
+ Returns:
125
+ Parsed namespace with owner, repo, number, body, and in_reply_to.
126
+ """
127
+ parser = argparse.ArgumentParser(description=__doc__)
128
+ parser.add_argument("--owner", required=True, help="GitHub repository owner")
129
+ parser.add_argument("--repo", required=True, help="GitHub repository name")
130
+ parser.add_argument("--pr-number", required=True, type=int, help="Pull request number")
131
+ parser.add_argument("--body", required=True, help="Reply body text")
132
+ parser.add_argument(
133
+ "--in-reply-to",
134
+ type=int,
135
+ default=None,
136
+ help="Comment ID to reply to (omit for general PR comment)",
137
+ )
138
+ return parser.parse_args(all_argv)
139
+
140
+
141
+ def main(all_arguments: list[str]) -> int:
142
+ """Entry point for post_fix_reply.
143
+
144
+ Args:
145
+ all_arguments: Command-line arguments.
146
+
147
+ Returns:
148
+ 0 on success, EXIT_CODE_GH_ERROR on failure.
149
+ """
150
+ arguments = parse_arguments(all_arguments)
151
+ if arguments.in_reply_to is not None:
152
+ return post_inline_reply(
153
+ owner=arguments.owner,
154
+ repo=arguments.repo,
155
+ number=getattr(arguments, "pr_number"),
156
+ body=arguments.body,
157
+ in_reply_to=arguments.in_reply_to,
158
+ )
159
+ return post_pr_comment(
160
+ owner=arguments.owner,
161
+ repo=arguments.repo,
162
+ number=getattr(arguments, "pr_number"),
163
+ body=arguments.body,
164
+ )
165
+
166
+
167
+ if __name__ == "__main__":
168
+ raise SystemExit(main(sys.argv[1:]))
@@ -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
 
@@ -198,17 +198,78 @@ The subagent receives this prompt and loops internally — the lead does not re-
198
198
  <qbug_temp_dir>/loop-<loop_count>-audit.json per the contract's
199
199
  persistence schema.
200
200
 
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].
201
+ Post ONE review per loop via `post_audit_thread.py`. Before
202
+ serializing, partition the merged findings into anchored (line
203
+ appears in the captured diff) and unanchored (line is not in the
204
+ diff) buckets. Only anchored findings are written to
205
+ `<qbug_temp_dir>/loop-<loop_count>-findings.json` the GitHub
206
+ reviews API rejects the entire POST if any inline comment in
207
+ `comments[]` targets a line not in the diff at `--commit`, so a
208
+ single unanchored entry breaks the whole review. Unanchored
209
+ findings surface in the loop's user-facing summary rather than
210
+ as inline anchored comments. The JSON root is a list of objects
211
+ shaped `{path, line, side, severity, description, fix_summary}`.
212
+ For each anchored finding, map `file` → `path`; split the
213
+ finding's `failure_mode` at the literal `Fix:` heading so the
214
+ failure narrative becomes `description` and the suffix beginning
215
+ at `Fix:` (including the trailing `Validation:` clause) becomes
216
+ `fix_summary` (the `failure_mode` field carries the full
217
+ audit-to-fix handoff per
218
+ [`agents/code-quality-agent.md`](../../agents/code-quality-agent.md)).
219
+ When a finding's `failure_mode` omits the `Fix:` heading, write
220
+ the full text to BOTH `description` and `fix_summary`. Set
221
+ `side="RIGHT"` for every entry. Zero anchored findings → write
222
+ `[]` and pass `--state CLEAN`; one or more anchored findings →
223
+ pass `--state DIRTY` with the full list.
224
+
225
+ **Self-PR precondition.** GitHub rejects both `APPROVE` and
226
+ `REQUEST_CHANGES` reviews when the authenticated identity matches
227
+ the PR author with HTTP 422; `post_audit_thread.py` retries and
228
+ then exits 2. To run qbug on a PR you authored, switch `gh auth`
229
+ to an alternate reviewer identity (a separate GitHub account)
230
+ BEFORE invoking the skill. Without this switch, exit 2 is a hard
231
+ halt — there is no automated fallback path. The script does not
232
+ auto-downgrade on the self-PR case.
233
+
234
+ ```
235
+ python "${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/post_audit_thread.py" \
236
+ --skill qbug \
237
+ --owner <owner> \
238
+ --repo <repo> \
239
+ --pr-number <pr_number> \
240
+ --commit <head_sha> \
241
+ --state <CLEAN|DIRTY> \
242
+ --findings-json <qbug_temp_dir>/loop-<loop_count>-findings.json
243
+ ```
244
+
245
+ The script POSTs a single review with `event=APPROVE` on CLEAN
246
+ (the request event; GitHub stores it as `state=APPROVED`; empty
247
+ `comments[]`, body documents "no findings") or
248
+ `event=REQUEST_CHANGES` on DIRTY (one inline anchored comment per
249
+ finding; each becomes its own resolvable thread on the PR). It
250
+ handles retries internally (1s / 4s / 16s backoff across four
251
+ attempts). Exit 0 emits the new review's `html_url` to stdout;
252
+ extract the numeric review id from the URL's
253
+ `#pullrequestreview-<id>` suffix (the trailing URL fragment of
254
+ `html_url`, the part after `#`). Then harvest child-comment URLs **and PR review
255
+ thread node ids** via
256
+ `pull_request_read(method="get_review_comments",
257
+ owner=<owner>, repo=<repo>, pullNumber=<pr_number>)` filtered to
258
+ that review id. The same response carries each
259
+ comment's PR review thread node id (e.g. `PRRT_kwDOxxx`) — match
260
+ children to findings in the order they appear in the findings
261
+ JSON. Each `loop_comment_index[finding_id]` entry must carry
262
+ `finding_comment_id` (numeric, used by
263
+ `add_reply_to_pull_request_comment`), `finding_comment_url`, and
264
+ `thread_node_id` (`PRRT_kwDOxxx`, used by `resolve_thread`) so
265
+ the FIX step can reply against the comment and resolve the
266
+ thread.
267
+
268
+ Exit 2 means retry exhaustion — exit
269
+ `error: post_audit_thread retry exhausted` without retrying and
270
+ without falling back to a flat issue comment. A hard blocker on
271
+ the audit-posting path is a halt condition, not a fallback
272
+ opportunity.
212
273
 
213
274
  Update state: last_action="audited", last_findings=counts.
214
275
  Append `<N> audit: <P0>P0 / <P1>P1 / <P2>P2` to audit_log.
@@ -245,12 +306,54 @@ The subagent receives this prompt and loops internally — the lead does not re-
245
306
  Write <qbug_temp_dir>/loop-<loop_count>-diagnostics.json per the
246
307
  contract's diagnostics schema (all eight keys required).
247
308
 
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>`
309
+ For each finding, atomically (a) post the fix reply and
310
+ (b) call `resolve_thread`. The two calls form one logical action
311
+ per thread do not yield to the lead between them, and do not
312
+ batch all replies before any resolves.
313
+
314
+ (a) Reply via
315
+ `add_reply_to_pull_request_comment(commentId=<loop_comment_index[finding_id].finding_comment_id>,
316
+ body=<reply_body>, owner=<owner>, repo=<repo>,
317
+ pullNumber=<pr_number>)`. The reply body uses the unified template
318
+ at [`../../_shared/pr-loop/audit-reply-template.md`](../../_shared/pr-loop/audit-reply-template.md).
319
+ Skeleton (identical across all paths):
320
+
321
+ ```
322
+ **Claude finished @<reviewer>'s task** —— <status_line>
323
+
324
+ ---
325
+ ### <action_heading> ✅
326
+
327
+ <1–2 paragraph plain-language explanation>
328
+
329
+ **`<file>:<line>`:**
330
+ - <bullet describing change or rationale>
331
+ - <bullet describing change or rationale>
332
+
333
+ <closing paragraph>
334
+ ```
335
+
336
+ Per-path `<status_line>` / `<action_heading>`:
337
+ - `status=fixed`: `Fixed in <short_sha>` (first 7 chars) /
338
+ finding-specific action verb (e.g.,
339
+ `Replaced Any with concrete type`).
340
+ - `status=could_not_address`: `Could not address this loop` /
341
+ one-line reason text.
342
+ - `status=hook_blocked`: `Hook blocked the fix commit` /
343
+ one-line hook summary.
344
+
345
+ Body text is passed directly as string parameters — no temp
346
+ files, no jq, no shell pipes.
347
+
348
+ (b) Immediately call
349
+ `pull_request_review_write(method="resolve_thread",
350
+ threadId=<loop_comment_index[finding_id].thread_node_id>,
351
+ owner=<owner>, repo=<repo>, pullNumber=<pr_number>)` for the
352
+ same thread (this is the PR review thread node ID —
353
+ `PRRT_kwDOxxx` — distinct from the numeric comment ID; the
354
+ AUDIT step captures it at audit time when calling
355
+ `get_review_comments` and stores it on each
356
+ `loop_comment_index` entry alongside `finding_comment_id`).
254
357
 
255
358
  Update state: last_action="fixed". Append
256
359
  `<N> fix: <short_sha> — <fixed>/<could_not_address>/<hook_blocked>`
@@ -260,14 +363,16 @@ The subagent receives this prompt and loops internally — the lead does not re-
260
363
  </cycle>
261
364
 
262
365
  <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._
366
+ Populate these two fields on each finding entry of the JSON payload
367
+ consumed by `post_audit_thread.py` (the script renders the inline
368
+ comment body via `INLINE_COMMENT_BODY_TEMPLATE`):
369
+
370
+ ```json
371
+ {
372
+ "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.",
373
+ "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."
374
+ }
375
+ ```
271
376
  </example_finding_body>
272
377
 
273
378
  <constraints>
@@ -315,7 +420,7 @@ Delete the resolved `<qbug_temp_dir>` tree and any `.qbug-*.md` temp files in th
315
420
  - **Code rules gate before every AUDIT.** Same `validate_content` logic as /bugteam.
316
421
  - **One commit per FIX action.** Linear branch, fast-forward push only.
317
422
  - **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.
423
+ - **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
424
  - **PR description rewrite on every exit**, same as /bugteam Step 4.5.
320
425
  - **Temp file cleanup on every exit path.**
321
426
  - **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.