claude-dev-env 1.38.1 → 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/CLAUDE.md +10 -36
  2. package/_shared/pr-loop/audit-reply-template.md +147 -0
  3. package/_shared/pr-loop/fix-protocol.md +25 -4
  4. package/_shared/pr-loop/gh-payloads.md +37 -50
  5. package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
  6. package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +199 -0
  7. package/_shared/pr-loop/scripts/config/reviews_disabled_constants.py +8 -0
  8. package/_shared/pr-loop/scripts/post_audit_thread.py +1242 -0
  9. package/_shared/pr-loop/scripts/preflight.py +129 -2
  10. package/_shared/pr-loop/scripts/reviews_disabled.py +59 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
  12. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1116 -0
  13. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
  14. package/_shared/pr-loop/scripts/tests/test_preflight.py +41 -0
  15. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +36 -0
  16. package/_shared/pr-loop/state-schema.md +1 -1
  17. package/agents/clean-coder.md +2 -2
  18. package/agents/pr-description-writer.md +150 -52
  19. package/bin/install.mjs +6 -7
  20. package/bin/install.test.mjs +8 -0
  21. package/commands/doc-gist.md +16 -0
  22. package/commands/plan.md +0 -2
  23. package/commands/review-plan.md +1 -1
  24. package/docs/CODE_RULES.md +122 -2
  25. package/docs/PR_DESCRIPTION_GUIDE.md +127 -64
  26. package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
  27. package/hooks/blocking/code_rules_enforcer.py +1143 -129
  28. package/hooks/blocking/convergence_gate_blocker.py +130 -0
  29. package/hooks/blocking/destructive_command_blocker.py +74 -0
  30. package/hooks/blocking/gh_body_arg_blocker.py +30 -0
  31. package/hooks/blocking/md_to_html_blocker.py +119 -0
  32. package/hooks/blocking/pr_description_enforcer.py +57 -22
  33. package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
  34. package/hooks/blocking/test_code_rules_enforcer.py +21 -0
  35. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
  36. package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
  37. package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
  38. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
  39. package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
  40. package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
  41. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  42. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
  43. package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
  44. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
  45. package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
  46. package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
  47. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
  48. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
  49. package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
  50. package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
  51. package/hooks/blocking/test_destructive_command_blocker.py +146 -0
  52. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
  53. package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
  54. package/hooks/blocking/test_md_to_html_blocker.py +317 -0
  55. package/hooks/blocking/test_pr_description_enforcer.py +69 -8
  56. package/hooks/config/any_type_config.py +7 -0
  57. package/hooks/config/banned_identifiers_constants.py +11 -0
  58. package/hooks/config/blocking_check_limits.py +38 -0
  59. package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
  60. package/hooks/config/code_rules_enforcer_constants.py +53 -0
  61. package/hooks/config/convergence_branch_constants.py +9 -0
  62. package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
  63. package/hooks/config/html_companion_constants.py +20 -0
  64. package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
  65. package/hooks/config/pr_description_enforcer_constants.py +14 -0
  66. package/hooks/config/test_banned_identifiers_constants.py +17 -0
  67. package/hooks/hooks.json +28 -20
  68. package/hooks/pyproject.toml +69 -0
  69. package/hooks/validators/mypy_integration.py +47 -1
  70. package/hooks/validators/run_all_validators.py +3 -3
  71. package/hooks/validators/test_mypy_integration.py +50 -1
  72. package/hooks/workflow/doc_gist_auto_publish.py +144 -0
  73. package/hooks/workflow/md_to_html_companion.py +365 -0
  74. package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
  75. package/hooks/workflow/test_md_to_html_companion.py +452 -0
  76. package/package.json +1 -1
  77. package/rules/gh-body-file.md +2 -0
  78. package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
  79. package/scripts/check.ps1 +106 -0
  80. package/scripts/config/timing.py +11 -0
  81. package/scripts/sweep_empty_dirs.py +138 -0
  82. package/scripts/sync_to_cursor/rules.py +1 -1
  83. package/scripts/test_sweep_empty_dirs.py +183 -0
  84. package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
  85. package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
  86. package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
  87. package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
  88. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
  89. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
  90. package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
  91. package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
  92. package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
  93. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
  94. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
  95. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
  96. package/skills/bugteam/CONSTRAINTS.md +21 -22
  97. package/skills/bugteam/EXAMPLES.md +3 -3
  98. package/skills/bugteam/PROMPTS.md +227 -67
  99. package/skills/bugteam/SKILL.md +132 -455
  100. package/skills/bugteam/reference/README.md +1 -1
  101. package/skills/bugteam/reference/audit-and-teammates.md +112 -39
  102. package/skills/bugteam/reference/audit-contract.md +4 -22
  103. package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
  104. package/skills/bugteam/reference/design-rationale.md +2 -2
  105. package/skills/bugteam/reference/github-pr-reviews.md +50 -57
  106. package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
  107. package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
  108. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
  109. package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
  110. package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
  111. package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
  112. package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
  113. package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
  114. package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
  115. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
  116. package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
  117. package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
  118. package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
  119. package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
  120. package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
  121. package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
  122. package/skills/bugteam/reference/team-setup.md +111 -9
  123. package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
  124. package/skills/bugteam/scripts/README.md +60 -0
  125. package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
  126. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
  127. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
  128. package/skills/bugteam/scripts/bugteam_preflight.py +328 -0
  129. package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
  130. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
  131. package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
  132. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
  133. package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
  134. package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
  135. package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
  136. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
  137. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
  138. package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
  139. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
  140. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
  141. package/skills/bugteam/scripts/test_bugteam_preflight.py +309 -0
  142. package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
  143. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
  144. package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
  145. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
  146. package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
  147. package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
  148. package/skills/bugteam/test_skill_additions.py +1 -11
  149. package/skills/code/SKILL.md +176 -0
  150. package/skills/copilot-review/SKILL.md +16 -0
  151. package/skills/doc-gist/SKILL.md +99 -0
  152. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
  153. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
  154. package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
  155. package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
  156. package/skills/doc-gist/references/examples/05-design-system.html +629 -0
  157. package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
  158. package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
  159. package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
  160. package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
  161. package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
  162. package/skills/doc-gist/references/examples/11-status-report.html +528 -0
  163. package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
  164. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
  165. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
  166. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
  167. package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
  168. package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
  169. package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
  170. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
  171. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
  172. package/skills/doc-gist/references/examples/README.md +5 -0
  173. package/skills/doc-gist/scripts/config/__init__.py +0 -0
  174. package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
  175. package/skills/doc-gist/scripts/gist_upload.py +177 -0
  176. package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
  177. package/skills/findbugs/SKILL.md +96 -2
  178. package/skills/monitor-open-prs/SKILL.md +14 -32
  179. package/skills/monitor-open-prs/test_skill_contract.py +0 -11
  180. package/skills/pr-consistency-audit/SKILL.md +112 -0
  181. package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
  182. package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
  183. package/skills/pr-converge/SKILL.md +229 -23
  184. package/skills/pr-converge/config/__init__.py +0 -0
  185. package/skills/pr-converge/config/constants.py +63 -0
  186. package/skills/pr-converge/reference/convergence-gates.md +138 -44
  187. package/skills/pr-converge/reference/examples.md +43 -11
  188. package/skills/pr-converge/reference/fix-protocol.md +6 -5
  189. package/skills/pr-converge/reference/ground-rules.md +5 -3
  190. package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
  191. package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
  192. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
  193. package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
  194. package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
  195. package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
  196. package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
  197. package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
  198. package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
  199. package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
  200. package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
  201. package/skills/pr-converge/reference/per-tick.md +107 -31
  202. package/skills/pr-converge/reference/state-schema.md +22 -1
  203. package/skills/pr-converge/reference/stop-conditions.md +9 -7
  204. package/skills/pr-converge/scripts/README.md +34 -46
  205. package/skills/pr-converge/scripts/check_bugbot_ci.py +279 -0
  206. package/skills/pr-converge/scripts/check_convergence.py +497 -0
  207. package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
  208. package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
  209. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
  210. package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
  211. package/skills/pr-converge/scripts/test_check_bugbot_ci.py +312 -0
  212. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
  213. package/skills/qbug/SKILL.md +157 -27
  214. package/skills/session-log/SKILL.md +216 -114
  215. package/skills/session-tidy/SKILL.md +1 -1
  216. package/skills/skill-builder/SKILL.md +138 -56
  217. package/skills/skill-builder/references/delegation-map.md +72 -113
  218. package/skills/skill-builder/references/progressive-disclosure.md +122 -0
  219. package/skills/skill-builder/references/self-audit-checklist.md +92 -0
  220. package/skills/skill-builder/references/skill-types.md +228 -0
  221. package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
  222. package/skills/skill-builder/templates/gap-analysis.md +15 -8
  223. package/skills/skill-builder/workflows/improve-skill.md +86 -57
  224. package/skills/skill-builder/workflows/new-skill.md +80 -168
  225. package/skills/skill-builder/workflows/polish-skill.md +78 -54
  226. package/skills/structure-prompt/SKILL.md +50 -0
  227. package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
  228. package/skills/structure-prompt/reference/block-classification.md +27 -0
  229. package/skills/structure-prompt/reference/canonical-case.md +48 -0
  230. package/skills/structure-prompt/reference/citation-depth.md +70 -0
  231. package/skills/structure-prompt/reference/cleanup.md +33 -0
  232. package/skills/structure-prompt/reference/constraints.md +33 -0
  233. package/skills/structure-prompt/reference/directives.md +37 -0
  234. package/skills/structure-prompt/reference/examples.md +72 -0
  235. package/skills/structure-prompt/reference/instantiation.md +51 -0
  236. package/skills/structure-prompt/reference/output-contract.md +72 -0
  237. package/skills/structure-prompt/reference/per-category.md +23 -0
  238. package/skills/structure-prompt/reference/persona.md +38 -0
  239. package/skills/structure-prompt/reference/research.md +33 -0
  240. package/skills/structure-prompt/reference/structure.md +28 -0
  241. package/agents/code-standards-agent.md +0 -93
  242. package/agents/groq-coder.md +0 -113
  243. package/agents/plan-executor.md +0 -226
  244. package/agents/project-docs-analyzer.md +0 -53
  245. package/agents/project-structure-organizer-agent.md +0 -72
  246. package/agents/skill-to-agent-converter.md +0 -370
  247. package/agents/skill-writer-agent.md +0 -470
  248. package/agents/user-docs-writer.md +0 -67
  249. package/agents/workflow-visual-documenter.md +0 -82
  250. package/commands/readability-review.md +0 -20
  251. package/hooks/mypy.ini +0 -2
  252. package/hooks/notification/attention_needed_notify.py +0 -71
  253. package/hooks/notification/claude_notification_handler.py +0 -67
  254. package/hooks/notification/notification_utils.py +0 -267
  255. package/hooks/notification/subagent_complete_notify.py +0 -381
  256. package/hooks/notification/test_attention_needed_notify.py +0 -47
  257. package/hooks/notification/test_claude_notification_handler.py +0 -54
  258. package/hooks/notification/test_notification_utils.py +0 -91
  259. package/hooks/notification/test_subagent_complete_notify.py +0 -79
  260. package/scripts/config/groq_bugteam_config.py +0 -230
  261. package/scripts/config/test_groq_bugteam_config.py +0 -83
  262. package/scripts/config/test_spec_implementer_prompt.py +0 -32
  263. package/scripts/groq_bugteam.README.md +0 -131
  264. package/scripts/groq_bugteam.py +0 -647
  265. package/scripts/groq_bugteam_dotenv.py +0 -40
  266. package/scripts/groq_bugteam_spec.py +0 -226
  267. package/scripts/test_groq_bugteam.py +0 -529
  268. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
  269. package/scripts/test_groq_bugteam_dotenv.py +0 -66
  270. package/scripts/test_groq_bugteam_spec.py +0 -338
  271. package/skills/bugteam/SKILL_EVALS.md +0 -309
  272. package/skills/dream/SKILL.md +0 -118
  273. package/skills/ingest/SKILL.md +0 -40
  274. package/skills/npm-creator/SKILL.md +0 -187
  275. package/skills/readability-review/SKILL.md +0 -127
  276. package/skills/resume-review/SKILL.md +0 -261
  277. package/skills/rule-audit/SKILL.md +0 -307
  278. package/skills/rule-creator/SKILL.md +0 -150
  279. package/skills/searching-obsidian-vault/SKILL.md +0 -131
  280. package/skills/skill-writer/REFERENCE.md +0 -284
  281. package/skills/skill-writer/SKILL.md +0 -222
  282. package/skills/tdd-team/SKILL.md +0 -128
