claude-dev-env 1.93.1 → 1.95.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 (262) hide show
  1. package/_shared/advisor/CLAUDE.md +2 -2
  2. package/_shared/advisor/advisor-protocol.md +35 -27
  3. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +3 -2
  4. package/_shared/advisor/scripts/model_tier_run_validator.py +23 -15
  5. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +81 -17
  6. package/_shared/pr-loop/CLAUDE.md +1 -0
  7. package/_shared/pr-loop/audit-contract.md +1 -1
  8. package/_shared/pr-loop/gh-payloads.md +3 -3
  9. package/_shared/pr-loop/post-audit-thread-contract.md +51 -0
  10. package/_shared/pr-loop/scripts/README.md +2 -2
  11. package/_shared/pr-loop/scripts/fix_hookspath.py +47 -12
  12. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -69
  13. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -374
  14. package/_shared/pr-loop/state-schema.md +3 -22
  15. package/bin/CLAUDE.md +11 -2
  16. package/bin/ever-shipped-skills.mjs +70 -0
  17. package/bin/install.mjs +138 -13
  18. package/bin/install.prune.test.mjs +457 -0
  19. package/bin/install.test.mjs +10 -4
  20. package/docs/CODE_RULES.md +1 -1
  21. package/hooks/blocking/CLAUDE.md +2 -0
  22. package/hooks/blocking/code_rules_enforcer.py +4 -0
  23. package/hooks/blocking/code_rules_shared.py +82 -0
  24. package/hooks/blocking/code_rules_test_layout.py +9 -3
  25. package/hooks/blocking/pii_commit_command.py +398 -0
  26. package/hooks/blocking/pii_payload_scan.py +133 -0
  27. package/hooks/blocking/pii_prevention_blocker.py +151 -449
  28. package/hooks/blocking/plain_language_blocker.py +138 -4
  29. package/hooks/blocking/sensitive_file_protector.py +114 -48
  30. package/hooks/blocking/tdd_enforcer.py +9 -2
  31. package/hooks/blocking/test_code_rules_enforcer_scratchpad.py +105 -0
  32. package/hooks/blocking/test_code_rules_shared.py +181 -0
  33. package/hooks/blocking/test_pii_prevention_blocker.py +299 -0
  34. package/hooks/blocking/test_plain_language_blocker_allowlist.py +184 -0
  35. package/hooks/blocking/test_sensitive_file_protector.py +185 -0
  36. package/hooks/blocking/test_tdd_enforcer_scratchpad.py +105 -0
  37. package/hooks/blocking/test_verdict_directory_write_blocker.py +42 -7
  38. package/hooks/hooks_constants/CLAUDE.md +4 -2
  39. package/hooks/hooks_constants/code_rules_path_utils_constants.py +0 -2
  40. package/hooks/hooks_constants/harness_scratchpad_constants.py +17 -0
  41. package/hooks/hooks_constants/local_identity.py +65 -15
  42. package/hooks/hooks_constants/pii_prevention_constants.py +16 -0
  43. package/hooks/hooks_constants/plain_language_blocker_constants.py +5 -0
  44. package/hooks/hooks_constants/pr_description_enforcer_constants.py +1 -1
  45. package/hooks/hooks_constants/sensitive_file_protector_constants.py +42 -0
  46. package/hooks/hooks_constants/test_local_identity.py +68 -0
  47. package/hooks/pyproject.toml +75 -4
  48. package/hooks/validators/CLAUDE.md +1 -1
  49. package/hooks/validators/README.md +2 -0
  50. package/hooks/validators/python_style_checks.py +114 -136
  51. package/hooks/validators/python_style_helpers.py +95 -0
  52. package/hooks/validators/test_python_style_checks.py +0 -164
  53. package/hooks/validators/test_python_style_checks_decorator_gap.py +119 -0
  54. package/hooks/validators/test_python_style_fixes.py +251 -0
  55. package/hooks/validators/test_python_style_helpers.py +125 -0
  56. package/package.json +1 -1
  57. package/rules/CLAUDE.md +1 -0
  58. package/rules/anti-corollary-tests.md +69 -0
  59. package/rules/bdd.md +1 -3
  60. package/rules/code-reviews.md +1 -1
  61. package/rules/gh-paginate.md +1 -1
  62. package/rules/plain-language.md +2 -0
  63. package/scripts/CLAUDE.md +10 -1
  64. package/scripts/Get-SessionAccount.ps1 +210 -0
  65. package/scripts/tests/CLAUDE.md +15 -1
  66. package/scripts/tests/Get-SessionAccount.Tests.ps1 +350 -0
  67. package/skills/CLAUDE.md +6 -27
  68. package/skills/anthropic-plan/SKILL.md +1 -13
  69. package/skills/autoconverge/CLAUDE.md +5 -2
  70. package/skills/autoconverge/SKILL.md +112 -387
  71. package/skills/autoconverge/reference/CLAUDE.md +4 -1
  72. package/skills/autoconverge/reference/closing-report.md +9 -2
  73. package/skills/autoconverge/reference/convergence.md +26 -22
  74. package/skills/autoconverge/reference/headless-safety.md +44 -0
  75. package/skills/autoconverge/reference/multi-pr.md +88 -0
  76. package/skills/autoconverge/reference/self-closing-loop.md +84 -0
  77. package/skills/autoconverge/reference/stop-conditions.md +21 -13
  78. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +87 -27
  79. package/skills/autoconverge/workflow/converge.contract.test.mjs +40 -10
  80. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +179 -47
  81. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  82. package/skills/autoconverge/workflow/converge.mjs +286 -96
  83. package/skills/bugteam/CLAUDE.md +1 -1
  84. package/skills/bugteam/CONSTRAINTS.md +3 -3
  85. package/skills/bugteam/PROMPTS.md +8 -19
  86. package/skills/bugteam/SKILL.md +13 -14
  87. package/skills/bugteam/reference/CLAUDE.md +1 -1
  88. package/skills/bugteam/reference/README.md +2 -3
  89. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  90. package/skills/bugteam/reference/copilot-gap-analysis.md +2 -0
  91. package/skills/bugteam/reference/github-pr-reviews.md +56 -38
  92. package/skills/bugteam/reference/team-setup.md +11 -11
  93. package/skills/bugteam/scripts/CLAUDE.md +6 -6
  94. package/skills/bugteam/scripts/README.md +20 -8
  95. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +13 -1463
  96. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +23 -358
  97. package/skills/bugteam/scripts/bugteam_preflight.py +13 -315
  98. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +2 -3
  99. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +34 -1053
  100. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +30 -347
  101. package/skills/bugteam/scripts/test_bugteam_preflight.py +30 -305
  102. package/skills/closeout/SKILL.md +153 -0
  103. package/skills/closeout/reference/handoff-prompt-template.md +72 -0
  104. package/skills/closeout/reference/issue-body-templates.md +108 -0
  105. package/skills/closeout/reference/pii-redaction-checklist.md +36 -0
  106. package/skills/copilot-finding-triage/SKILL.md +80 -28
  107. package/skills/copilot-finding-triage/reference/tier-rubric.md +70 -11
  108. package/skills/copilot-finding-triage/templates/notification.md +8 -3
  109. package/skills/everything-search/SKILL.md +1 -2
  110. package/skills/grokify/SKILL.md +58 -0
  111. package/skills/grokify/templates/handoff-template.md +92 -0
  112. package/skills/orchestrator/SKILL.md +35 -25
  113. package/skills/orchestrator-refresh/SKILL.md +15 -11
  114. package/skills/pr-converge/CLAUDE.md +1 -1
  115. package/skills/pr-converge/SKILL.md +132 -233
  116. package/skills/pr-converge/reference/convergence-gates.md +102 -66
  117. package/skills/pr-converge/reference/examples.md +37 -25
  118. package/skills/pr-converge/reference/fix-protocol.md +7 -7
  119. package/skills/pr-converge/reference/multi-pr-orchestration.md +10 -4
  120. package/skills/pr-converge/reference/per-tick.md +64 -41
  121. package/skills/pr-converge/reference/state-schema.md +27 -2
  122. package/skills/pr-converge/scripts/CLAUDE.md +8 -0
  123. package/skills/pr-converge/scripts/README.md +4 -3
  124. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +23 -0
  125. package/skills/pr-converge/scripts/check_convergence.py +189 -645
  126. package/skills/pr-converge/scripts/check_convergence_gates.py +235 -0
  127. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +219 -0
  128. package/skills/pr-converge/scripts/conftest.py +8 -0
  129. package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -0
  130. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -0
  131. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +26 -0
  132. package/skills/pr-converge/scripts/test_check_convergence.py +73 -228
  133. package/skills/pr-converge/scripts/test_check_convergence_contract.py +321 -0
  134. package/skills/pr-converge/scripts/test_check_convergence_gates.py +68 -0
  135. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +75 -0
  136. package/skills/pr-fix-protocol/SKILL.md +20 -20
  137. package/skills/privacy-hygiene/SKILL.md +4 -3
  138. package/skills/reviewer-gates/SKILL.md +26 -8
  139. package/skills/session-log/SKILL.md +11 -12
  140. package/skills/session-tidy/SKILL.md +40 -30
  141. package/skills/skill-builder/CLAUDE.md +13 -10
  142. package/skills/skill-builder/SKILL.md +31 -31
  143. package/skills/skill-builder/references/CLAUDE.md +10 -6
  144. package/skills/skill-builder/references/delegation-map.md +13 -4
  145. package/skills/skill-builder/references/description-field.md +113 -0
  146. package/skills/skill-builder/references/self-audit-checklist.md +21 -4
  147. package/skills/skill-builder/references/skill-modularity.md +113 -0
  148. package/skills/skill-builder/references/skill-types.md +4 -0
  149. package/skills/skill-builder/templates/CLAUDE.md +4 -4
  150. package/skills/skill-builder/templates/gap-analysis.md +25 -1
  151. package/skills/skill-builder/workflows/CLAUDE.md +6 -4
  152. package/skills/skill-builder/workflows/improve-skill.md +17 -16
  153. package/skills/skill-builder/workflows/new-skill.md +34 -10
  154. package/skills/skill-builder/workflows/polish-skill.md +59 -43
  155. package/skills/team-advisor/SKILL.md +15 -11
  156. package/skills/usage-pause/SKILL.md +10 -14
  157. package/system-prompts/software-engineer.xml +7 -6
  158. package/hooks/validators/test_verify_paths.py +0 -32
  159. package/hooks/validators/verify_paths.py +0 -57
  160. package/skills/bdd-protocol/CLAUDE.md +0 -26
  161. package/skills/bdd-protocol/SKILL.md +0 -30
  162. package/skills/bdd-protocol/references/CLAUDE.md +0 -10
  163. package/skills/bdd-protocol/references/anti-patterns.md +0 -26
  164. package/skills/bdd-protocol/references/example-mapping.md +0 -23
  165. package/skills/bg-agent/CLAUDE.md +0 -17
  166. package/skills/bg-agent/SKILL.md +0 -69
  167. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +0 -55
  168. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_fix_hookspath_constants.py +0 -26
  169. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_preflight_constants.py +0 -35
  170. package/skills/caveman/CLAUDE.md +0 -15
  171. package/skills/caveman/SKILL.md +0 -38
  172. package/skills/code/CLAUDE.md +0 -17
  173. package/skills/code/SKILL.md +0 -178
  174. package/skills/deep-research/CLAUDE.md +0 -17
  175. package/skills/deep-research/SKILL.md +0 -80
  176. package/skills/findbugs/CLAUDE.md +0 -20
  177. package/skills/findbugs/SKILL.md +0 -241
  178. package/skills/fixbugs/CLAUDE.md +0 -19
  179. package/skills/fixbugs/SKILL.md +0 -142
  180. package/skills/gh-paginate/CLAUDE.md +0 -18
  181. package/skills/gh-paginate/SKILL.md +0 -84
  182. package/skills/gotcha/CLAUDE.md +0 -33
  183. package/skills/gotcha/SKILL.md +0 -73
  184. package/skills/implement/CLAUDE.md +0 -27
  185. package/skills/implement/SKILL.md +0 -66
  186. package/skills/implement/scripts/CLAUDE.md +0 -22
  187. package/skills/implement/scripts/append_note.py +0 -133
  188. package/skills/implement/scripts/implement_scripts_constants/CLAUDE.md +0 -22
  189. package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
  190. package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +0 -12
  191. package/skills/implement/scripts/test_append_note.py +0 -191
  192. package/skills/log-audit/CLAUDE.md +0 -20
  193. package/skills/log-audit/SKILL.md +0 -66
  194. package/skills/log-audit/reference/CLAUDE.md +0 -9
  195. package/skills/log-audit/reference/charter.md +0 -52
  196. package/skills/log-audit/scripts/CLAUDE.md +0 -27
  197. package/skills/log-audit/scripts/cluster_recurrences.py +0 -261
  198. package/skills/log-audit/scripts/collect_log_window.py +0 -199
  199. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +0 -12
  200. package/skills/log-audit/scripts/log_audit_constants/__init__.py +0 -0
  201. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +0 -23
  202. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +0 -24
  203. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +0 -49
  204. package/skills/log-audit/scripts/mine_copilot_findings.py +0 -302
  205. package/skills/log-audit/scripts/test_cluster_recurrences.py +0 -160
  206. package/skills/log-audit/scripts/test_collect_log_window.py +0 -111
  207. package/skills/log-audit/scripts/test_mine_copilot_findings.py +0 -126
  208. package/skills/logifix/CLAUDE.md +0 -36
  209. package/skills/logifix/SKILL.md +0 -69
  210. package/skills/logifix/scripts/CLAUDE.md +0 -16
  211. package/skills/logifix/scripts/logifix.ps1 +0 -205
  212. package/skills/monitor-open-prs/CLAUDE.md +0 -34
  213. package/skills/monitor-open-prs/SKILL.md +0 -88
  214. package/skills/monitor-open-prs/scripts/CLAUDE.md +0 -17
  215. package/skills/monitor-open-prs/scripts/discover_open_prs.py +0 -69
  216. package/skills/monitor-open-prs/scripts/test_discover_open_prs.py +0 -149
  217. package/skills/monitor-open-prs/test_skill_contract.py +0 -27
  218. package/skills/post-audit-findings/SKILL.md +0 -80
  219. package/skills/pr-consistency-audit/CLAUDE.md +0 -34
  220. package/skills/pr-consistency-audit/SKILL.md +0 -116
  221. package/skills/pr-consistency-audit/reference/CLAUDE.md +0 -16
  222. package/skills/pr-consistency-audit/reference/detection-rules.md +0 -96
  223. package/skills/pr-consistency-audit/reference/illustrations.md +0 -78
  224. package/skills/pr-review-responder/CLAUDE.md +0 -35
  225. package/skills/pr-review-responder/EXAMPLES.md +0 -590
  226. package/skills/pr-review-responder/PRINCIPLES.md +0 -533
  227. package/skills/pr-review-responder/README.md +0 -168
  228. package/skills/pr-review-responder/SKILL.md +0 -203
  229. package/skills/pr-review-responder/TESTING.md +0 -350
  230. package/skills/pr-scope-resolve/SKILL.md +0 -45
  231. package/skills/pre-compact/CLAUDE.md +0 -24
  232. package/skills/pre-compact/SKILL.md +0 -134
  233. package/skills/qbug/CLAUDE.md +0 -40
  234. package/skills/qbug/SKILL.md +0 -387
  235. package/skills/qbug/test_qbug_skill_audit_schema.py +0 -152
  236. package/skills/qbug/test_qbug_skill_post_fix_audit.py +0 -103
  237. package/skills/refine/CLAUDE.md +0 -44
  238. package/skills/refine/SKILL.md +0 -262
  239. package/skills/refine/templates/CLAUDE.md +0 -17
  240. package/skills/refine/templates/implementation-notes-template.html +0 -56
  241. package/skills/refine/templates/plan-template.md +0 -60
  242. package/skills/research-mode/CLAUDE.md +0 -35
  243. package/skills/research-mode/SKILL.md +0 -53
  244. package/skills/structure-prompt/CLAUDE.md +0 -42
  245. package/skills/structure-prompt/SKILL.md +0 -44
  246. package/skills/structure-prompt/reference/CLAUDE.md +0 -28
  247. package/skills/structure-prompt/reference/adversarial-tuning.md +0 -62
  248. package/skills/structure-prompt/reference/block-classification.md +0 -27
  249. package/skills/structure-prompt/reference/canonical-case.md +0 -48
  250. package/skills/structure-prompt/reference/citation-depth.md +0 -70
  251. package/skills/structure-prompt/reference/cleanup.md +0 -33
  252. package/skills/structure-prompt/reference/constraints.md +0 -33
  253. package/skills/structure-prompt/reference/directives.md +0 -37
  254. package/skills/structure-prompt/reference/examples.md +0 -72
  255. package/skills/structure-prompt/reference/instantiation.md +0 -51
  256. package/skills/structure-prompt/reference/output-contract.md +0 -72
  257. package/skills/structure-prompt/reference/per-category.md +0 -23
  258. package/skills/structure-prompt/reference/persona.md +0 -38
  259. package/skills/structure-prompt/reference/research.md +0 -33
  260. package/skills/structure-prompt/reference/structure.md +0 -28
  261. package/skills/verified-build/CLAUDE.md +0 -33
  262. package/skills/verified-build/SKILL.md +0 -33
