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,135 @@
1
+ """Create a loop-state.json file for a bugteam run.
2
+
3
+ Usage:
4
+ python scripts/init_loop_state.py --pr-number 422 --head-ref feat/branch --starting-sha abc1234 [--is-multi-pr]
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ _self_dir = Path(__file__).resolve().parent
15
+ if str(_self_dir) not in sys.path:
16
+ sys.path.insert(0, str(_self_dir))
17
+
18
+ from _path_resolver import (
19
+ build_run_name,
20
+ per_pr_workspace,
21
+ resolve_run_temp_dir,
22
+ )
23
+ from config.path_resolver_constants import LOOP_STATE_JSON_INDENT
24
+
25
+
26
+ def create_loop_state(
27
+ *,
28
+ pr_number: int,
29
+ head_ref: str,
30
+ starting_sha: str,
31
+ is_multi_pr: bool = False,
32
+ ) -> Path:
33
+ """Create the loop-state.json file and return its path.
34
+
35
+ The written state dict carries the subset of the keys documented
36
+ in `_shared/pr-loop/state-schema.md` common-fields table that are
37
+ initialized at loop creation. Fields populated only during the loop
38
+ (e.g. `audit_log`) are added by later steps and are not written
39
+ here:
40
+
41
+ - `loop_count: 0` (int counter, bumps on each AUDIT or tick)
42
+ - `last_action: "fresh"` (enum: fresh | audited | fixed)
43
+ - `last_findings: {p0: 0, p1: 0, p2: 0, total: 0}` (count dict
44
+ populated by AUDIT)
45
+ - `starting_sha: <str>` (the SHA passed in)
46
+ - `loop_comment_index: {}` (dict keyed by finding_id; AUDIT
47
+ populates `finding_comment_id`, `finding_comment_url`, and
48
+ `thread_node_id` per entry when it posts the per-loop review,
49
+ and FIX sets `fix_status` when its commit lands)
50
+
51
+ Args:
52
+ pr_number: Pull request number.
53
+ head_ref: Head branch ref.
54
+ starting_sha: Starting commit SHA.
55
+ is_multi_pr: Whether multi-PR mode is active.
56
+
57
+ Returns:
58
+ Path to the created loop-state.json file.
59
+ """
60
+ run_name = build_run_name(pr_number, head_ref, is_multi_pr=is_multi_pr)
61
+ run_temp_dir = resolve_run_temp_dir(run_name)
62
+ workspace = per_pr_workspace(run_temp_dir, "", "", pr_number)
63
+ worktree_path = workspace["worktree"]
64
+ assert isinstance(worktree_path, Path)
65
+
66
+ worktree_path.mkdir(parents=True, exist_ok=True)
67
+ state_path = worktree_path / "loop-state.json"
68
+
69
+ state = {
70
+ "loop_count": 0,
71
+ "last_action": "fresh",
72
+ "last_findings": {"p0": 0, "p1": 0, "p2": 0, "total": 0},
73
+ "starting_sha": starting_sha,
74
+ "loop_comment_index": {},
75
+ }
76
+
77
+ state_path.write_text(
78
+ json.dumps(state, indent=LOOP_STATE_JSON_INDENT) + "\n",
79
+ encoding="utf-8",
80
+ )
81
+ return state_path
82
+
83
+
84
+ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
85
+ """Parse command-line arguments.
86
+
87
+ Args:
88
+ all_argv: Command-line argument list.
89
+
90
+ Returns:
91
+ Parsed namespace with pr_number, head_ref, starting_sha, and is_multi_pr.
92
+ """
93
+ parser = argparse.ArgumentParser(description=__doc__)
94
+ parser.add_argument("--pr-number", type=int, required=True)
95
+ parser.add_argument("--head-ref", required=True)
96
+ parser.add_argument("--starting-sha", required=True)
97
+ parser.add_argument(
98
+ "--is-multi-pr",
99
+ action="store_true",
100
+ default=False,
101
+ )
102
+ return parser.parse_args(all_argv)
103
+
104
+
105
+ def main(
106
+ all_arguments: list[str], *, is_multi_pr: bool | None = None
107
+ ) -> int:
108
+ """Entry point: create loop-state.json and print its path.
109
+
110
+ Args:
111
+ all_arguments: Command-line arguments.
112
+ is_multi_pr: Override for multi-PR mode (default: from CLI).
113
+
114
+ Returns:
115
+ 0 on success.
116
+ """
117
+ arguments = parse_arguments(all_arguments)
118
+ if arguments.starting_sha is None:
119
+ return 1
120
+ state_path = create_loop_state(
121
+ pr_number=getattr(arguments, "pr_number"),
122
+ head_ref=getattr(arguments, "head_ref"),
123
+ starting_sha=arguments.starting_sha,
124
+ is_multi_pr=(
125
+ arguments.is_multi_pr if is_multi_pr is None else is_multi_pr
126
+ ),
127
+ )
128
+ print(state_path)
129
+ return 0
130
+
131
+
132
+ if __name__ == "__main__":
133
+ all_argv = sys.argv[1:]
134
+ arguments = parse_arguments(all_argv)
135
+ raise SystemExit(main(all_argv, is_multi_pr=arguments.is_multi_pr))
@@ -0,0 +1,175 @@
1
+ """Remove git worktrees and run temp directories for a bugteam run.
2
+
3
+ Usage:
4
+ python scripts/teardown_worktrees.py --run-temp-dir <PATH> --all-pr-jsons <JSON>
5
+
6
+ The JSON array must contain objects with keys: number, owner, repo.
7
+ Tolerates already-removed worktrees and missing directories.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import os
15
+ import shutil
16
+ import stat
17
+ import subprocess
18
+ import sys
19
+ from collections.abc import Callable
20
+ from pathlib import Path
21
+
22
+ _self_dir = Path(__file__).resolve().parent
23
+ if str(_self_dir) not in sys.path:
24
+ sys.path.insert(0, str(_self_dir))
25
+
26
+ from _path_resolver import per_pr_workspace
27
+ from config.path_resolver_constants import ALL_PYTHON_ONEXC_VERSION
28
+
29
+
30
+ def _remove_readonly_attribute(
31
+ removal_function: Callable[[str], None],
32
+ target_path: str,
33
+ *_exc_info: object,
34
+ ) -> None:
35
+ """Windows-safe handler: strip ReadOnly attribute and retry the syscall.
36
+
37
+ Args:
38
+ removal_function: The syscall that failed (os.unlink or os.rmdir).
39
+ target_path: Path to the file or directory that triggered the error.
40
+ *_exc_info: Exception information (collapses onerror/onexc signature difference).
41
+ """
42
+ try:
43
+ os.chmod(target_path, stat.S_IWRITE)
44
+ removal_function(target_path)
45
+ except OSError:
46
+ pass
47
+
48
+
49
+ def force_rmtree(target_path: str) -> None:
50
+ """Remove a directory tree, handling Windows ReadOnly attribute.
51
+
52
+ Args:
53
+ target_path: Path to the directory tree to remove.
54
+ """
55
+ handler_kw: dict[str, object] = (
56
+ {"onexc": _remove_readonly_attribute}
57
+ if sys.version_info >= ALL_PYTHON_ONEXC_VERSION
58
+ else {"onerror": _remove_readonly_attribute}
59
+ )
60
+ try:
61
+ shutil.rmtree(target_path, **handler_kw)
62
+ except OSError:
63
+ pass
64
+
65
+
66
+ def remove_worktree(worktree_path: Path) -> bool:
67
+ """Remove a single git worktree via `git worktree remove`.
68
+
69
+ Args:
70
+ worktree_path: Path to the worktree directory.
71
+
72
+ Returns:
73
+ True when the worktree was registered and removed, False when
74
+ it was already absent or unregistered.
75
+ """
76
+ if not worktree_path.exists():
77
+ return False
78
+ completed_process = subprocess.run(
79
+ ["git", "worktree", "remove", str(worktree_path)],
80
+ capture_output=True,
81
+ text=True,
82
+ encoding="utf-8",
83
+ errors="replace",
84
+ check=False,
85
+ )
86
+ if completed_process.returncode != 0:
87
+ stderr_text = completed_process.stderr or ""
88
+ if "not a working tree" in stderr_text.lower():
89
+ force_rmtree(str(worktree_path))
90
+ return False
91
+ print(
92
+ f"git worktree remove failed for {worktree_path}: {stderr_text}",
93
+ file=sys.stderr,
94
+ )
95
+ return False
96
+ return True
97
+
98
+
99
+ def teardown_run(
100
+ *,
101
+ run_temp_dir: Path,
102
+ all_pr_entries: list[dict[str, object]],
103
+ ) -> int:
104
+ """Remove all worktrees and the run temp directory.
105
+
106
+ Args:
107
+ run_temp_dir: Path to the run's temp directory.
108
+ all_pr_entries: List of dicts with number, owner, and repo keys.
109
+
110
+ Returns:
111
+ Count of worktrees successfully removed via git.
112
+ """
113
+ removed_count = 0
114
+ for each_entry in all_pr_entries:
115
+ pr_number = each_entry.get("number")
116
+ owner = each_entry.get("owner", "")
117
+ repo = each_entry.get("repo", "")
118
+ if not isinstance(pr_number, int):
119
+ continue
120
+ workspace = per_pr_workspace(run_temp_dir, str(owner), str(repo), pr_number)
121
+ worktree_path = workspace["worktree"]
122
+ if not isinstance(worktree_path, Path):
123
+ continue
124
+ if remove_worktree(worktree_path):
125
+ removed_count += 1
126
+
127
+ force_rmtree(str(run_temp_dir))
128
+ return removed_count
129
+
130
+
131
+ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
132
+ """Parse command-line arguments.
133
+
134
+ Args:
135
+ all_argv: Command-line argument list.
136
+
137
+ Returns:
138
+ Parsed namespace with run_temp_dir and all_pr_jsons.
139
+ """
140
+ parser = argparse.ArgumentParser(description=__doc__)
141
+ parser.add_argument("--run-temp-dir", type=Path, required=True)
142
+ parser.add_argument("--all-pr-jsons", required=True)
143
+ return parser.parse_args(all_argv)
144
+
145
+
146
+ def main(all_arguments: list[str]) -> int:
147
+ """Entry point: remove worktrees and temp directory.
148
+
149
+ Args:
150
+ all_arguments: Command-line arguments.
151
+
152
+ Returns:
153
+ 0 on success, 1 on JSON parse failure.
154
+ """
155
+ arguments = parse_arguments(all_arguments)
156
+ run_temp_dir = getattr(arguments, "run_temp_dir")
157
+ try:
158
+ all_pr_entries = json.loads(getattr(arguments, "all_pr_jsons"))
159
+ except json.JSONDecodeError as exc:
160
+ print(f"Invalid JSON for --all-pr-jsons: {exc}", file=sys.stderr)
161
+ return 1
162
+ if not isinstance(all_pr_entries, list):
163
+ print("--all-pr-jsons must be a JSON array", file=sys.stderr)
164
+ return 1
165
+
166
+ removed_count = teardown_run(
167
+ run_temp_dir=run_temp_dir,
168
+ all_pr_entries=all_pr_entries,
169
+ )
170
+ print(f"Removed {removed_count} worktree(s), cleaned {run_temp_dir}")
171
+ return 0
172
+
173
+
174
+ if __name__ == "__main__":
175
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,182 @@
1
+ """Validate Shape A/B contract and write <bugteam_audit> XML at the canonical path.
2
+
3
+ Usage:
4
+ python scripts/write_audit_outcomes.py --pr-number 422 --loop 3 --review-url <URL> --findings-json <PATH> --worktree-path <PATH>
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import json
11
+ import sys
12
+ from pathlib import Path
13
+ from xml.etree.ElementTree import Element, SubElement
14
+
15
+ _self_dir = Path(__file__).resolve().parent
16
+ if str(_self_dir) not in sys.path:
17
+ sys.path.insert(0, str(_self_dir))
18
+
19
+ from _cli_utils import require_file
20
+ from _path_resolver import outcome_xml_path
21
+ from _xml_utils import emit_pretty_xml
22
+ from config.path_resolver_constants import ALL_FINDING_BODY_ELEMENT_KEYS
23
+
24
+
25
+ def build_audit_xml(
26
+ *,
27
+ pr_number: int,
28
+ loop: int,
29
+ review_url: str,
30
+ findings_json_path: Path,
31
+ ) -> Element:
32
+ """Build the <bugteam_audit> XML element from findings data.
33
+
34
+ Args:
35
+ pr_number: Pull request number.
36
+ loop: Loop iteration number.
37
+ review_url: URL of the GitHub review.
38
+ findings_json_path: Path to the findings JSON file.
39
+
40
+ Returns:
41
+ Root <bugteam_audit> element.
42
+
43
+ Raises:
44
+ SystemExit: When findings-json is not a JSON array of objects.
45
+ """
46
+ findings_data = json.loads(findings_json_path.read_text(encoding="utf-8"))
47
+ if not isinstance(findings_data, list):
48
+ print("findings-json must contain a JSON array", file=sys.stderr)
49
+ raise SystemExit(1)
50
+ for each_index, each_finding in enumerate(findings_data):
51
+ if not isinstance(each_finding, dict):
52
+ print(
53
+ f"findings-json[{each_index}]: each entry must be a JSON object",
54
+ file=sys.stderr,
55
+ )
56
+ raise SystemExit(1)
57
+ root = Element("bugteam_audit", {
58
+ "pr": str(pr_number),
59
+ "loop": str(loop),
60
+ "review_url": review_url,
61
+ })
62
+
63
+ findings_elem = SubElement(root, "findings")
64
+ _populate_findings(findings_elem, findings_data)
65
+ return root
66
+
67
+
68
+ def _populate_findings(parent: Element, findings_data: list[dict[str, object]]) -> None:
69
+ """Populate <finding> elements from a validated list of finding dicts.
70
+
71
+ Scalar finding fields become XML attributes on `<finding>`; the
72
+ body fields named in `ALL_FINDING_BODY_ELEMENT_KEYS` (defined in
73
+ `config/path_resolver_constants.py` and currently
74
+ `("title", "excerpt", "description")`) become child elements.
75
+ Nested dicts or lists in scalar slots are flattened to string form
76
+ so attribute serialization stays well-defined.
77
+
78
+ Args:
79
+ parent: Parent XML element (typically `<findings>`).
80
+ findings_data: Validated list of finding dicts (caller must have
81
+ confirmed each entry is a dict via the build_audit_xml gate).
82
+ """
83
+ all_finding_body_element_keys = ALL_FINDING_BODY_ELEMENT_KEYS
84
+ for each_finding in findings_data:
85
+ finding_elem = SubElement(parent, "finding")
86
+ for each_key, each_field_detail in each_finding.items():
87
+ field_text = (
88
+ str(each_field_detail) if each_field_detail is not None else ""
89
+ )
90
+ if each_key in all_finding_body_element_keys:
91
+ child = SubElement(finding_elem, each_key)
92
+ child.text = field_text
93
+ else:
94
+ finding_elem.set(each_key, field_text)
95
+
96
+
97
+ def write_audit_xml(
98
+ *,
99
+ pr_number: int,
100
+ loop: int,
101
+ review_url: str,
102
+ findings_json_path: Path,
103
+ worktree_path: Path,
104
+ ) -> Path:
105
+ """Write the <bugteam_audit> XML to the canonical outcome path.
106
+
107
+ Args:
108
+ pr_number: Pull request number.
109
+ loop: Loop iteration number.
110
+ review_url: URL of the GitHub review.
111
+ findings_json_path: Path to the findings JSON file.
112
+ worktree_path: Path to the git worktree.
113
+
114
+ Returns:
115
+ Path to the written XML file.
116
+ """
117
+ root = build_audit_xml(
118
+ pr_number=pr_number,
119
+ loop=loop,
120
+ review_url=review_url,
121
+ findings_json_path=findings_json_path,
122
+ )
123
+ xml_string = emit_pretty_xml(root)
124
+
125
+ output_path = outcome_xml_path(worktree_path, pr_number, loop)
126
+ output_path.write_text(xml_string, encoding="utf-8")
127
+ return output_path
128
+
129
+
130
+ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
131
+ """Parse command-line arguments.
132
+
133
+ Args:
134
+ all_argv: Command-line argument list.
135
+
136
+ Returns:
137
+ Parsed namespace with pr_number, loop, review_url, findings_json, and worktree_path.
138
+ """
139
+ parser = argparse.ArgumentParser(description=__doc__)
140
+ parser.add_argument("--pr-number", type=int, required=True)
141
+ parser.add_argument("--loop", type=int, required=True)
142
+ parser.add_argument("--review-url", required=True)
143
+ parser.add_argument("--findings-json", type=Path, required=True)
144
+ parser.add_argument("--worktree-path", type=Path, required=True)
145
+ return parser.parse_args(all_argv)
146
+
147
+
148
+ def main(all_arguments: list[str]) -> int:
149
+ """Entry point: write <bugteam_audit> XML at the canonical path.
150
+
151
+ Args:
152
+ all_arguments: Command-line arguments.
153
+
154
+ Returns:
155
+ 0 on success, 1 on validation or write failure.
156
+ """
157
+ arguments = parse_arguments(all_arguments)
158
+ findings_path = getattr(arguments, "findings_json")
159
+
160
+ early_exit = require_file(findings_path, "findings-json")
161
+ if early_exit is not None:
162
+ return early_exit
163
+
164
+ try:
165
+ output_path = write_audit_xml(
166
+ pr_number=getattr(arguments, "pr_number"),
167
+ loop=arguments.loop,
168
+ review_url=getattr(arguments, "review_url"),
169
+ findings_json_path=findings_path,
170
+ worktree_path=getattr(arguments, "worktree_path"),
171
+ )
172
+ except SystemExit:
173
+ return 1
174
+ except (json.JSONDecodeError, OSError) as exc:
175
+ print(f"write_audit_xml failed: {exc}", file=sys.stderr)
176
+ return 1
177
+ print(output_path)
178
+ return 0
179
+
180
+
181
+ if __name__ == "__main__":
182
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,206 @@
1
+ """Validate status enum values and write <bugteam_fix> XML at the canonical path.
2
+
3
+ Status enum (canonical source: `ALL_VALID_FIX_STATUSES` in
4
+ `config/path_resolver_constants.py`): fixed | could_not_address |
5
+ hook_blocked | unverified_fixed.
6
+
7
+ Each outcome's scalar fields become XML attributes on `<outcome>`; the
8
+ body fields named in `ALL_FIX_OUTCOME_BODY_ELEMENT_KEYS` (currently
9
+ `("reason", "hook_output")`) become child elements.
10
+
11
+ Usage:
12
+ python scripts/write_fix_outcomes.py --pr-number 422 --loop 3 --commit-sha abc1234 --outcomes-json <PATH> --worktree-path <PATH>
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import json
19
+ import sys
20
+ from pathlib import Path
21
+ from xml.etree.ElementTree import Element, SubElement
22
+
23
+ _self_dir = Path(__file__).resolve().parent
24
+ if str(_self_dir) not in sys.path:
25
+ sys.path.insert(0, str(_self_dir))
26
+
27
+ from _cli_utils import require_file
28
+ from _path_resolver import fix_outcome_xml_path
29
+ from _xml_utils import emit_pretty_xml
30
+ from config.path_resolver_constants import (
31
+ ALL_FIX_OUTCOME_BODY_ELEMENT_KEYS,
32
+ ALL_VALID_FIX_STATUSES,
33
+ )
34
+
35
+
36
+ def validate_fix_statuses(all_outcomes: list[dict[str, object]]) -> list[str]:
37
+ """Validate that every outcome entry has a recognized status.
38
+
39
+ Args:
40
+ all_outcomes: List of outcome dicts, each expected to have a 'status' key.
41
+
42
+ Returns:
43
+ List of validation error messages (empty when valid).
44
+ """
45
+ valid_statuses = ALL_VALID_FIX_STATUSES
46
+ errors: list[str] = []
47
+ for each_index, each_outcome in enumerate(all_outcomes):
48
+ status = each_outcome.get("status")
49
+ if not isinstance(status, str):
50
+ errors.append(f"outcome[{each_index}]: status missing or not a string")
51
+ continue
52
+ if status not in valid_statuses:
53
+ errors.append(
54
+ f"outcome[{each_index}]: invalid status '{status}' "
55
+ f"(expected one of {sorted(valid_statuses)})"
56
+ )
57
+ return errors
58
+
59
+
60
+ def build_fix_xml(
61
+ *,
62
+ pr_number: int,
63
+ loop: int,
64
+ commit_sha: str,
65
+ outcomes_json_path: Path,
66
+ ) -> Element:
67
+ """Build the <bugteam_fix> XML element from outcomes data.
68
+
69
+ Args:
70
+ pr_number: Pull request number.
71
+ loop: Loop iteration number.
72
+ commit_sha: Commit SHA of the fix commit.
73
+ outcomes_json_path: Path to the outcomes JSON file.
74
+
75
+ Returns:
76
+ Root <bugteam_fix> element.
77
+
78
+ Raises:
79
+ SystemExit: When outcomes-json is not a JSON array of objects, or
80
+ when status validation fails.
81
+ """
82
+ outcomes_data = json.loads(outcomes_json_path.read_text(encoding="utf-8"))
83
+ if not isinstance(outcomes_data, list):
84
+ print("outcomes-json must contain a JSON array", file=sys.stderr)
85
+ raise SystemExit(1)
86
+ for each_index, each_entry in enumerate(outcomes_data):
87
+ if not isinstance(each_entry, dict):
88
+ print(
89
+ f"outcomes-json[{each_index}]: each entry must be a JSON object",
90
+ file=sys.stderr,
91
+ )
92
+ raise SystemExit(1)
93
+ errors = validate_fix_statuses(outcomes_data)
94
+ if errors:
95
+ for each_error in errors:
96
+ print(each_error, file=sys.stderr)
97
+ raise SystemExit(1)
98
+
99
+ root = Element("bugteam_fix", {
100
+ "pr": str(pr_number),
101
+ "loop": str(loop),
102
+ "commit_sha": commit_sha,
103
+ })
104
+
105
+ all_fix_outcome_body_element_keys = ALL_FIX_OUTCOME_BODY_ELEMENT_KEYS
106
+ outcomes_elem = SubElement(root, "outcomes")
107
+ for each_outcome in outcomes_data:
108
+ outcome_elem = SubElement(outcomes_elem, "outcome")
109
+ for each_key, each_field_detail in each_outcome.items():
110
+ field_text = (
111
+ str(each_field_detail) if each_field_detail is not None else ""
112
+ )
113
+ if each_key in all_fix_outcome_body_element_keys:
114
+ child = SubElement(outcome_elem, each_key)
115
+ child.text = field_text
116
+ else:
117
+ outcome_elem.set(each_key, field_text)
118
+ return root
119
+
120
+
121
+ def write_fix_xml(
122
+ *,
123
+ pr_number: int,
124
+ loop: int,
125
+ commit_sha: str,
126
+ outcomes_json_path: Path,
127
+ worktree_path: Path,
128
+ ) -> Path:
129
+ """Write the <bugteam_fix> XML to the canonical outcome path.
130
+
131
+ Args:
132
+ pr_number: Pull request number.
133
+ loop: Loop iteration number.
134
+ commit_sha: Commit SHA of the fix commit.
135
+ outcomes_json_path: Path to the outcomes JSON file.
136
+ worktree_path: Path to the git worktree.
137
+
138
+ Returns:
139
+ Path to the written XML file.
140
+ """
141
+ root = build_fix_xml(
142
+ pr_number=pr_number,
143
+ loop=loop,
144
+ commit_sha=commit_sha,
145
+ outcomes_json_path=outcomes_json_path,
146
+ )
147
+ xml_string = emit_pretty_xml(root)
148
+
149
+ output_path = fix_outcome_xml_path(worktree_path, pr_number, loop)
150
+ output_path.write_text(xml_string, encoding="utf-8")
151
+ return output_path
152
+
153
+
154
+ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
155
+ """Parse command-line arguments.
156
+
157
+ Args:
158
+ all_argv: Command-line argument list.
159
+
160
+ Returns:
161
+ Parsed namespace with pr_number, loop, commit_sha, outcomes_json, and worktree_path.
162
+ """
163
+ parser = argparse.ArgumentParser(description=__doc__)
164
+ parser.add_argument("--pr-number", type=int, required=True)
165
+ parser.add_argument("--loop", type=int, required=True)
166
+ parser.add_argument("--commit-sha", required=True)
167
+ parser.add_argument("--outcomes-json", type=Path, required=True)
168
+ parser.add_argument("--worktree-path", type=Path, required=True)
169
+ return parser.parse_args(all_argv)
170
+
171
+
172
+ def main(all_arguments: list[str]) -> int:
173
+ """Entry point: write <bugteam_fix> XML at the canonical path.
174
+
175
+ Args:
176
+ all_arguments: Command-line arguments.
177
+
178
+ Returns:
179
+ 0 on success, 1 on validation or write failure.
180
+ """
181
+ arguments = parse_arguments(all_arguments)
182
+ outcomes_path = getattr(arguments, "outcomes_json")
183
+
184
+ early_exit = require_file(outcomes_path, "outcomes-json")
185
+ if early_exit is not None:
186
+ return early_exit
187
+
188
+ try:
189
+ output_path = write_fix_xml(
190
+ pr_number=getattr(arguments, "pr_number"),
191
+ loop=arguments.loop,
192
+ commit_sha=getattr(arguments, "commit_sha"),
193
+ outcomes_json_path=outcomes_path,
194
+ worktree_path=getattr(arguments, "worktree_path"),
195
+ )
196
+ except SystemExit:
197
+ return 1
198
+ except (json.JSONDecodeError, OSError) as exc:
199
+ print(f"write_fix_xml failed: {exc}", file=sys.stderr)
200
+ return 1
201
+ print(output_path)
202
+ return 0
203
+
204
+
205
+ if __name__ == "__main__":
206
+ raise SystemExit(main(sys.argv[1:]))