@@ -0,0 +1,22 @@
1
+ """Shared CLI utilities for pr-loop scripts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+
9
+ def require_file(file_path: Path, label: str) -> int | None:
10
+ """Verify a required file exists, returning an exit code when absent.
11
+
12
+ Args:
13
+ file_path: Path to the required file.
14
+ label: Human-readable label for the error message.
15
+
16
+ Returns:
17
+ None when the file exists (caller continues), or 1 when absent.
18
+ """
19
+ if not file_path.is_file():
20
+ print(f"{label} not found: {file_path}", file=sys.stderr)
21
+ return 1
22
+ return None
@@ -0,0 +1,165 @@
1
+ """Canonical path resolution for pr-loop skills (bugteam, qbug, findbugs, fixbugs).
2
+
3
+ Single source of truth for all path patterns — when a path changes, only this
4
+ file is edited. Pure functions with no side effects.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import tempfile
10
+ from pathlib import Path
11
+
12
+ from config.path_resolver_constants import (
13
+ DIFF_PATCH_TEMPLATE,
14
+ FIX_OUTCOME_XML_TEMPLATE,
15
+ MULTI_PR_SLUG_TEMPLATE,
16
+ OUTCOME_XML_TEMPLATE,
17
+ PER_PR_WORKSPACE_TEMPLATE,
18
+ RUN_NAME_TEMPLATE_MULTI,
19
+ RUN_NAME_TEMPLATE_SINGLE,
20
+ SLUGIFY_REPLACEMENT,
21
+ SLUGIFY_SAFE_CHARS,
22
+ WORKTREE_DIRNAME,
23
+ )
24
+
25
+
26
+ def sanitize_branch_name(head_branch: str) -> str:
27
+ """Replace filesystem-unsafe characters in a git branch name.
28
+
29
+ Args:
30
+ head_branch: Raw git branch name (e.g. 'feat/my-branch').
31
+
32
+ Returns:
33
+ Sanitized string safe for use in directory names.
34
+ """
35
+ return SLUGIFY_SAFE_CHARS.sub(SLUGIFY_REPLACEMENT, head_branch)
36
+
37
+
38
+ def build_run_name(pr_number: int, head_branch: str, *, is_multi_pr: bool) -> str:
39
+ """Build the run_name directory token for a single or multi-PR run.
40
+
41
+ Args:
42
+ pr_number: Pull request number.
43
+ head_branch: Head branch ref (used for multi-PR naming).
44
+ is_multi_pr: True when the run spans multiple PRs.
45
+
46
+ Returns:
47
+ Run name string (e.g. 'bugteam-pr-422' or 'bugteam-feat-my-branch').
48
+ """
49
+ if is_multi_pr:
50
+ return RUN_NAME_TEMPLATE_MULTI.format(
51
+ sanitized_branch=sanitize_branch_name(head_branch),
52
+ )
53
+ return RUN_NAME_TEMPLATE_SINGLE.format(number=pr_number)
54
+
55
+
56
+ def resolve_run_temp_dir(run_name: str) -> Path:
57
+ """Resolve the temporary directory for a given run name.
58
+
59
+ Args:
60
+ run_name: Run name token (from build_run_name).
61
+
62
+ Returns:
63
+ Absolute path to the run's temp directory.
64
+ """
65
+ return Path(tempfile.gettempdir()) / run_name
66
+
67
+
68
+ def slugify_pr_identity(owner: str, repo: str, pr_number: int) -> str:
69
+ """Generate a slugified identity token for a PR.
70
+
71
+ Used as a subdirectory name under the run temp dir in multi-PR scenarios.
72
+
73
+ Args:
74
+ owner: GitHub repository owner.
75
+ repo: GitHub repository name.
76
+ pr_number: Pull request number.
77
+
78
+ Returns:
79
+ Slugified token (e.g. 'jl-cmd-claude-code-config-pr-422').
80
+ """
81
+ return MULTI_PR_SLUG_TEMPLATE.format(owner=owner, repo=repo, number=pr_number)
82
+
83
+
84
+ def per_pr_workspace(
85
+ run_temp_dir: Path, owner: str, repo: str, pr_number: int
86
+ ) -> dict[str, object]:
87
+ """Build the per-PR workspace paths dict.
88
+
89
+ Args:
90
+ run_temp_dir: Run temp directory (from resolve_run_temp_dir).
91
+ owner: GitHub repository owner.
92
+ repo: GitHub repository name.
93
+ pr_number: Pull request number.
94
+
95
+ Returns:
96
+ Dict with keys:
97
+ - worktree: Path to the git worktree checkout
98
+ - diff_patch_template: str template with {loop} placeholder
99
+ - outcome_xml_template: str template with {number} and {loop} placeholders
100
+ - fix_outcome_xml_template: str template with {number} and {loop} placeholders
101
+ """
102
+ pr_workspace_dir = run_temp_dir / PER_PR_WORKSPACE_TEMPLATE.format(number=pr_number)
103
+ slug = slugify_pr_identity(owner, repo, pr_number)
104
+ return {
105
+ "worktree": pr_workspace_dir / WORKTREE_DIRNAME,
106
+ "diff_patch_template": str(pr_workspace_dir / slug / DIFF_PATCH_TEMPLATE),
107
+ "outcome_xml_template": OUTCOME_XML_TEMPLATE,
108
+ "fix_outcome_xml_template": FIX_OUTCOME_XML_TEMPLATE,
109
+ }
110
+
111
+
112
+ def outcome_xml_path(worktree_path: Path, pr_number: int, loop_number: int) -> Path:
113
+ """Construct the canonical path for an AUDIT outcome XML file.
114
+
115
+ Args:
116
+ worktree_path: Path to the git worktree (from per_pr_workspace).
117
+ pr_number: Pull request number.
118
+ loop_number: Current loop iteration.
119
+
120
+ Returns:
121
+ Absolute path (e.g. '<worktree>/.bugteam-pr422-loop3.outcomes.xml').
122
+ """
123
+ return worktree_path / OUTCOME_XML_TEMPLATE.format(
124
+ number=pr_number, loop=loop_number
125
+ )
126
+
127
+
128
+ def fix_outcome_xml_path(worktree_path: Path, pr_number: int, loop_number: int) -> Path:
129
+ """Construct the canonical path for a FIX outcome XML file.
130
+
131
+ Args:
132
+ worktree_path: Path to the git worktree (from per_pr_workspace).
133
+ pr_number: Pull request number.
134
+ loop_number: Current loop iteration.
135
+
136
+ Returns:
137
+ Absolute path (e.g. '<worktree>/.bugteam-pr422-loop3.fix-outcomes.xml').
138
+ """
139
+ return worktree_path / FIX_OUTCOME_XML_TEMPLATE.format(
140
+ number=pr_number, loop=loop_number
141
+ )
142
+
143
+
144
+ def diff_patch_path(
145
+ run_temp_dir: Path,
146
+ owner: str,
147
+ repo: str,
148
+ pr_number: int,
149
+ loop_number: int,
150
+ ) -> Path:
151
+ """Construct the path for a diff/patch file.
152
+
153
+ Args:
154
+ run_temp_dir: Run temp directory (from resolve_run_temp_dir).
155
+ owner: GitHub repository owner.
156
+ repo: GitHub repository name.
157
+ pr_number: Pull request number.
158
+ loop_number: Current loop iteration.
159
+
160
+ Returns:
161
+ Absolute path (e.g. '<run_temp>/jl-cmd-claude-code-config-pr-422/loop-3.patch').
162
+ """
163
+ slug = slugify_pr_identity(owner, repo, pr_number)
164
+ filename = DIFF_PATCH_TEMPLATE.format(loop=loop_number)
165
+ return run_temp_dir / slug / filename
@@ -0,0 +1,20 @@
1
+ """Shared XML utilities for pr-loop scripts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from xml.dom import minidom
6
+ from xml.etree.ElementTree import Element, tostring
7
+
8
+
9
+ def emit_pretty_xml(root: Element) -> str:
10
+ """Serialize an ElementTree to a pretty-printed XML string.
11
+
12
+ Args:
13
+ root: Root XML element.
14
+
15
+ Returns:
16
+ Pretty-printed XML string.
17
+ """
18
+ raw_text = tostring(root, encoding="unicode")
19
+ reparsed_dom = minidom.parseString(raw_text)
20
+ return reparsed_dom.toprettyxml(indent=" ")
@@ -0,0 +1,182 @@
1
+ """Emit the complete AUDIT spawn prompt XML to stdout.
2
+
3
+ Substitutes <context>, <scope>, <bug_categories>, <constraints>,
4
+ <comment_posting>, and <output_format> blocks from CLI args.
5
+
6
+ Usage:
7
+ python scripts/build_audit_prompt.py --owner jl-cmd --repo claude-code-config --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --run-temp-dir <PATH>
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import sys
14
+ from xml.etree.ElementTree import Element, SubElement
15
+ from pathlib import Path
16
+
17
+ _self_dir = Path(__file__).resolve().parent
18
+ if str(_self_dir) not in sys.path:
19
+ sys.path.insert(0, str(_self_dir))
20
+
21
+ from _xml_utils import emit_pretty_xml
22
+ from config.path_resolver_constants import (
23
+ ALL_AUDIT_CATEGORY_ENTRIES,
24
+ ALL_AUDIT_CONSTRAINT_TEXTS,
25
+ )
26
+
27
+
28
+ def build_audit_prompt_xml(
29
+ *,
30
+ owner: str,
31
+ repo: str,
32
+ pr_number: int,
33
+ loop: int,
34
+ head_ref: str,
35
+ base_ref: str,
36
+ worktree_path: Path,
37
+ run_temp_dir: Path,
38
+ ) -> Element:
39
+ """Build the complete AUDIT spawn prompt XML.
40
+
41
+ Args:
42
+ owner: GitHub repository owner.
43
+ repo: GitHub repository name.
44
+ pr_number: Pull request number.
45
+ loop: Loop iteration number.
46
+ head_ref: Head branch ref.
47
+ base_ref: Base branch ref.
48
+ worktree_path: Path to the git worktree.
49
+ run_temp_dir: Path to the run temp directory.
50
+
51
+ Returns:
52
+ Root <spawn_prompt> element.
53
+ """
54
+ root = Element("spawn_prompt", {"role": "audit", "loop": str(loop)})
55
+
56
+ context = SubElement(root, "context")
57
+ SubElement(context, "owner").text = owner
58
+ SubElement(context, "repo").text = repo
59
+ SubElement(context, "pr_number").text = str(pr_number)
60
+ SubElement(context, "head_ref").text = head_ref
61
+ SubElement(context, "base_ref").text = base_ref
62
+ SubElement(context, "worktree_path").text = str(worktree_path)
63
+ SubElement(context, "run_temp_dir").text = str(run_temp_dir)
64
+
65
+ scope = SubElement(root, "scope")
66
+ scope.text = (
67
+ f"Audit the full diff of {owner}/{repo}#{pr_number} "
68
+ f"({head_ref} against {base_ref}) for CODE_RULES violations, "
69
+ f"bugs, and anti-patterns. Work in {worktree_path}."
70
+ )
71
+
72
+ bug_categories = SubElement(root, "bug_categories")
73
+ for each_category_id, each_category_label in ALL_AUDIT_CATEGORY_ENTRIES:
74
+ cat_elem = SubElement(bug_categories, "category", {"id": each_category_id})
75
+ cat_elem.text = each_category_label
76
+
77
+ constraints = SubElement(root, "constraints")
78
+ for each_constraint in ALL_AUDIT_CONSTRAINT_TEXTS:
79
+ SubElement(constraints, "constraint").text = each_constraint
80
+
81
+ comment_posting = SubElement(root, "comment_posting")
82
+ comment_posting.text = (
83
+ "Post findings as inline review comments on the PR via "
84
+ "the GitHub MCP add_comment_to_pending_review tool. "
85
+ "Group related findings into a single pending review."
86
+ )
87
+
88
+ output_format = SubElement(root, "output_format")
89
+ output_format.text = (
90
+ "Emit findings as JSON array of objects with keys: "
91
+ "severity (P0/P1/P2), file, line, category, message, suggestion."
92
+ )
93
+
94
+ return root
95
+
96
+
97
+ def emit_audit_prompt(
98
+ *,
99
+ owner: str,
100
+ repo: str,
101
+ pr_number: int,
102
+ loop: int,
103
+ head_ref: str,
104
+ base_ref: str,
105
+ worktree_path: Path,
106
+ run_temp_dir: Path,
107
+ ) -> str:
108
+ """Build and serialize the AUDIT spawn prompt to a pretty-printed XML string.
109
+
110
+ Args:
111
+ owner: GitHub repository owner.
112
+ repo: GitHub repository name.
113
+ pr_number: Pull request number.
114
+ loop: Loop iteration number.
115
+ head_ref: Head branch ref.
116
+ base_ref: Base branch ref.
117
+ worktree_path: Path to the git worktree.
118
+ run_temp_dir: Path to the run temp directory.
119
+
120
+ Returns:
121
+ Pretty-printed XML string.
122
+ """
123
+ root = build_audit_prompt_xml(
124
+ owner=owner,
125
+ repo=repo,
126
+ pr_number=pr_number,
127
+ loop=loop,
128
+ head_ref=head_ref,
129
+ base_ref=base_ref,
130
+ worktree_path=worktree_path,
131
+ run_temp_dir=run_temp_dir,
132
+ )
133
+ return emit_pretty_xml(root)
134
+
135
+
136
+ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
137
+ """Parse command-line arguments.
138
+
139
+ Args:
140
+ all_argv: Command-line argument list.
141
+
142
+ Returns:
143
+ Parsed namespace with all required audit prompt parameters.
144
+ """
145
+ parser = argparse.ArgumentParser(description=__doc__)
146
+ parser.add_argument("--owner", required=True)
147
+ parser.add_argument("--repo", required=True)
148
+ parser.add_argument("--pr-number", type=int, required=True)
149
+ parser.add_argument("--loop", type=int, required=True)
150
+ parser.add_argument("--head-ref", required=True)
151
+ parser.add_argument("--base-ref", required=True)
152
+ parser.add_argument("--worktree-path", type=Path, required=True)
153
+ parser.add_argument("--run-temp-dir", type=Path, required=True)
154
+ return parser.parse_args(all_argv)
155
+
156
+
157
+ def main(all_arguments: list[str]) -> int:
158
+ """Entry point: emit AUDIT spawn prompt XML to stdout.
159
+
160
+ Args:
161
+ all_arguments: Command-line arguments.
162
+
163
+ Returns:
164
+ 0 on success.
165
+ """
166
+ arguments = parse_arguments(all_arguments)
167
+ xml_output = emit_audit_prompt(
168
+ owner=arguments.owner,
169
+ repo=arguments.repo,
170
+ pr_number=getattr(arguments, "pr_number"),
171
+ loop=arguments.loop,
172
+ head_ref=getattr(arguments, "head_ref"),
173
+ base_ref=getattr(arguments, "base_ref"),
174
+ worktree_path=getattr(arguments, "worktree_path"),
175
+ run_temp_dir=getattr(arguments, "run_temp_dir"),
176
+ )
177
+ sys.stdout.write(xml_output)
178
+ return 0
179
+
180
+
181
+ if __name__ == "__main__":
182
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,185 @@
1
+ """Emit the complete FIX spawn prompt XML to stdout.
2
+
3
+ Populates <bug> entries from findings JSON, plus <execution> checklist
4
+ and <constraints>.
5
+
6
+ Usage:
7
+ python scripts/build_fix_prompt.py --owner jl-cmd --repo claude-code-config --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --findings-json <PATH>
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import sys
15
+ from xml.etree.ElementTree import Element, SubElement
16
+ from pathlib import Path
17
+
18
+ _self_dir = Path(__file__).resolve().parent
19
+ if str(_self_dir) not in sys.path:
20
+ sys.path.insert(0, str(_self_dir))
21
+
22
+ from _cli_utils import require_file
23
+ from _xml_utils import emit_pretty_xml
24
+ from config.path_resolver_constants import (
25
+ ALL_FIX_CONSTRAINT_TEXTS,
26
+ ALL_FIX_EXECUTION_STEPS,
27
+ )
28
+
29
+
30
+ def build_fix_prompt_xml(
31
+ *,
32
+ owner: str,
33
+ repo: str,
34
+ pr_number: int,
35
+ loop: int,
36
+ head_ref: str,
37
+ base_ref: str,
38
+ worktree_path: Path,
39
+ findings_json_path: Path,
40
+ ) -> Element:
41
+ """Build the complete FIX spawn prompt XML.
42
+
43
+ Args:
44
+ owner: GitHub repository owner.
45
+ repo: GitHub repository name.
46
+ pr_number: Pull request number.
47
+ loop: Loop iteration number.
48
+ head_ref: Head branch ref.
49
+ base_ref: Base branch ref.
50
+ worktree_path: Path to the git worktree.
51
+ findings_json_path: Path to the findings JSON file.
52
+
53
+ Returns:
54
+ Root <spawn_prompt> element.
55
+ """
56
+ findings_data = json.loads(findings_json_path.read_text(encoding="utf-8"))
57
+
58
+ root = Element("spawn_prompt", {"role": "fix", "loop": str(loop)})
59
+
60
+ context = SubElement(root, "context")
61
+ SubElement(context, "owner").text = owner
62
+ SubElement(context, "repo").text = repo
63
+ SubElement(context, "pr_number").text = str(pr_number)
64
+ SubElement(context, "head_ref").text = head_ref
65
+ SubElement(context, "base_ref").text = base_ref
66
+ SubElement(context, "worktree_path").text = str(worktree_path)
67
+
68
+ bugs_elem = SubElement(root, "bugs")
69
+ if isinstance(findings_data, list):
70
+ for each_finding in findings_data:
71
+ if isinstance(each_finding, dict):
72
+ bug = SubElement(bugs_elem, "bug")
73
+ for each_key, each_field_detail in each_finding.items():
74
+ child = SubElement(bug, each_key)
75
+ child.text = (
76
+ str(each_field_detail) if each_field_detail is not None else ""
77
+ )
78
+
79
+ execution = SubElement(root, "execution")
80
+ for each_step in ALL_FIX_EXECUTION_STEPS:
81
+ SubElement(execution, "step").text = each_step
82
+
83
+ constraints = SubElement(root, "constraints")
84
+ for each_constraint in ALL_FIX_CONSTRAINT_TEXTS:
85
+ SubElement(constraints, "constraint").text = each_constraint
86
+
87
+ return root
88
+
89
+
90
+ def emit_fix_prompt(
91
+ *,
92
+ owner: str,
93
+ repo: str,
94
+ pr_number: int,
95
+ loop: int,
96
+ head_ref: str,
97
+ base_ref: str,
98
+ worktree_path: Path,
99
+ findings_json_path: Path,
100
+ ) -> str:
101
+ """Build and serialize the FIX spawn prompt to a pretty-printed XML string.
102
+
103
+ Args:
104
+ owner: GitHub repository owner.
105
+ repo: GitHub repository name.
106
+ pr_number: Pull request number.
107
+ loop: Loop iteration number.
108
+ head_ref: Head branch ref.
109
+ base_ref: Base branch ref.
110
+ worktree_path: Path to the git worktree.
111
+ findings_json_path: Path to the findings JSON file.
112
+
113
+ Returns:
114
+ Pretty-printed XML string.
115
+ """
116
+ root = build_fix_prompt_xml(
117
+ owner=owner,
118
+ repo=repo,
119
+ pr_number=pr_number,
120
+ loop=loop,
121
+ head_ref=head_ref,
122
+ base_ref=base_ref,
123
+ worktree_path=worktree_path,
124
+ findings_json_path=findings_json_path,
125
+ )
126
+ return emit_pretty_xml(root)
127
+
128
+
129
+ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
130
+ """Parse command-line arguments.
131
+
132
+ Args:
133
+ all_argv: Command-line argument list.
134
+
135
+ Returns:
136
+ Parsed namespace with all required fix prompt parameters.
137
+ """
138
+ parser = argparse.ArgumentParser(description=__doc__)
139
+ parser.add_argument("--owner", required=True)
140
+ parser.add_argument("--repo", required=True)
141
+ parser.add_argument("--pr-number", type=int, required=True)
142
+ parser.add_argument("--loop", type=int, required=True)
143
+ parser.add_argument("--head-ref", required=True)
144
+ parser.add_argument("--base-ref", required=True)
145
+ parser.add_argument("--worktree-path", type=Path, required=True)
146
+ parser.add_argument("--findings-json", type=Path, required=True)
147
+ return parser.parse_args(all_argv)
148
+
149
+
150
+ def main(all_arguments: list[str]) -> int:
151
+ """Entry point: emit FIX spawn prompt XML to stdout.
152
+
153
+ Args:
154
+ all_arguments: Command-line arguments.
155
+
156
+ Returns:
157
+ 0 on success, 1 on failure.
158
+ """
159
+ arguments = parse_arguments(all_arguments)
160
+ findings_path = getattr(arguments, "findings_json")
161
+
162
+ early_exit = require_file(findings_path, "findings-json")
163
+ if early_exit is not None:
164
+ return early_exit
165
+
166
+ try:
167
+ xml_output = emit_fix_prompt(
168
+ owner=arguments.owner,
169
+ repo=arguments.repo,
170
+ pr_number=getattr(arguments, "pr_number"),
171
+ loop=arguments.loop,
172
+ head_ref=getattr(arguments, "head_ref"),
173
+ base_ref=getattr(arguments, "base_ref"),
174
+ worktree_path=getattr(arguments, "worktree_path"),
175
+ findings_json_path=findings_path,
176
+ )
177
+ except (json.JSONDecodeError, OSError) as exc:
178
+ print(f"emit_fix_prompt failed: {exc}", file=sys.stderr)
179
+ return 1
180
+ sys.stdout.write(xml_output)
181
+ return 0
182
+
183
+
184
+ if __name__ == "__main__":
185
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,78 @@
1
+ """Path template constants for the bugteam / pr-loop shared scripts."""
2
+
3
+ import re
4
+
5
+ RUN_NAME_TEMPLATE_SINGLE = "bugteam-pr-{number}"
6
+ RUN_NAME_TEMPLATE_MULTI = "bugteam-{sanitized_branch}"
7
+ PER_PR_WORKSPACE_TEMPLATE = "pr-{number}"
8
+ WORKTREE_DIRNAME = "worktree"
9
+ DIFF_PATCH_TEMPLATE = "loop-{loop}.patch"
10
+ OUTCOME_XML_TEMPLATE = ".bugteam-pr{number}-loop{loop}.outcomes.xml"
11
+ FIX_OUTCOME_XML_TEMPLATE = ".bugteam-pr{number}-loop{loop}.fix-outcomes.xml"
12
+ LOOP_STATE_FILENAME = "loop-state.json"
13
+ SLUGIFY_SAFE_CHARS = re.compile(r"[^A-Za-z0-9._-]")
14
+ SLUGIFY_REPLACEMENT = "-"
15
+ MULTI_PR_SLUG_TEMPLATE = "{owner}-{repo}-pr-{number}"
16
+ LOOP_STATE_JSON_INDENT = 2
17
+ ALL_VALID_FIX_STATUSES = frozenset({
18
+ "fixed",
19
+ "could_not_address",
20
+ "hook_blocked",
21
+ "unverified_fixed",
22
+ })
23
+
24
+ ALL_AUDIT_CONSTRAINT_TEXTS = [
25
+ "Work exclusively within the worktree directory.",
26
+ "Every finding must cite file:line.",
27
+ "Document each finding with severity, file, line, and suggested fix.",
28
+ "Read each file in the diff before reporting on it.",
29
+ ]
30
+
31
+ ALL_AUDIT_CATEGORY_ENTRIES = [
32
+ ("A", "Documentation / API call accuracy"),
33
+ ("B", "Type safety / boundary types"),
34
+ ("C", "Magic values / hardcoded constants"),
35
+ ("D", "Naming / banned identifiers"),
36
+ ("E", "Orphans / dead code"),
37
+ ("F", "Error handling / bare except"),
38
+ ("G", "Bounds / silent cap exits"),
39
+ ("H", "Testing / test quality"),
40
+ ("I", "Control flow / logic errors"),
41
+ ("J", "Architecture / SOLID violations"),
42
+ ("K", "Codebase conflicts / DRY"),
43
+ ]
44
+
45
+ ALL_FIX_EXECUTION_STEPS = [
46
+ "Read the finding and verify it against the current file at file:line.",
47
+ "Write a failing test that reproduces the bug.",
48
+ "Implement the smallest change that resolves the finding.",
49
+ "Run the full test suite — confirm the new test and all existing tests pass.",
50
+ "Stage and commit the fix with a descriptive message.",
51
+ "Push the commit to the head branch.",
52
+ "Post an inline reply on the finding thread confirming the fix.",
53
+ ]
54
+
55
+ ALL_FIX_CONSTRAINT_TEXTS = [
56
+ "Work exclusively within the worktree directory.",
57
+ "Change only the lines directly related to each finding.",
58
+ "Create one commit per fix loop, each focused on a single category of findings.",
59
+ "Every fix must have a corresponding test.",
60
+ "Remove deprecated code directly and update all call sites.",
61
+ "Handle each error case with a named exception type.",
62
+ ]
63
+
64
+ XML_PRETTY_INDENT = " "
65
+ XML_SERIALIZE_ENCODING = "unicode"
66
+ XML_OUTPUT_ENCODING = "utf-8"
67
+ ALL_PYTHON_ONEXC_VERSION = (3, 12)
68
+
69
+ ALL_FINDING_BODY_ELEMENT_KEYS: tuple[str, ...] = (
70
+ "title",
71
+ "excerpt",
72
+ "description",
73
+ )
74
+
75
+ ALL_FIX_OUTCOME_BODY_ELEMENT_KEYS: tuple[str, ...] = (
76
+ "reason",
77
+ "hook_output",
78
+ )