@@ -17,7 +17,7 @@ export const meta = {
17
17
  { title: 'Reuse', detail: 'Before convergence, one reuse lens scans the full diff for reuse improvements that are certain, behaviorally identical, and autonomously implementable, and applies the qualifying ones in one commit' },
18
18
  { title: 'Converge', detail: 'A deterministic static sweep runs first each round; then code-review, bug-audit, and self-review run in parallel on the same HEAD; one clean-coder applies all fixes; the loop repeats until every lens is clean on a stable HEAD' },
19
19
  { title: 'Bugbot gate', detail: 'A terminal confirmation gate that runs once the internal lenses are clean; when Bugbot is disabled or unavailable it spawns no agent and passes, otherwise it fetches Bugbot\'s verdict and routes any finding back into Converge' },
20
- { title: 'Copilot gate', detail: 'When the quota pre-check reports Copilot out of premium-request quota or unavailable, skip the gate with no agent spawned; otherwise request a Copilot review and poll up to the configured cap, then route findings by tier — self-healing findings flow back into Converge, and one or more code-concern findings return blocker user-review with the findings for the orchestrator to gate on rather than auto-fixing or marking the PR ready; when Copilot is down or out of quota log a notice and mark the PR ready with the gate bypassed' },
20
+ { title: 'Copilot gate', detail: 'When the quota pre-check reports Copilot out of premium-request quota or unavailable, skip the gate with no agent spawned; otherwise request a Copilot review and poll up to the configured cap, then route findings by tier — self-healing findings flow back into Converge, and each code-concern finding goes to its own verifier agent that must execute a check against HEAD: a confirmed finding (defect reproduced by a run command) joins the fix round carrying its repro, a refuted finding (correct behavior demonstrated by a run command) is replied-to and resolved with the check evidence, and only inconclusive findings return blocker user-review for the orchestrator to gate on rather than auto-fixing or marking the PR ready; when Copilot is down or out of quota log a notice and mark the PR ready with the gate bypassed' },
21
21
  { title: 'Finalize', detail: 'Run check_convergence.py; mark draft=false on a full pass' },
22
22
  ],
23
23
  }
@@ -481,7 +481,9 @@ function serializeOneLineJson(valueToSerialize) {
481
481
  /**
482
482
  * Spawn a fresh general-utility general-purpose agent for its administrative task:
483
483
  * 'post-clean-audit' posts the terminal CLEAN bugteam review. The agent edits no
484
- * code.
484
+ * code. The post_audit_thread.py invocation and exit-code contract this prompt
485
+ * drives is owned by _shared/pr-loop/post-audit-thread-contract.md; the clean-audit
486
+ * bypass policy for a refused post is in reference/stop-conditions.md.
485
487
  * @param {'post-clean-audit'} task the short task name
486
488
  * @param {object} context task-specific context
487
489
  * @returns {Promise<object>} the task result
@@ -537,24 +539,33 @@ function runGeneralUtilityTask(task, context) {
537
539
  * the FINALIZE loop routes to the repair path, which loops back and re-runs this
538
540
  * same combined check; only the passing path ever attempts gh pr ready. The agent
539
541
  * edits no code, so it runs on the cheapest model at low effort.
540
- * @param {object} context carries head, bugbotDown, and copilotDown
542
+ * @param {object} context carries head, bugbotDown, copilotDown, and bugteamPostBlocked
541
543
  * @returns {Promise<object>} FINALIZE_SCHEMA result
542
544
  */
543
545
  function runConvergenceCheck(context) {
544
546
  const label = 'finalize-check'
545
547
  const bugbotDownFlag = context.bugbotDown ? ' --bugbot-down' : ''
546
548
  const copilotDownFlag = context.copilotDown ? ' --copilot-down' : ''
547
- const copilotOptOut = context.copilotDown
548
- ? ` Copilot is down this run, so before gh pr ready export the token in this same shell session so the independent mark-ready blocker hook's convergence re-check inherits it:\n bash: export CLAUDE_REVIEWS_DISABLED="copilot" (PowerShell: $env:CLAUDE_REVIEWS_DISABLED = "copilot")\n`
549
+ const bugteamPostBlockedFlag = context.bugteamPostBlocked ? ' --bugteam-post-blocked' : ''
550
+ const bugteamPostBlockedNote = context.bugteamPostBlocked
551
+ ? ` The --bugteam-post-blocked flag is set because the environment refused the CLEAN bugteam review post this run. The review lenses already cleared this HEAD, so the check skips the bugteam CLEAN-review gate and reports that gate as PASS — bypassed (bugteam_post_blocked) rather than failing on a review that was never allowed to land.\n`
552
+ : ''
553
+ const reviewerOptOutTokens = []
554
+ if (context.bugbotDown) reviewerOptOutTokens.push('bugbot')
555
+ if (context.copilotDown) reviewerOptOutTokens.push('copilot')
556
+ if (context.bugteamPostBlocked) reviewerOptOutTokens.push('bugteam')
557
+ const reviewerOptOut = reviewerOptOutTokens.length > 0
558
+ ? ` Reviewer(s) opted out this run (${reviewerOptOutTokens.join(', ')}), so before gh pr ready export the token(s) in this same shell session so the independent mark-ready blocker hook's convergence re-check — which re-runs check_convergence.py with no flags — inherits the bypass through the env:\n bash: export CLAUDE_REVIEWS_DISABLED="${reviewerOptOutTokens.join(',')}" (PowerShell: $env:CLAUDE_REVIEWS_DISABLED = "${reviewerOptOutTokens.join(',')}")\n`
549
559
  : ''
550
560
  return convergeReadOnlyAgent(
551
561
  `Run the convergence gate for ${prCoordinates} on HEAD ${context.head} and, only when every gate passes, mark the PR ready. Do not edit code.\n\n` +
552
- `1. Run: python "${CONFIG.sharedScripts}/check_convergence.py" --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber}${bugbotDownFlag}${copilotDownFlag}\n` +
562
+ `1. Run: python "${CONFIG.sharedScripts}/check_convergence.py" --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber}${bugbotDownFlag}${copilotDownFlag}${bugteamPostBlockedFlag}\n` +
563
+ bugteamPostBlockedNote +
553
564
  ` Exit 0 -> every gate passed: set pass:true, failures:[].\n` +
554
565
  ` Exit 1 -> set pass:false and failures to each printed FAIL line verbatim.\n` +
555
566
  ` Exit 2 -> retry once; if it still errors, set pass:false, failures:["check_convergence gh error"].\n\n` +
556
567
  `2. Only when step 1 set pass:true, mark the PR ready and confirm it left draft state:\n` +
557
- copilotOptOut +
568
+ reviewerOptOut +
558
569
  ` Run: gh pr ready ${input.prNumber} --repo ${input.owner}/${input.repo}\n` +
559
570
  ` Re-query the draft state: gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq .draft\n` +
560
571
  ` Set ready:true only when the re-query prints false (the PR is no longer a draft); set ready:false when gh pr ready errors or the re-query still prints true.\n` +
@@ -635,6 +646,24 @@ const COPILOT_SCHEMA = {
635
646
  required: ['sha', 'clean', 'down', 'findings'],
636
647
  }
637
648
 
649
+ const COPILOT_VERIFY_VERDICTS = ['confirmed', 'refuted', 'inconclusive']
650
+
651
+ const COPILOT_VERIFY_SCHEMA = {
652
+ type: 'object',
653
+ additionalProperties: false,
654
+ properties: {
655
+ verdict: {
656
+ type: 'string',
657
+ enum: COPILOT_VERIFY_VERDICTS,
658
+ description: 'confirmed when the executed check tangibly reproduces the defect; refuted when the executed check tangibly demonstrates correct behavior in the exact scenario the finding claims is broken; inconclusive (the default) for everything else — no runnable check exists for the claim, the check is infeasible in this environment, the results are ambiguous, or the fix would require a product decision between defensible behaviors',
659
+ },
660
+ checkCommand: { type: 'string', description: 'the exact command(s) executed against the flagged HEAD; non-empty for confirmed and refuted — the workflow downgrades a conclusive verdict with an empty checkCommand to inconclusive' },
661
+ checkOutput: { type: 'string', description: 'the captured output of checkCommand demonstrating the behavior in question; non-empty for confirmed and refuted — the workflow downgrades a conclusive verdict with an empty checkOutput to inconclusive' },
662
+ evidence: { type: 'string', description: 'one line naming what check was attempted and what it showed, or why it was not decisive' },
663
+ },
664
+ required: ['verdict', 'checkCommand', 'checkOutput', 'evidence'],
665
+ }
666
+
638
667
  const REVIEWER_AVAILABILITY_SCHEMA = {
639
668
  type: 'object',
640
669
  additionalProperties: false,
@@ -1387,47 +1416,79 @@ function classifyReviewerGateOutcome(reviewerGate) {
1387
1416
  }
1388
1417
 
1389
1418
  /**
1390
- * Layer the Copilot-specific two-tier triage over the shared reviewer-gate
1391
- * classifier. The base classifier decides retry / down / approved and routes any
1392
- * findings to a fix; this wrapper then upgrades a fix round carrying a
1393
- * code-concern finding to a 'user-review' outcome, so the workflow neither
1394
- * auto-fixes it nor marks the PR ready. A self-healing-only fix round passes
1395
- * through unchanged and flows into the fix flow. Only the Copilot gate uses this
1396
- * wrapper; the terminal Bugbot gate keeps the base classifier, whose findings
1397
- * carry no tier and always route to a fix.
1398
- * @param {object|null|undefined} copilot the Copilot gate result, or null on agent failure
1399
- * @returns {{kind: string, findings?: Array<object>}} the next action
1419
+ * Split a Copilot round's findings by routing tier. Any tier other than the
1420
+ * explicit 'self-healing' counts as a code concern, so a missing or unexpected
1421
+ * tier routes to the verification stage rather than silently into the auto-fix
1422
+ * flow.
1423
+ * @param {Array<object>} findings the Copilot findings for the round
1424
+ * @returns {{selfHealing: Array<object>, codeConcern: Array<object>}} the tier partition
1400
1425
  */
1401
- function classifyCopilotOutcome(copilot) {
1402
- const outcome = classifyReviewerGateOutcome(copilot)
1403
- if (outcome.kind === 'fix' && hasCodeConcernFinding(outcome.findings)) {
1404
- return { kind: 'user-review', findings: outcome.findings }
1426
+ function partitionFindingsByTier(findings) {
1427
+ return {
1428
+ selfHealing: findings.filter((each) => each.tier === 'self-healing'),
1429
+ codeConcern: findings.filter((each) => each.tier !== 'self-healing'),
1405
1430
  }
1406
- return outcome
1407
1431
  }
1408
1432
 
1409
1433
  /**
1410
- * Decide whether a Copilot round carries a code-concern finding one that is
1411
- * behavior-changing or needs a product decision, so the workflow may neither
1412
- * auto-fix it nor mark the PR ready. Any tier other than the explicit
1413
- * 'self-healing' counts as a code concern, so a missing or unexpected tier
1414
- * defaults to code-concern rather than silently routing into the auto-fix flow.
1415
- * @param {Array<object>} findings the Copilot findings for the round
1416
- * @returns {boolean} true when at least one finding is not tier self-healing
1434
+ * Enforce the executed-check rule on one verifier result. A verdict is
1435
+ * conclusive (confirmed or refuted) only when an actual check ran: a dead
1436
+ * verifier agent (null result) is inconclusive, and a conclusive verdict whose
1437
+ * checkCommand or checkOutput is empty carries no executed check, so it is
1438
+ * downgraded to inconclusive rather than trusted source-reading reasoning
1439
+ * alone never confirms or refutes a finding.
1440
+ * @param {object|null|undefined} verifier the COPILOT_VERIFY_SCHEMA result, or null on agent failure
1441
+ * @returns {object} a verifier result whose verdict honors the executed-check rule
1417
1442
  */
1418
- function hasCodeConcernFinding(findings) {
1419
- return findings.some((each) => each.tier !== 'self-healing')
1443
+ function normalizeVerifierVerdict(verifier) {
1444
+ if (verifier == null) {
1445
+ return { verdict: 'inconclusive', checkCommand: '', checkOutput: '', evidence: 'verifier agent died — no check was executed' }
1446
+ }
1447
+ const isConclusive = verifier.verdict === 'confirmed' || verifier.verdict === 'refuted'
1448
+ const hasExecutedCheck = verifier.checkCommand.trim() !== '' && verifier.checkOutput.trim() !== ''
1449
+ if (isConclusive && !hasExecutedCheck) {
1450
+ return {
1451
+ ...verifier,
1452
+ verdict: 'inconclusive',
1453
+ evidence: `conclusive verdict arrived with an empty checkCommand or checkOutput, so no executed check backs it — downgraded to inconclusive. ${verifier.evidence || ''}`.trim(),
1454
+ }
1455
+ }
1456
+ return verifier
1457
+ }
1458
+
1459
+ /**
1460
+ * Fold a confirmed finding's executed repro into the finding detail the fix
1461
+ * steps render, so the fix prompt carries the exact repro command and captured
1462
+ * failing output, requires the before/after re-run of that same command, and
1463
+ * asks for the repro as a regression test. The verification field is dropped so
1464
+ * the finding matches the shape the fix flow renders.
1465
+ * @param {object} finding a code-concern finding carrying a confirmed verification
1466
+ * @returns {object} the finding with the repro folded into its detail
1467
+ */
1468
+ function attachVerifiedRepro(finding) {
1469
+ const { verification, ...bareFinding } = finding
1470
+ return {
1471
+ ...bareFinding,
1472
+ detail:
1473
+ `${finding.detail}\n\n` +
1474
+ `CONFIRMED BY EXECUTED CHECK — this repro already demonstrates the defect on HEAD.\n` +
1475
+ ` Repro command(s):\n${verification.checkCommand}\n` +
1476
+ ` Captured failing output:\n${verification.checkOutput}\n` +
1477
+ ` After fixing: re-run the exact repro command(s) above and capture the output showing the wrong behavior is gone; include that before/after output in the thread reply. Where the repo's test suite covers this surface, add the repro as a regression test.`,
1478
+ }
1420
1479
  }
1421
1480
 
1422
1481
  /**
1423
1482
  * Build the user-review payload the orchestrator hands to the
1424
- * copilot-finding-triage skill: the Copilot review link plus the code-concern
1425
- * findings pared to the fields the ntfy summary and the review gate read. The
1426
- * workflow returns this alongside blocker 'user-review' so the orchestrating
1427
- * session runs the notify-and-wait gate; a background workflow cannot hold for a
1428
- * human, so it carries the findings out rather than deciding them.
1483
+ * copilot-finding-triage skill: the Copilot review link plus the inconclusive
1484
+ * findings pared to the fields the ntfy summary and the review gate read, each
1485
+ * carrying its verifier's one-line evidence note (what check was attempted and
1486
+ * why it was not decisive). The workflow returns this alongside blocker
1487
+ * 'user-review' so the orchestrating session runs the notify-and-wait gate; a
1488
+ * background workflow cannot hold for a human, so it carries the findings out
1489
+ * rather than deciding them.
1429
1490
  * @param {object} copilot the COPILOT_SCHEMA gate result carrying the review URL
1430
- * @param {Array<object>} findings the Copilot findings for this HEAD
1491
+ * @param {Array<object>} findings the inconclusive findings for this HEAD, each carrying its verification
1431
1492
  * @returns {{reviewUrl: string, findings: Array<object>}} the triage payload
1432
1493
  */
1433
1494
  function buildUserReview(copilot, findings) {
@@ -1439,6 +1500,7 @@ function buildUserReview(copilot, findings) {
1439
1500
  severity: each.severity,
1440
1501
  tier: each.tier,
1441
1502
  title: each.title,
1503
+ evidence: each.verification?.evidence || '',
1442
1504
  })),
1443
1505
  }
1444
1506
  }
@@ -1660,7 +1722,7 @@ function runAuditLens(head, preflightResult) {
1660
1722
  * Self-review lens: the semantic pre-catch parity pass over the full diff. It
1661
1723
  * covers the doc-vs-code parity, test-assertion completeness, and
1662
1724
  * PR-description-vs-diff lanes that sit outside the A-P bug categories, reusing
1663
- * the pr-consistency-audit skill's canonical-source cross-reference method. It
1725
+ * the pr-consistency-audit prompt's canonical-source cross-reference method. It
1664
1726
  * stays on opus because line-citation accuracy, symbol attribution, and
1665
1727
  * inventory or count claims are semantic judgments a cheaper tier misreads. It
1666
1728
  * reports findings without editing.
@@ -1673,7 +1735,7 @@ function runSelfReviewLens(head, preflightResult) {
1673
1735
  `You are the self-review parity lens for ${prCoordinates}, HEAD ${head}. Review the FULL origin/main...HEAD diff — every file the PR touches. Do NOT edit, commit, or push.\n\n` +
1674
1736
  renderLensDiffContext(preflightResult) +
1675
1737
  `Read the pre-catch rubric at ${CONFIG.precatchRubric} for the three lane checklists, and cover each lane:\n` +
1676
- `1. Doc-vs-code parity: reuse the pr-consistency-audit skill's canonical-source cross-reference method ($HOME/.claude/skills/pr-consistency-audit/SKILL.md) and the drift rubric at $HOME/.claude/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md. Verify every line citation resolves, every referenced file or script path exists, every symbol is attributed to the file that defines it, and every inventory, count, and ordering claim matches the code.\n` +
1738
+ `1. Doc-vs-code parity: reuse the pr-consistency-audit prompt's canonical-source cross-reference method ($HOME/.claude/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml) and the drift rubric at $HOME/.claude/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md. Verify every line citation resolves, every referenced file or script path exists, every symbol is attributed to the file that defines it, and every inventory, count, and ordering claim matches the code.\n` +
1677
1739
  `2. Test-assertion completeness: every changed or new production path has a paired test that calls it and asserts on its behavior, and a changed test pins behavior rather than hiding it behind a mock.\n` +
1678
1740
  `3. PR-description-vs-diff two-way parity: fetch the PR body read-only, then confirm every PR-body claim maps to a hunk in the diff and every hunk maps to a claim; flag invented paths, invented counts, and out-of-scope changes.\n\n` +
1679
1741
  `Before returning clean, state one proof-of-absence line per lane naming what you checked. Return strictly the schema: clean=true with empty findings when all three lanes pass, otherwise one entry per finding (severity P0/P1/P2; category 'code-standard' for a pure doc or style parity gap with no behavioral impact, 'bug' otherwise; replyToCommentId=null). Set sha=${'`'}${head}${'`'}, down=false.`,
@@ -1818,27 +1880,59 @@ async function applyFixes(head, findings, sourceLabel) {
1818
1880
  }
1819
1881
 
1820
1882
  /**
1821
- * Blocker message for a CLEAN bugteam audit that did not land. The convergence
1822
- * gate's bugteam-review check can never pass without this review, so a blocked
1823
- * post stops the run with an actionable message rather than re-converging until
1824
- * the iteration cap. This fires only after the round already produced its grounded
1825
- * audit outcome for this HEAD all lenses clean, or code-standard-only findings
1826
- * deferred so re-issuing the run's own grounded post is legitimate recovery.
1827
- * Handles a dead post agent (a null result) as not posted.
1828
- * @param {string} head converged PR HEAD SHA
1829
- * @param {object} auditResult CLEAN_AUDIT_SCHEMA result from the post-clean-audit task, or null when the agent died
1830
- * @returns {string} the blocker message naming the post failure and the unblock path
1883
+ * Reason a CLEAN bugteam audit post did not land, read from a not-posted result.
1884
+ *
1885
+ * ::
1886
+ *
1887
+ * {posted: false, reason: 'denied by the classifier'} -> 'denied by the classifier'
1888
+ * null -> 'the post agent returned no result' (the spawn was refused)
1889
+ *
1890
+ * A null result is the environment-refused case: the post agent, or its very
1891
+ * spawn, returned nothing, so a fixed phrase stands in for the reason the agent
1892
+ * would otherwise carry.
1893
+ * @param {object} auditResult CLEAN_AUDIT_SCHEMA result from the post-clean-audit task, or null when the agent never ran
1894
+ * @returns {string} the one-line refusal reason
1831
1895
  */
1832
- function cleanAuditBlocker(head, auditResult) {
1833
- const reason = auditResult?.reason || 'the post agent returned no result'
1896
+ function cleanAuditPostReason(auditResult) {
1897
+ return auditResult?.reason || 'the post agent returned no result'
1898
+ }
1899
+
1900
+ /**
1901
+ * Build the run-level bypass note naming the HEAD and the refusal reason for a
1902
+ * CLEAN bugteam post the environment refused. See reference/stop-conditions.md
1903
+ * § "Clean-audit post bypassed" for the full policy.
1904
+ * @param {string} head converged PR HEAD SHA the CLEAN post targeted
1905
+ * @param {object} auditResult CLEAN_AUDIT_SCHEMA result from the post-clean-audit task, or null when the agent never ran
1906
+ * @returns {string} the run-level bypass note
1907
+ */
1908
+ function cleanAuditBypassNote(head, auditResult) {
1834
1909
  return (
1835
- `clean-audit post blocked: the CLEAN bugteam review could not be posted on HEAD ${head} (${reason}) — ` +
1836
- `the convergence gate's bugteam-review check can never pass without it, so the run stops rather than re-converge to the iteration cap. ` +
1837
- `To unblock: when the reason is a permission denial, allow post_audit_thread.py for this run with a Bash permission rule and re-run. ` +
1838
- `For any other failure (gh auth expiry, network, a script error), diagnose the printed reason, then re-issue the run's own grounded post: create a fresh temporary file whose exact content is an empty JSON array ([]) and re-run post_audit_thread.py --skill bugteam --state CLEAN for this lens-verified HEAD ${head} with that file as --findings-json. A CLEAN post carries an empty findings array by construction, so this re-runs the run's own command for the outcome the lenses already established for this HEAD, not a newly authored review.`
1910
+ `The CLEAN bugteam review could not be posted on HEAD ${head} ` +
1911
+ `(${cleanAuditPostReason(auditResult)}) every review lens already cleared this HEAD, ` +
1912
+ `so the CLEAN post is bypassed and the run proceeds to the terminal Bugbot gate ` +
1913
+ `with the convergence check's bugteam-review gate skipped.`
1839
1914
  )
1840
1915
  }
1841
1916
 
1917
+ /**
1918
+ * Resolve the run-level clean-audit note from a CLEAN bugteam post attempt.
1919
+ *
1920
+ * A landed post clears any earlier-round bypass note so FINALIZE runs the
1921
+ * bugteam gate; a refused post records the bypass note and logs it. This one
1922
+ * helper handles both outcomes at both post sites.
1923
+ * @param {object} auditResult CLEAN_AUDIT_SCHEMA result from the post task, or null when the agent never ran
1924
+ * @param {string} head converged PR HEAD SHA the CLEAN post targeted
1925
+ * @param {number} rounds current round number, named in the log line
1926
+ * @returns {string|null} the bypass note when the post was refused, or null when it landed
1927
+ */
1928
+ function resolveCleanAuditNote(auditResult, head, rounds) {
1929
+ if (auditResult?.posted) {
1930
+ return null
1931
+ }
1932
+ log(`Round ${rounds}: CLEAN bugteam post bypassed on ${head?.slice(0, 7)} (${cleanAuditPostReason(auditResult)}) — recording the bypass and proceeding to the terminal Bugbot gate`)
1933
+ return cleanAuditBypassNote(head, auditResult)
1934
+ }
1935
+
1842
1936
  /**
1843
1937
  * Copilot gate: request a Copilot review on HEAD and poll until it lands or the
1844
1938
  * poll cap is hit; return Copilot's findings or a down signal. Copilot is down
@@ -1862,7 +1956,7 @@ function runCopilotGate(head) {
1862
1956
  ` - No review after ${CONFIG.copilotMaxPolls} attempts -> Copilot is down for this run (unreachable, or silently out of quota with no notice): return {sha:${'`'}${head}${'`'}, clean:false, down:true, findings:[]}.\n\n` +
1863
1957
  `Tier every finding for routing, separate from its category:\n` +
1864
1958
  ` - tier 'self-healing': pure style, type hints, misplaced or unused imports, formatting, magic-value extraction, test-only changes, doc-or-description vs code mismatches, or code de-duplication — any fix that cannot change observable runtime behavior for production callers. These route into the fix flow automatically.\n` +
1865
- ` - tier 'code-concern': logic or correctness, security, data handling, error-handling semantics, or concurrency — anything behavior-changing or needing a product decision. These hold for a user-review gate rather than an auto-fix.\n` +
1959
+ ` - tier 'code-concern': logic or correctness, security, data handling, error-handling semantics, or concurrency — anything behavior-changing or needing a product decision. Each of these goes to a verification agent that must execute a check against HEAD; only findings the check leaves inconclusive hold for a user-review gate.\n` +
1866
1960
  ` - Classify as 'code-concern' whenever the tier is in doubt.\n\n` +
1867
1961
  `A down verdict is valid ONLY in two cases: the review request itself failed, or the FULL poll budget (${CONFIG.copilotMaxPolls} attempts x 360 seconds) elapsed with no review on HEAD. A successful review request means the review is in flight; returning down:true on a partial poll is an invalid result. Never end the poll early for any reason other than a received review on HEAD or an out-of-usage notice — not tooling friction, not turn-length pressure, not a failed attempt to write a polling helper. When your wait tooling fails, re-arm it and keep polling until the budget is spent.\n\n` +
1868
1962
  `Return strictly the schema.`,
@@ -1870,6 +1964,88 @@ function runCopilotGate(head) {
1870
1964
  )
1871
1965
  }
1872
1966
 
1967
+ /**
1968
+ * Spawn the verifier agent for one Copilot code-concern finding. The verifier
1969
+ * decides confirmed / refuted / inconclusive by executing a check against the
1970
+ * flagged HEAD; normalizeVerifierVerdict then enforces the executed-check rule
1971
+ * on its result. It routes through convergeAgent (not the read-only spawn)
1972
+ * because building a purpose-built check may write scratch files, but it fixes
1973
+ * nothing and never commits or pushes.
1974
+ * @param {string} head converged PR HEAD SHA
1975
+ * @param {object} finding one code-concern finding from the Copilot gate
1976
+ * @returns {Promise<object>} COPILOT_VERIFY_SCHEMA result
1977
+ */
1978
+ function runCopilotFindingVerifier(head, finding) {
1979
+ return convergeAgent(
1980
+ `You are the VERIFICATION step for one Copilot code-concern finding on ${prCoordinates}, HEAD ${head}. Decide whether the finding is tangibly real by EXECUTING a check. Do not fix anything, do not commit, and do not push.\n\n` +
1981
+ `The finding:\n${renderFindingsBlock([finding])}\n\n` +
1982
+ `THE HARD RULE — a verdict is conclusive ONLY if an actual check was executed. Reading the source and reasoning about it, however sound, never produces a conclusive verdict. A check is a concrete command you run against this HEAD — executing the flagged code path with crafted inputs, forcing the claimed error condition, or running a purpose-built test — whose captured output demonstrates the behavior in question. Source inspection may inform where to aim the check, but is never itself grounds for confirmed or refuted.\n\n` +
1983
+ `Steps:\n` +
1984
+ `1. Confirm the working tree is on the PR branch at HEAD ${head} with no uncommitted edits.\n` +
1985
+ `2. Read the flagged code only to aim the check, then build and run it. Keep any purpose-built test or scratch input in the OS temp dir, and leave the repo working tree exactly as you found it (git status clean when you finish).\n` +
1986
+ `3. Choose the verdict:\n` +
1987
+ ` - confirmed: your executed check tangibly reproduces the defect the finding claims — the captured output shows the wrong behavior.\n` +
1988
+ ` - refuted: your executed check tangibly demonstrates the code already behaves correctly in the exact scenario the finding claims is broken — the captured output shows the correct behavior.\n` +
1989
+ ` - inconclusive (the DEFAULT): everything else — no runnable check exists for the claim, the check is infeasible in this environment, the results are ambiguous, or the fix would require a product decision between defensible behaviors.\n\n` +
1990
+ `Return strictly the schema. For confirmed and refuted, checkCommand is the exact command(s) you ran and checkOutput is their captured output — both non-empty; the workflow downgrades a conclusive verdict with an empty checkCommand or checkOutput to inconclusive. evidence is one line naming what check was attempted and what it showed (for inconclusive: why it was not decisive).`,
1991
+ { label: `copilot-verify:${finding.file}:${finding.line}`, phase: 'Copilot gate', schema: COPILOT_VERIFY_SCHEMA, agentType: 'general-purpose', ...TIERS.sonnetMedium },
1992
+ )
1993
+ }
1994
+
1995
+ /**
1996
+ * Verify each code-concern Copilot finding with its own executed-check verifier,
1997
+ * all in parallel, and partition the round by normalized verdict. Reachable only
1998
+ * from the COPILOT phase's code-concern path, so a round with no code-concern
1999
+ * findings spawns no verifier.
2000
+ * @param {string} head converged PR HEAD SHA
2001
+ * @param {Array<object>} codeConcernFindings the round's code-concern findings
2002
+ * @returns {Promise<{confirmed: Array<object>, refuted: Array<object>, inconclusive: Array<object>}>} the findings, each carrying its verification
2003
+ */
2004
+ async function verifyCodeConcernFindings(head, codeConcernFindings) {
2005
+ const verdicts = await parallel(
2006
+ codeConcernFindings.map((each) => () => runCopilotFindingVerifier(head, each)),
2007
+ )
2008
+ const verified = codeConcernFindings.map((each, position) => ({
2009
+ ...each,
2010
+ verification: normalizeVerifierVerdict(verdicts[position]),
2011
+ }))
2012
+ return {
2013
+ confirmed: verified.filter((each) => each.verification.verdict === 'confirmed'),
2014
+ refuted: verified.filter((each) => each.verification.verdict === 'refuted'),
2015
+ inconclusive: verified.filter((each) => each.verification.verdict === 'inconclusive'),
2016
+ }
2017
+ }
2018
+
2019
+ /**
2020
+ * Reply to and resolve the review threads of findings refuted by an executed
2021
+ * check, quoting the check command and captured output so the thread records why
2022
+ * the finding counts clean. Findings with no review thread (replyToCommentId
2023
+ * null) need no reply, so a batch carrying no thread ids spawns no agent. Makes
2024
+ * no code edits, no commit, and no push.
2025
+ * @param {string} head converged PR HEAD SHA
2026
+ * @param {Array<object>} refutedFindings the refuted findings, each carrying its verification
2027
+ * @returns {Promise<string|null>} the agent transcript, or null when no finding carries a thread
2028
+ */
2029
+ function runRefutedThreadResolution(head, refutedFindings) {
2030
+ const threadBearing = refutedFindings.filter((each) => collectFindingThreadIds(each).length > 0)
2031
+ if (threadBearing.length === 0) return Promise.resolve(null)
2032
+ const findingsBlock = threadBearing
2033
+ .map((each, position) =>
2034
+ `${position + 1}. [${each.severity}] ${each.file}:${each.line} — ${each.title}\n` +
2035
+ ` (GitHub review comment ids: ${collectFindingThreadIds(each).join(', ')})\n` +
2036
+ ` Check command(s): ${each.verification.checkCommand}\n` +
2037
+ ` Captured output: ${each.verification.checkOutput}\n` +
2038
+ ` Evidence note: ${each.verification.evidence}`)
2039
+ .join('\n')
2040
+ return convergeAgent(
2041
+ `You are the THREAD-RESOLUTION step for ${threadBearing.length} Copilot finding(s) on ${prCoordinates}, HEAD ${head}, each refuted by an executed check: the check's captured output demonstrates the code already behaves correctly in the exact scenario the finding claims is broken. Make NO code edits, NO commit, and NO push — only reply to and resolve the review threads below.\n\n` +
2042
+ `Refuted findings with their check evidence:\n${findingsBlock}\n\n` +
2043
+ `For each finding: post an inline reply via python "${CONFIG.sharedScripts}/post_fix_reply.py" --owner ${input.owner} --repo ${input.repo} --pr-number ${input.prNumber} --in-reply-to <id> --body "<the check command(s) and the captured output demonstrating the correct behavior>". Then resolve the thread by its PRRT_ node id (GraphQL lookup on comment databaseId, then resolveReviewThread or the github MCP pull_request_review_write method=resolve_thread — not the numeric comment id).\n\n` +
2044
+ `Return a one-line summary naming the threads you resolved.`,
2045
+ { label: 'copilot-refuted-resolve', phase: 'Copilot gate', agentType: 'general-purpose', ...TIERS.sonnetMedium },
2046
+ )
2047
+ }
2048
+
1873
2049
  /**
1874
2050
  * Address the gates a convergence check reported as failing, then hand control
1875
2051
  * back to the converge phase: edit (clean-coder resolves bot threads, applies
@@ -2239,6 +2415,7 @@ let bugbotDown = input.bugbotDisabled || false
2239
2415
  let copilotDown = input.copilotDisabled || false
2240
2416
  let copilotNote = null
2241
2417
  let standardsNote = null
2418
+ let cleanAuditNote = null
2242
2419
  let hasStandardsFollowUpFiled = false
2243
2420
  let wasStandardsHardeningPrOpened = false
2244
2421
  let standardsFollowUpIssueUrl = ''
@@ -2246,6 +2423,15 @@ let reuseNote = null
2246
2423
  let reviewerAvailability = null
2247
2424
  const deferredPrs = []
2248
2425
 
2426
+ const assembleResult = (outcomeFields) => ({
2427
+ ...outcomeFields,
2428
+ standardsNote,
2429
+ copilotNote,
2430
+ cleanAuditNote,
2431
+ reuseNote,
2432
+ deferredPrs,
2433
+ })
2434
+
2249
2435
  const preflight = await runGitTask('preflight-git')
2250
2436
  reviewerAvailability = preflight
2251
2437
  if (isResolvedHeadUsable(preflight?.sha)) {
@@ -2333,10 +2519,7 @@ while (iterations < CONFIG.maxIterations) {
2333
2519
  deferredStandardsFindings: findings,
2334
2520
  standardsDeferral,
2335
2521
  })
2336
- if (!auditResult?.posted) {
2337
- blocker = cleanAuditBlocker(head, auditResult)
2338
- break
2339
- }
2522
+ cleanAuditNote = resolveCleanAuditNote(auditResult, head, rounds)
2340
2523
  phase = 'BUGBOT'
2341
2524
  continue
2342
2525
  }
@@ -2377,10 +2560,7 @@ while (iterations < CONFIG.maxIterations) {
2377
2560
  head = null
2378
2561
  continue
2379
2562
  }
2380
- if (!auditResult?.posted) {
2381
- blocker = cleanAuditBlocker(head, auditResult)
2382
- break
2383
- }
2563
+ cleanAuditNote = resolveCleanAuditNote(auditResult, head, rounds)
2384
2564
  resetNoLensRounds()
2385
2565
  phase = 'BUGBOT'
2386
2566
  continue
@@ -2443,7 +2623,7 @@ while (iterations < CONFIG.maxIterations) {
2443
2623
  continue
2444
2624
  }
2445
2625
  const copilot = await runCopilotGate(head)
2446
- const copilotOutcome = classifyCopilotOutcome(copilot)
2626
+ const copilotOutcome = classifyReviewerGateOutcome(copilot)
2447
2627
  copilotDown = resolveCopilotDown(copilotOutcome)
2448
2628
  copilotNote = null
2449
2629
  if (copilotOutcome.kind === 'retry') {
@@ -2457,39 +2637,53 @@ while (iterations < CONFIG.maxIterations) {
2457
2637
  phase = 'FINALIZE'
2458
2638
  continue
2459
2639
  }
2460
- if (copilotOutcome.kind === 'user-review') {
2461
- log(`Copilot raised ${copilotOutcome.findings.length} code-concern finding(s) — holding for user review: the workflow does not auto-fix them and does not mark the PR ready`)
2462
- return {
2463
- converged: false,
2464
- rounds,
2465
- finalSha: head,
2466
- blocker: 'user-review',
2467
- userReview: buildUserReview(copilot, copilotOutcome.findings),
2468
- standardsNote,
2469
- copilotNote,
2470
- reuseNote,
2471
- deferredPrs,
2472
- }
2473
- }
2474
2640
  if (copilotOutcome.kind === 'fix') {
2475
- if (isStandardsOnlyRound(copilotOutcome.findings)) {
2476
- log(`Copilot raised ${copilotOutcome.findings.length} code-standard-only finding(s) — deferring to follow-up PRs and treating the gate as passed`)
2477
- const standardsOutcome = await openStandardsFollowUpOnce(head, copilotOutcome.findings, 'copilot', { copilotDisabled: copilotDown, bugbotDisabled: bugbotDown })
2478
- standardsNote = standardsDeferralNote(copilotOutcome.findings.length, buildStandardsDeferral())
2641
+ const { selfHealing, codeConcern } = partitionFindingsByTier(copilotOutcome.findings)
2642
+ let roundFindings = copilotOutcome.findings
2643
+ if (codeConcern.length > 0) {
2644
+ log(`Copilot raised ${codeConcern.length} code-concern finding(s) — verifying each with an executed check before any routing`)
2645
+ const { confirmed, refuted, inconclusive } = await verifyCodeConcernFindings(head, codeConcern)
2646
+ if (refuted.length > 0) {
2647
+ log(`${refuted.length} finding(s) refuted by an executed check — replying with the evidence and resolving their threads`)
2648
+ await runRefutedThreadResolution(head, refuted)
2649
+ }
2650
+ if (inconclusive.length > 0) {
2651
+ log(`${inconclusive.length} finding(s) stayed inconclusive after verification — holding for user review: the workflow does not auto-fix them and does not mark the PR ready`)
2652
+ return assembleResult({
2653
+ converged: false,
2654
+ rounds,
2655
+ finalSha: head,
2656
+ blocker: 'user-review',
2657
+ userReview: buildUserReview(copilot, inconclusive),
2658
+ })
2659
+ }
2660
+ roundFindings = [...selfHealing, ...confirmed.map((each) => attachVerifiedRepro(each))]
2661
+ if (roundFindings.length === 0) {
2662
+ log('Every code-concern finding was refuted with executed-check evidence and the round carries no self-healing findings — the Copilot gate passes')
2663
+ copilotDown = false
2664
+ copilotNote = null
2665
+ phase = 'FINALIZE'
2666
+ continue
2667
+ }
2668
+ }
2669
+ if (isStandardsOnlyRound(roundFindings)) {
2670
+ log(`Copilot raised ${roundFindings.length} code-standard-only finding(s) — deferring to follow-up PRs and treating the gate as passed`)
2671
+ const standardsOutcome = await openStandardsFollowUpOnce(head, roundFindings, 'copilot', { copilotDisabled: copilotDown, bugbotDisabled: bugbotDown })
2672
+ standardsNote = standardsDeferralNote(roundFindings.length, buildStandardsDeferral())
2479
2673
  if (standardsOutcome?.deferredPr) deferredPrs.push(standardsOutcome.deferredPr)
2480
2674
  copilotDown = false
2481
2675
  copilotNote = null
2482
2676
  phase = 'FINALIZE'
2483
2677
  continue
2484
2678
  }
2485
- log(`Copilot raised ${copilotOutcome.findings.length} finding(s) — fixing and re-converging`)
2486
- const fixResult = await applyFixes(head, copilotOutcome.findings, 'copilot')
2487
- const hadThreadBearingFinding = copilotOutcome.findings.some((each) => collectFindingThreadIds(each).length > 0)
2679
+ log(`Copilot raised ${roundFindings.length} finding(s) — fixing and re-converging`)
2680
+ const fixResult = await applyFixes(head, roundFindings, 'copilot')
2681
+ const hadThreadBearingFinding = roundFindings.some((each) => collectFindingThreadIds(each).length > 0)
2488
2682
  const fixProgress = detectFixProgress(fixResult, head, hadThreadBearingFinding)
2489
2683
  if (!fixProgress.progressed) {
2490
2684
  blocker = fixResult?.resolvedWithoutCommit === true && !hadThreadBearingFinding
2491
- ? `fix stalled: copilot round raised ${copilotOutcome.findings.length} in-memory finding(s) with no GitHub thread, the fix judged them all stale (resolvedWithoutCommit) and moved no code on HEAD ${head} — re-raising would loop to the iteration cap`
2492
- : `copilot fix lens landed no push for ${copilotOutcome.findings.length} finding(s) on HEAD ${head}`
2685
+ ? `fix stalled: copilot round raised ${roundFindings.length} in-memory finding(s) with no GitHub thread, the fix judged them all stale (resolvedWithoutCommit) and moved no code on HEAD ${head} — re-raising would loop to the iteration cap`
2686
+ : `copilot fix lens landed no push for ${roundFindings.length} finding(s) on HEAD ${head}`
2493
2687
  break
2494
2688
  }
2495
2689
  head = null
@@ -2503,7 +2697,7 @@ while (iterations < CONFIG.maxIterations) {
2503
2697
  }
2504
2698
 
2505
2699
  if (phase === 'FINALIZE') {
2506
- const finalizeResult = await runConvergenceCheck({ head, bugbotDown, copilotDown })
2700
+ const finalizeResult = await runConvergenceCheck({ head, bugbotDown, copilotDown, bugteamPostBlocked: cleanAuditNote !== null })
2507
2701
  const convergenceOutcome = classifyConvergenceOutcome(finalizeResult)
2508
2702
  if (convergenceOutcome.kind === 'retry') {
2509
2703
  log('Convergence check agent died or returned no FAIL lines — re-running the check on the same HEAD')
@@ -2512,7 +2706,7 @@ while (iterations < CONFIG.maxIterations) {
2512
2706
  if (convergenceOutcome.kind === 'ready') {
2513
2707
  const readyOutcome = classifyReadyOutcome(finalizeResult)
2514
2708
  if (readyOutcome.converged) {
2515
- return { converged: true, rounds, finalSha: head, blocker: null, standardsNote, copilotNote, reuseNote, deferredPrs }
2709
+ return assembleResult({ converged: true, rounds, finalSha: head, blocker: null })
2516
2710
  }
2517
2711
  blocker = readyOutcome.blocker
2518
2712
  break
@@ -2525,13 +2719,9 @@ while (iterations < CONFIG.maxIterations) {
2525
2719
  }
2526
2720
  }
2527
2721
 
2528
- return {
2722
+ return assembleResult({
2529
2723
  converged: false,
2530
2724
  rounds,
2531
2725
  finalSha: head,
2532
2726
  blocker: blocker || `iteration cap reached (${CONFIG.maxIterations})`,
2533
- standardsNote,
2534
- copilotNote,
2535
- reuseNote,
2536
- deferredPrs,
2537
- }
2727
+ })
@@ -10,7 +10,7 @@ Each loop: a `code-quality-agent` (fresh context, all A–P audit categories) pr
10
10
 
11
11
  | File | Purpose |
12
12
  |---|---|
13
- | `SKILL.md` | Hub — pre-flight call, refusals, the audit-posting step (applies the `post-audit-findings` skill), progress checklist, and situation-to-reference table. Read this first. |
13
+ | `SKILL.md` | Hub — pre-flight call, refusals, the audit-posting step (runs the shared `post_audit_thread.py` helper), progress checklist, and situation-to-reference table. Read this first. |
14
14
  | `PROMPTS.md` | Spawn XML, A–P category bindings, outcome XML schemas. |
15
15
  | `CONSTRAINTS.md` | Invariants — what the loop must never violate. |
16
16
  | `EXAMPLES.md` | Exit scenarios: converged, cap-reached, stuck, refusal, mixed-outcome. |
@@ -4,15 +4,15 @@
4
4
 
5
5
  - **Full A–P audit every loop, no exceptions.** PR size, "focused audit," "team overhead," "CODE_RULES already passed" — not valid reasons. Empty `<findings/>` for any category is a valid result. The audit agent walks all A–P rubrics each loop.
6
6
  - **One run per invocation, multi-PR supported.** All PRs in a single /bugteam invocation share one `run_temp_dir`. Per-PR identity lives in the subagent name prefix (`bugfind-pr<N>-loop<L>` / `bugfix-pr<N>-loop<L>`) and the `<run_temp_dir>/pr-<N>/` subfolder containing that PR's git worktree, diff patches, and outcome XML files.
7
- - **Grant before any spawn, revoke before any return.** Step 0 grants project `.claude/**` permissions; Step 5 revokes. Both are mandatory. Revoke runs on every exit path including error, cap-reached, and stuck.
7
+ - **Grant before any spawn, revoke before any return.** Step 0 grants project `.claude/**` permissions; Step 4 (`pr-loop-lifecycle` Close) revokes. Both are mandatory. Revoke runs on every exit path including error, cap-reached, and stuck.
8
8
  - **Fresh subagent per loop.** Both bugfind and bugfix are spawned new each loop. Reusing a subagent across loops accumulates context inside that subagent's window — defeats clean-room.
9
9
  - **One up-front confirmation = whole cycle.** The `/bugteam` invocation authorizes the entire cycle; every subsequent decision runs on that single authorization.
10
10
  - **20-loop hard cap.** Counted as **AUDIT** completions (increment in Step 3). Standards-fix passes before an audit do not advance `loop_count`. Worst case includes extra clean-coder spawns for the code-rules gate.
11
- - **Code rules gate before every AUDIT.** Run `${CLAUDE_SKILL_DIR}/scripts/bugteam_code_rules_gate.py` until exit **0** before spawning **bugfind**. Same `validate_content` logic as `hooks/blocking/code_rules_enforcer.py`.
11
+ - **Code rules gate before every AUDIT.** Run `python "${CLAUDE_SKILL_DIR}/../../_shared/pr-loop/scripts/code_rules_gate.py" --base origin/<baseRefName>` until exit **0** before spawning **bugfind**. Same `validate_content` logic as `hooks/blocking/code_rules_enforcer.py`.
12
12
  - **Clean-room audits, every loop.** Each bugfind subagent's spawn prompt contains only the PR scope, audit rubric, and the current loop number. Prior loop history stays in the lead.
13
13
  - **Targeted fixes.** Each fix subagent sees ONLY the most recent audit's findings. Prior loops are invisible to the fix subagent.
14
14
  - **Fix subagent receives the latest audit as its input contract.** Each loop's fix run operates on the current audit's output and only that.
15
- - **Lead owns the final PR description rewrite only** (Step 4.5), composed directly against `docs/PR_DESCRIPTION_GUIDE.md`.
15
+ - **Lead owns the final PR description rewrite only** (Step 4 Close substep via `pr-loop-lifecycle`), composed directly against `docs/PR_DESCRIPTION_GUIDE.md`.
16
16
 
17
17
  ## Why this design
18
18