claude-dev-env 1.95.0 → 2.0.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 (240) hide show
  1. package/_shared/advisor/CLAUDE.md +2 -2
  2. package/_shared/advisor/advisor-protocol.md +20 -20
  3. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +15 -12
  4. package/_shared/advisor/scripts/model_tier_run_validator.py +11 -10
  5. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +25 -19
  6. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +17 -17
  7. package/_shared/advisor/scripts/tier_model_ids.py +18 -18
  8. package/_shared/pr-loop/CLAUDE.md +1 -0
  9. package/_shared/pr-loop/scripts/CLAUDE.md +2 -1
  10. package/_shared/pr-loop/scripts/README.md +1 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate.py +253 -1980
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +32 -0
  13. package/_shared/pr-loop/scripts/code_rules_gate_parts/__init__.py +7 -0
  14. package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +268 -0
  15. package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +172 -0
  16. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +70 -0
  17. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +326 -0
  18. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +85 -0
  19. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +331 -0
  20. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +369 -0
  21. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/conftest.py +14 -0
  22. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_added_line_maps.py +118 -0
  23. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +17 -0
  24. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_arguments.py +29 -0
  25. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +99 -0
  26. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_blob_readers.py +69 -0
  27. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_file_sets.py +137 -0
  28. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +116 -0
  29. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_violation_scoping.py +75 -0
  30. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_wrapper_plumb_check.py +49 -0
  31. package/_shared/pr-loop/scripts/code_rules_gate_parts/violation_scoping.py +328 -0
  32. package/_shared/pr-loop/scripts/code_rules_gate_parts/wrapper_plumb_check.py +206 -0
  33. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +24 -17
  34. package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
  35. package/_shared/pr-loop/scripts/reviews_disabled.py +19 -2
  36. package/_shared/pr-loop/scripts/test_code_rules_gate.py +278 -0
  37. package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +6 -39
  38. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +43 -0
  39. package/_shared/pr-loop/worker-spawn.md +186 -0
  40. package/agents/code-verifier.md +1 -1
  41. package/bin/ever-shipped-skills.mjs +3 -0
  42. package/bin/expand_home_directory_tokens.mjs +1 -1
  43. package/bin/install.mjs +5 -2
  44. package/hooks/advisory/refactor_guard.py +3 -4
  45. package/hooks/blocking/CLAUDE.md +7 -1
  46. package/hooks/blocking/block_main_commit.py +2 -2
  47. package/hooks/blocking/claude_md_orphan_file_blocker.py +75 -699
  48. package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +28 -0
  49. package/hooks/blocking/claude_md_orphan_file_blocker_parts/__init__.py +1 -0
  50. package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/__init__.py +1 -0
  51. package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/orphan_blocker_constants.py +18 -0
  52. package/hooks/blocking/claude_md_orphan_file_blocker_parts/decision.py +81 -0
  53. package/hooks/blocking/claude_md_orphan_file_blocker_parts/references.py +307 -0
  54. package/hooks/blocking/claude_md_orphan_file_blocker_parts/scan_plan.py +124 -0
  55. package/hooks/blocking/claude_md_orphan_file_blocker_parts/subtree_scan.py +179 -0
  56. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/conftest.py +10 -0
  57. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_decision.py +34 -0
  58. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_references.py +42 -0
  59. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_scan_plan.py +27 -0
  60. package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_subtree_scan.py +30 -0
  61. package/hooks/blocking/code_rules_boolean_mustcheck.py +1 -1
  62. package/hooks/blocking/code_rules_mock_completeness.py +1 -1
  63. package/hooks/blocking/code_rules_optional_params.py +2 -2
  64. package/hooks/blocking/code_rules_shared.py +1 -1
  65. package/hooks/blocking/code_rules_test_assertions.py +1 -1
  66. package/hooks/blocking/code_rules_typeddict_stub.py +1 -1
  67. package/hooks/blocking/gh_pr_author_enforcer.py +1 -1
  68. package/hooks/blocking/inventory_intent_records/CLAUDE.md +26 -0
  69. package/hooks/blocking/inventory_intent_records/__init__.py +1 -0
  70. package/hooks/blocking/inventory_intent_records/config/__init__.py +1 -0
  71. package/hooks/blocking/inventory_intent_records/config/intent_records_constants.py +20 -0
  72. package/hooks/blocking/inventory_intent_records/records.py +271 -0
  73. package/hooks/blocking/inventory_intent_records/tests/conftest.py +10 -0
  74. package/hooks/blocking/inventory_intent_records/tests/test_records.py +80 -0
  75. package/hooks/blocking/package_inventory_stale_blocker.py +54 -384
  76. package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +26 -0
  77. package/hooks/blocking/package_inventory_stale_blocker_parts/__init__.py +1 -0
  78. package/hooks/blocking/package_inventory_stale_blocker_parts/config/__init__.py +1 -0
  79. package/hooks/blocking/package_inventory_stale_blocker_parts/config/inventory_blocker_constants.py +16 -0
  80. package/hooks/blocking/package_inventory_stale_blocker_parts/decision.py +84 -0
  81. package/hooks/blocking/package_inventory_stale_blocker_parts/inventory_detection.py +307 -0
  82. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/conftest.py +10 -0
  83. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_decision.py +38 -0
  84. package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_inventory_detection.py +61 -0
  85. package/hooks/blocking/pii_payload_scan.py +138 -42
  86. package/hooks/blocking/pii_prevention_blocker.py +185 -291
  87. package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +24 -0
  88. package/hooks/blocking/pii_prevention_blocker_parts/__init__.py +1 -0
  89. package/hooks/blocking/pii_prevention_blocker_parts/config/__init__.py +1 -0
  90. package/hooks/blocking/pii_prevention_blocker_parts/config/repository_resolution_constants.py +28 -0
  91. package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +214 -0
  92. package/hooks/blocking/pii_prevention_blocker_parts/repository_resolution.py +208 -0
  93. package/hooks/blocking/pr_description_command_parser.py +8 -4
  94. package/hooks/blocking/precommit_code_rules_gate.py +3 -3
  95. package/hooks/blocking/tdd_enforcer.py +97 -608
  96. package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +30 -0
  97. package/hooks/blocking/tdd_enforcer_parts/__init__.py +1 -0
  98. package/hooks/blocking/tdd_enforcer_parts/candidate_paths.py +142 -0
  99. package/hooks/blocking/tdd_enforcer_parts/config/__init__.py +1 -0
  100. package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +32 -0
  101. package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +268 -0
  102. package/hooks/blocking/tdd_enforcer_parts/decisions.py +92 -0
  103. package/hooks/blocking/tdd_enforcer_parts/freshness.py +80 -0
  104. package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +63 -0
  105. package/hooks/blocking/tdd_enforcer_parts/path_classification.py +119 -0
  106. package/hooks/blocking/tdd_enforcer_parts/tests/conftest.py +10 -0
  107. package/hooks/blocking/tdd_enforcer_parts/tests/test_candidate_paths.py +31 -0
  108. package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +30 -0
  109. package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +34 -0
  110. package/hooks/blocking/tdd_enforcer_parts/tests/test_freshness.py +28 -0
  111. package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +48 -0
  112. package/hooks/blocking/tdd_enforcer_parts/tests/test_path_classification.py +36 -0
  113. package/hooks/blocking/test_inventory_deadlock_resolution.py +154 -0
  114. package/hooks/blocking/test_pii_payload_scan.py +168 -0
  115. package/hooks/blocking/test_tdd_enforcer_restore.py +108 -0
  116. package/hooks/blocking/tests/conftest.py +10 -0
  117. package/hooks/blocking/tests/test_pii_prevention_blocker.py +260 -0
  118. package/hooks/blocking/tests/test_repository_exemption.py +105 -0
  119. package/hooks/blocking/tests/test_repository_resolution.py +108 -0
  120. package/hooks/diagnostic/hook_log_extractor.py +12 -10
  121. package/hooks/git-hooks/post_commit.py +3 -4
  122. package/hooks/hooks_constants/CLAUDE.md +2 -2
  123. package/hooks/hooks_constants/banned_identifiers_constants.py +0 -1
  124. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
  125. package/hooks/hooks_constants/local_identity.py +59 -8
  126. package/hooks/hooks_constants/pii_prevention_constants.py +0 -6
  127. package/hooks/hooks_constants/test_local_identity.py +105 -3
  128. package/hooks/pyproject.toml +13 -36
  129. package/hooks/session/plugin_data_dir_cleanup.py +0 -1
  130. package/hooks/validation/mypy_validator.py +2 -2
  131. package/hooks/validators/health_check.py +1 -0
  132. package/hooks/validators/mypy_integration.py +2 -0
  133. package/hooks/validators/ruff_integration.py +3 -0
  134. package/hooks/workflow/auto_formatter.py +5 -4
  135. package/package.json +1 -1
  136. package/scripts/CLAUDE.md +4 -0
  137. package/scripts/dev_env_scripts_constants/CLAUDE.md +6 -4
  138. package/scripts/dev_env_scripts_constants/code_review_constants.py +71 -0
  139. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +435 -0
  140. package/scripts/dev_env_scripts_constants/timing.py +7 -1
  141. package/scripts/grok_headless_runner.py +294 -0
  142. package/scripts/grok_worker_preflight.py +410 -0
  143. package/scripts/invoke_code_review.py +463 -0
  144. package/scripts/resolve_worker_spawn.py +619 -0
  145. package/scripts/spawn_grok_batch.py +672 -0
  146. package/scripts/test_grok_headless_runner.py +626 -0
  147. package/scripts/test_grok_worker_preflight.py +1054 -0
  148. package/scripts/test_invoke_code_review.py +672 -0
  149. package/scripts/test_resolve_worker_spawn.py +1014 -0
  150. package/scripts/test_spawn_grok_batch.py +1017 -0
  151. package/skills/CLAUDE.md +5 -3
  152. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +72 -13
  153. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +121 -14
  154. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +78 -0
  155. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +121 -0
  156. package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +196 -6
  157. package/skills/autoconverge/CLAUDE.md +3 -3
  158. package/skills/autoconverge/SKILL.md +9 -3
  159. package/skills/autoconverge/reference/CLAUDE.md +2 -2
  160. package/skills/autoconverge/reference/convergence.md +33 -11
  161. package/skills/autoconverge/reference/stop-conditions.md +16 -5
  162. package/skills/autoconverge/workflow/CLAUDE.md +2 -1
  163. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +7 -2
  164. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +300 -0
  165. package/skills/autoconverge/workflow/converge.contract.test.mjs +5 -5
  166. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +29 -29
  167. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  168. package/skills/autoconverge/workflow/converge.mjs +200 -16
  169. package/skills/bugteam/CLAUDE.md +2 -2
  170. package/skills/bugteam/CONSTRAINTS.md +3 -2
  171. package/skills/bugteam/PROMPTS.md +7 -6
  172. package/skills/bugteam/SKILL.md +18 -13
  173. package/skills/bugteam/reference/audit-and-teammates.md +215 -35
  174. package/skills/bugteam/reference/design-rationale.md +1 -1
  175. package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -1
  176. package/skills/bugteam/reference/team-setup.md +8 -2
  177. package/skills/codex-review/CLAUDE.md +46 -0
  178. package/skills/codex-review/SKILL.md +181 -0
  179. package/skills/codex-review/reference/CLAUDE.md +15 -0
  180. package/skills/codex-review/reference/cli-contract.md +253 -0
  181. package/skills/codex-review/reference/loop-integration.md +118 -0
  182. package/skills/codex-review/scripts/codex_down_classifier.py +98 -0
  183. package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +18 -0
  184. package/skills/codex-review/scripts/codex_review_scripts_constants/__init__.py +1 -0
  185. package/skills/codex-review/scripts/codex_review_scripts_constants/classifier_constants.py +35 -0
  186. package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +86 -0
  187. package/skills/codex-review/scripts/codex_review_scripts_constants/findings_constants.py +18 -0
  188. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +45 -0
  189. package/skills/codex-review/scripts/codex_usage_probe.py +573 -0
  190. package/skills/codex-review/scripts/fixtures/auth_failure_synthetic.txt +1 -0
  191. package/skills/codex-review/scripts/fixtures/config_load_failure_v0.125.0.txt +1 -0
  192. package/skills/codex-review/scripts/fixtures/freeform_findings_v0.144.3.txt +6 -0
  193. package/skills/codex-review/scripts/fixtures/model_rejection_v0.125.0.jsonl +5 -0
  194. package/skills/codex-review/scripts/fixtures/structured_findings.txt +13 -0
  195. package/skills/codex-review/scripts/fixtures/success_stream_v0.144.3.jsonl +6 -0
  196. package/skills/codex-review/scripts/fixtures/unknown_failure_synthetic.txt +1 -0
  197. package/skills/codex-review/scripts/fixtures/usage_limit_synthetic.txt +1 -0
  198. package/skills/codex-review/scripts/parse_codex_findings.py +207 -0
  199. package/skills/codex-review/scripts/run_codex_review.py +415 -0
  200. package/skills/codex-review/scripts/test_codex_down_classifier.py +143 -0
  201. package/skills/codex-review/scripts/test_codex_usage_probe.py +678 -0
  202. package/skills/codex-review/scripts/test_parse_codex_findings.py +130 -0
  203. package/skills/codex-review/scripts/test_run_codex_review.py +812 -0
  204. package/skills/codex-review/test_skill_scaffold.py +192 -0
  205. package/skills/grok-spawn/CLAUDE.md +28 -0
  206. package/skills/grok-spawn/SKILL.md +226 -0
  207. package/skills/grok-spawn/reference/flag-profiles.md +132 -0
  208. package/skills/grok-spawn/reference/worker-briefs.md +152 -0
  209. package/skills/grokify/SKILL.md +9 -1
  210. package/skills/grokify/capability-claims.test.mjs +28 -0
  211. package/skills/grokify/evals/README.md +72 -0
  212. package/skills/grokify/evals/parse-payload.test.mjs +171 -0
  213. package/skills/grokify/evals/run-capability-evals.mjs +545 -0
  214. package/skills/orchestrator/SKILL.md +15 -11
  215. package/skills/orchestrator-refresh/SKILL.md +5 -5
  216. package/skills/pr-converge/SKILL.md +34 -13
  217. package/skills/pr-converge/reference/convergence-gates.md +42 -15
  218. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  219. package/skills/pr-converge/reference/ground-rules.md +1 -1
  220. package/skills/pr-converge/reference/per-tick.md +130 -42
  221. package/skills/pr-converge/reference/state-schema.md +10 -0
  222. package/skills/pr-converge/scripts/CLAUDE.md +2 -0
  223. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +5 -1
  224. package/skills/pr-converge/scripts/check_convergence.py +605 -29
  225. package/skills/pr-converge/scripts/check_convergence_availability.py +232 -0
  226. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -235
  227. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +1 -1
  228. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -2
  229. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +4 -0
  230. package/skills/pr-converge/scripts/test_check_convergence.py +71 -3
  231. package/skills/pr-converge/scripts/test_check_convergence_availability.py +326 -0
  232. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -0
  233. package/skills/pr-converge/scripts/test_check_convergence_contract.py +89 -17
  234. package/skills/pr-converge/scripts/test_check_convergence_fixture.py +179 -0
  235. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -68
  236. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +24 -0
  237. package/skills/pr-converge/test_step5_host_branch.py +106 -0
  238. package/skills/pr-loop-cloud-transport/SKILL.md +2 -0
  239. package/skills/reviewer-gates/SKILL.md +7 -5
  240. package/skills/team-advisor/SKILL.md +7 -7
package/skills/CLAUDE.md CHANGED
@@ -23,9 +23,11 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
23
23
 
24
24
  **Planning and implementation**
25
25
  - `anthropic-plan` — creates a source-grounded plan packet before any code changes
26
- - `orchestrator` — turns the session into the orchestrator: it spawns executor subagents to do the code edits and test runs; hard decisions go to a shared advisor (Claude warm `session-advisor` via SendMessage; Grok: max-tier Claude via CLI Claude-chain)
27
- - `orchestrator-refresh` — sub-skill fired by the `/orchestrator` loop to re-assert the host-matched shared-advisor discipline mid-run (Claude SendMessage; Grok Claude CLI chain, no Agent-tool advisor spawn)
28
- - `team-advisor` — binds one advisor at the strongest reachable tier (Claude warm agent; Grok max-tier Claude via CLI Claude-chain, fail closed when unreachable) and consults it for a second opinion before a big decision, at completion, when stuck, or when reconsidering the approach
26
+ - `orchestrator` — turns the session into the orchestrator: it spawns executor subagents to do the code edits and test runs; hard decisions go to a shared advisor (Claude warm `session-advisor` via SendMessage; a third-party host: max-tier Claude via CLI Claude-chain)
27
+ - `orchestrator-refresh` — sub-skill fired by the `/orchestrator` loop to re-assert the host-matched shared-advisor discipline mid-run (Claude SendMessage; a third-party host's Claude CLI chain, no Agent-tool advisor spawn)
28
+ - `team-advisor` — binds one advisor at the strongest reachable tier (Claude warm agent; a third-party host: max-tier Claude via CLI Claude-chain, fail closed when unreachable) and consults it for a second opinion before a big decision, at completion, when stuck, or when reconsidering the approach
29
+ - `grokify` — builds a paste-ready Grok Build handoff with a Claude advisor charter
30
+ - `grok-spawn` — orchestrator playbook for fleets of headless grok CLI workers (preflight, batch spec, `spawn_grok_batch.py`)
29
31
 
30
32
  **PR review and convergence**
31
33
  - `autoconverge` — autonomous single-run workflow that drives a PR to ready
@@ -2,12 +2,13 @@
2
2
 
3
3
  Builds <context> and <scope> from CLI args; <bug_categories>,
4
4
  <rubric_reference>, and <constraints> come from the shared constants in
5
- skills_pr_loop_constants; <comment_posting> and <output_format> are built
6
- inline. <pr_description> carries the PR body text read from --pr-body-file,
7
- and stays empty when that argument is absent or the file cannot be read.
5
+ skills_pr_loop_constants; <comment_posting> and <output_format> follow
6
+ ``--flavor`` (agent or headless). <pr_description> carries the PR body
7
+ text read from --pr-body-file, and stays empty when that argument is
8
+ absent or the file cannot be read.
8
9
 
9
10
  Usage:
10
- python scripts/build_audit_prompt.py --owner jl-cmd --repo claude-dev-env --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --run-temp-dir <PATH>
11
+ python scripts/build_audit_prompt.py --owner jl-cmd --repo claude-dev-env --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --run-temp-dir <PATH> [--flavor agent|headless]
11
12
  """
12
13
 
13
14
  from __future__ import annotations
@@ -21,10 +22,18 @@ _self_dir = Path(__file__).resolve().parent
21
22
  if str(_self_dir) not in sys.path:
22
23
  sys.path.insert(0, str(_self_dir))
23
24
 
25
+ from _path_resolver import outcome_xml_path
24
26
  from _xml_utils import emit_pretty_xml
25
27
  from skills_pr_loop_constants.path_resolver_constants import (
26
28
  ALL_AUDIT_CATEGORY_ENTRIES,
27
29
  ALL_AUDIT_CONSTRAINT_TEXTS,
30
+ ALL_AUDIT_PROMPT_FLAVORS,
31
+ AUDIT_COMMENT_POSTING_AGENT_TEXT,
32
+ AUDIT_COMMENT_POSTING_HEADLESS_TEXT,
33
+ AUDIT_OUTPUT_FORMAT_AGENT_TEXT,
34
+ AUDIT_OUTPUT_FORMAT_HEADLESS_TEMPLATE,
35
+ AUDIT_PROMPT_FLAVOR_AGENT,
36
+ AUDIT_PROMPT_FLAVOR_HEADLESS,
28
37
  AUDIT_RUBRIC_REFERENCE_TEXT,
29
38
  )
30
39
 
@@ -61,6 +70,46 @@ def _append_pr_description(root: Element, pr_body_text: str | None) -> None:
61
70
  pr_description.text = pr_body_text
62
71
 
63
72
 
73
+ def _comment_posting_text(flavor: str) -> str:
74
+ """Return the <comment_posting> body for the given prompt flavor.
75
+
76
+ Args:
77
+ flavor: ``agent`` or ``headless`` prompt flavor.
78
+
79
+ Returns:
80
+ Comment-posting instruction text for that flavor.
81
+ """
82
+ if flavor == AUDIT_PROMPT_FLAVOR_HEADLESS:
83
+ return AUDIT_COMMENT_POSTING_HEADLESS_TEXT
84
+ return AUDIT_COMMENT_POSTING_AGENT_TEXT
85
+
86
+
87
+ def _format_instruction_text(
88
+ *,
89
+ flavor: str,
90
+ worktree_path: Path,
91
+ pr_number: int,
92
+ loop: int,
93
+ ) -> str:
94
+ """Return the <output_format> body for the given prompt flavor.
95
+
96
+ Args:
97
+ flavor: ``agent`` or ``headless`` prompt flavor.
98
+ worktree_path: Path to the git worktree.
99
+ pr_number: Pull request number.
100
+ loop: Loop iteration number.
101
+
102
+ Returns:
103
+ Format-instruction text for that flavor.
104
+ """
105
+ if flavor == AUDIT_PROMPT_FLAVOR_HEADLESS:
106
+ outcome_path = outcome_xml_path(worktree_path, pr_number, loop)
107
+ return AUDIT_OUTPUT_FORMAT_HEADLESS_TEMPLATE.format(
108
+ outcome_path=outcome_path.as_posix(),
109
+ )
110
+ return AUDIT_OUTPUT_FORMAT_AGENT_TEXT
111
+
112
+
64
113
  def build_audit_prompt_xml(
65
114
  *,
66
115
  owner: str,
@@ -72,6 +121,7 @@ def build_audit_prompt_xml(
72
121
  worktree_path: Path,
73
122
  run_temp_dir: Path,
74
123
  pr_body_text: str | None = None,
124
+ flavor: str = AUDIT_PROMPT_FLAVOR_AGENT,
75
125
  ) -> Element:
76
126
  """Build the complete AUDIT spawn prompt XML.
77
127
 
@@ -85,6 +135,8 @@ def build_audit_prompt_xml(
85
135
  worktree_path: Path to the git worktree.
86
136
  run_temp_dir: Path to the run temp directory.
87
137
  pr_body_text: PR description body text, or None when unavailable.
138
+ flavor: Prompt flavor — ``agent`` (MCP posting) or ``headless``
139
+ (outcome file, lead-owned posting).
88
140
 
89
141
  Returns:
90
142
  Root <spawn_prompt> element.
@@ -122,16 +174,14 @@ def build_audit_prompt_xml(
122
174
  SubElement(constraints, "constraint").text = each_constraint
123
175
 
124
176
  comment_posting = SubElement(root, "comment_posting")
125
- comment_posting.text = (
126
- "Post findings as inline review comments on the PR via "
127
- "the GitHub MCP add_comment_to_pending_review tool. "
128
- "Group related findings into a single pending review."
129
- )
177
+ comment_posting.text = _comment_posting_text(flavor)
130
178
 
131
- output_format = SubElement(root, "output_format")
132
- output_format.text = (
133
- "Emit findings as JSON array of objects with keys: "
134
- "severity (P0/P1/P2), file, line, category, message, suggestion."
179
+ format_section = SubElement(root, "output_format")
180
+ format_section.text = _format_instruction_text(
181
+ flavor=flavor,
182
+ worktree_path=worktree_path,
183
+ pr_number=pr_number,
184
+ loop=loop,
135
185
  )
136
186
 
137
187
  return root
@@ -148,6 +198,7 @@ def emit_audit_prompt(
148
198
  worktree_path: Path,
149
199
  run_temp_dir: Path,
150
200
  pr_body_text: str | None = None,
201
+ flavor: str = AUDIT_PROMPT_FLAVOR_AGENT,
151
202
  ) -> str:
152
203
  """Build and serialize the AUDIT spawn prompt to a pretty-printed XML string.
153
204
 
@@ -161,6 +212,7 @@ def emit_audit_prompt(
161
212
  worktree_path: Path to the git worktree.
162
213
  run_temp_dir: Path to the run temp directory.
163
214
  pr_body_text: PR description body text, or None when unavailable.
215
+ flavor: Prompt flavor — ``agent`` or ``headless``.
164
216
 
165
217
  Returns:
166
218
  Pretty-printed XML string.
@@ -175,6 +227,7 @@ def emit_audit_prompt(
175
227
  worktree_path=worktree_path,
176
228
  run_temp_dir=run_temp_dir,
177
229
  pr_body_text=pr_body_text,
230
+ flavor=flavor,
178
231
  )
179
232
  return emit_pretty_xml(root)
180
233
 
@@ -198,6 +251,11 @@ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
198
251
  parser.add_argument("--worktree-path", type=Path, required=True)
199
252
  parser.add_argument("--run-temp-dir", type=Path, required=True)
200
253
  parser.add_argument("--pr-body-file", type=Path, default=None)
254
+ parser.add_argument(
255
+ "--flavor",
256
+ choices=sorted(ALL_AUDIT_PROMPT_FLAVORS),
257
+ default=AUDIT_PROMPT_FLAVOR_AGENT,
258
+ )
201
259
  return parser.parse_args(all_argv)
202
260
 
203
261
 
@@ -221,6 +279,7 @@ def main(all_arguments: list[str]) -> int:
221
279
  worktree_path=arguments.worktree_path,
222
280
  run_temp_dir=arguments.run_temp_dir,
223
281
  pr_body_text=read_pr_body_text(arguments.pr_body_file),
282
+ flavor=arguments.flavor,
224
283
  )
225
284
  sys.stdout.write(xml_output)
226
285
  return 0
@@ -1,10 +1,11 @@
1
1
  """Emit the complete FIX spawn prompt XML to stdout.
2
2
 
3
3
  Populates <bug> entries from findings JSON, plus <execution> checklist
4
- and <constraints>.
4
+ and <constraints>. <comment_posting> and <output_format> follow
5
+ ``--flavor`` (agent or headless).
5
6
 
6
7
  Usage:
7
- python scripts/build_fix_prompt.py --owner jl-cmd --repo claude-dev-env --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --findings-json <PATH>
8
+ python scripts/build_fix_prompt.py --owner jl-cmd --repo claude-dev-env --pr-number 422 --loop 1 --head-ref feat/branch --base-ref main --worktree-path <PATH> --findings-json <PATH> [--flavor agent|headless]
8
9
  """
9
10
 
10
11
  from __future__ import annotations
@@ -20,13 +21,92 @@ if str(_self_dir) not in sys.path:
20
21
  sys.path.insert(0, str(_self_dir))
21
22
 
22
23
  from _cli_utils import require_file
24
+ from _path_resolver import fix_outcome_xml_path
23
25
  from _xml_utils import emit_pretty_xml
24
26
  from skills_pr_loop_constants.path_resolver_constants import (
25
27
  ALL_FIX_CONSTRAINT_TEXTS,
28
+ ALL_FIX_CONSTRAINT_TEXTS_HEADLESS,
26
29
  ALL_FIX_EXECUTION_STEPS,
30
+ ALL_FIX_EXECUTION_STEPS_HEADLESS,
31
+ ALL_FIX_PROMPT_FLAVORS,
32
+ FINDINGS_JSON_MUST_BE_LIST_MESSAGE,
33
+ FIX_COMMENT_POSTING_AGENT_TEXT,
34
+ FIX_COMMENT_POSTING_HEADLESS_TEXT,
35
+ FIX_OUTPUT_FORMAT_AGENT_TEXT,
36
+ FIX_OUTPUT_FORMAT_HEADLESS_TEMPLATE,
37
+ FIX_PROMPT_FLAVOR_AGENT,
38
+ FIX_PROMPT_FLAVOR_HEADLESS,
27
39
  )
28
40
 
29
41
 
42
+ def _comment_posting_text(flavor: str) -> str:
43
+ """Return the <comment_posting> body for the given prompt flavor.
44
+
45
+ Args:
46
+ flavor: ``agent`` or ``headless`` prompt flavor.
47
+
48
+ Returns:
49
+ Comment-posting instruction text for that flavor.
50
+ """
51
+ if flavor == FIX_PROMPT_FLAVOR_HEADLESS:
52
+ return FIX_COMMENT_POSTING_HEADLESS_TEXT
53
+ return FIX_COMMENT_POSTING_AGENT_TEXT
54
+
55
+
56
+ def _format_instruction_text(
57
+ *,
58
+ flavor: str,
59
+ worktree_path: Path,
60
+ pr_number: int,
61
+ loop: int,
62
+ ) -> str:
63
+ """Return the <output_format> body for the given prompt flavor.
64
+
65
+ Args:
66
+ flavor: ``agent`` or ``headless`` prompt flavor.
67
+ worktree_path: Path to the git worktree.
68
+ pr_number: Pull request number.
69
+ loop: Loop iteration number.
70
+
71
+ Returns:
72
+ Format-instruction text for that flavor.
73
+ """
74
+ if flavor == FIX_PROMPT_FLAVOR_HEADLESS:
75
+ outcome_path = fix_outcome_xml_path(worktree_path, pr_number, loop)
76
+ return FIX_OUTPUT_FORMAT_HEADLESS_TEMPLATE.format(
77
+ outcome_path=outcome_path.as_posix(),
78
+ )
79
+ return FIX_OUTPUT_FORMAT_AGENT_TEXT
80
+
81
+
82
+ def _all_execution_steps(flavor: str) -> list[str]:
83
+ """Return the execution checklist for the given prompt flavor.
84
+
85
+ Args:
86
+ flavor: ``agent`` or ``headless`` prompt flavor.
87
+
88
+ Returns:
89
+ Ordered execution step texts for that flavor.
90
+ """
91
+ if flavor == FIX_PROMPT_FLAVOR_HEADLESS:
92
+ return list(ALL_FIX_EXECUTION_STEPS_HEADLESS)
93
+ return list(ALL_FIX_EXECUTION_STEPS)
94
+
95
+
96
+ def _all_constraint_texts(flavor: str) -> list[str]:
97
+ """Return the constraint texts for the given prompt flavor.
98
+
99
+ Args:
100
+ flavor: ``agent`` or ``headless`` prompt flavor.
101
+
102
+ Returns:
103
+ Ordered constraint texts for that flavor.
104
+ """
105
+ if flavor == FIX_PROMPT_FLAVOR_HEADLESS:
106
+ return list(ALL_FIX_CONSTRAINT_TEXTS_HEADLESS)
107
+ return list(ALL_FIX_CONSTRAINT_TEXTS)
108
+
109
+
30
110
  def build_fix_prompt_xml(
31
111
  *,
32
112
  owner: str,
@@ -37,6 +117,7 @@ def build_fix_prompt_xml(
37
117
  base_ref: str,
38
118
  worktree_path: Path,
39
119
  findings_json_path: Path,
120
+ flavor: str = FIX_PROMPT_FLAVOR_AGENT,
40
121
  ) -> Element:
41
122
  """Build the complete FIX spawn prompt XML.
42
123
 
@@ -49,11 +130,18 @@ def build_fix_prompt_xml(
49
130
  base_ref: Base branch ref.
50
131
  worktree_path: Path to the git worktree.
51
132
  findings_json_path: Path to the findings JSON file.
133
+ flavor: Prompt flavor — ``agent`` (in-agent commit/post) or
134
+ ``headless`` (outcome file, lead-owned commit/post).
52
135
 
53
136
  Returns:
54
137
  Root <spawn_prompt> element.
55
138
  """
56
139
  findings_data = json.loads(findings_json_path.read_text(encoding="utf-8"))
140
+ if not isinstance(findings_data, list):
141
+ findings_type_name = type(findings_data).__name__
142
+ raise TypeError(
143
+ FINDINGS_JSON_MUST_BE_LIST_MESSAGE % findings_type_name
144
+ )
57
145
 
58
146
  root = Element("spawn_prompt", {"role": "fix", "loop": str(loop)})
59
147
 
@@ -66,24 +154,34 @@ def build_fix_prompt_xml(
66
154
  SubElement(context, "worktree_path").text = worktree_path.as_posix()
67
155
 
68
156
  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
- )
157
+ for each_finding in findings_data:
158
+ if isinstance(each_finding, dict):
159
+ bug = SubElement(bugs_elem, "bug")
160
+ for each_key, each_field_detail in each_finding.items():
161
+ child = SubElement(bug, each_key)
162
+ child.text = (
163
+ str(each_field_detail) if each_field_detail is not None else ""
164
+ )
78
165
 
79
166
  execution = SubElement(root, "execution")
80
- for each_step in ALL_FIX_EXECUTION_STEPS:
167
+ for each_step in _all_execution_steps(flavor):
81
168
  SubElement(execution, "step").text = each_step
82
169
 
83
170
  constraints = SubElement(root, "constraints")
84
- for each_constraint in ALL_FIX_CONSTRAINT_TEXTS:
171
+ for each_constraint in _all_constraint_texts(flavor):
85
172
  SubElement(constraints, "constraint").text = each_constraint
86
173
 
174
+ comment_posting = SubElement(root, "comment_posting")
175
+ comment_posting.text = _comment_posting_text(flavor)
176
+
177
+ format_section = SubElement(root, "output_format")
178
+ format_section.text = _format_instruction_text(
179
+ flavor=flavor,
180
+ worktree_path=worktree_path,
181
+ pr_number=pr_number,
182
+ loop=loop,
183
+ )
184
+
87
185
  return root
88
186
 
89
187
 
@@ -97,6 +195,7 @@ def emit_fix_prompt(
97
195
  base_ref: str,
98
196
  worktree_path: Path,
99
197
  findings_json_path: Path,
198
+ flavor: str = FIX_PROMPT_FLAVOR_AGENT,
100
199
  ) -> str:
101
200
  """Build and serialize the FIX spawn prompt to a pretty-printed XML string.
102
201
 
@@ -109,6 +208,7 @@ def emit_fix_prompt(
109
208
  base_ref: Base branch ref.
110
209
  worktree_path: Path to the git worktree.
111
210
  findings_json_path: Path to the findings JSON file.
211
+ flavor: Prompt flavor — ``agent`` or ``headless``.
112
212
 
113
213
  Returns:
114
214
  Pretty-printed XML string.
@@ -122,6 +222,7 @@ def emit_fix_prompt(
122
222
  base_ref=base_ref,
123
223
  worktree_path=worktree_path,
124
224
  findings_json_path=findings_json_path,
225
+ flavor=flavor,
125
226
  )
126
227
  return emit_pretty_xml(root)
127
228
 
@@ -144,6 +245,11 @@ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
144
245
  parser.add_argument("--base-ref", required=True)
145
246
  parser.add_argument("--worktree-path", type=Path, required=True)
146
247
  parser.add_argument("--findings-json", type=Path, required=True)
248
+ parser.add_argument(
249
+ "--flavor",
250
+ choices=sorted(ALL_FIX_PROMPT_FLAVORS),
251
+ default=FIX_PROMPT_FLAVOR_AGENT,
252
+ )
147
253
  return parser.parse_args(all_argv)
148
254
 
149
255
 
@@ -173,8 +279,9 @@ def main(all_arguments: list[str]) -> int:
173
279
  base_ref=getattr(arguments, "base_ref"),
174
280
  worktree_path=getattr(arguments, "worktree_path"),
175
281
  findings_json_path=findings_path,
282
+ flavor=arguments.flavor,
176
283
  )
177
- except (json.JSONDecodeError, OSError) as exc:
284
+ except (json.JSONDecodeError, OSError, TypeError) as exc:
178
285
  print(f"emit_fix_prompt failed: {exc}", file=sys.stderr)
179
286
  return 1
180
287
  sys.stdout.write(xml_output)
@@ -56,6 +56,34 @@ AUDIT_RUBRIC_REFERENCE_TEXT = (
56
56
  "Read the rubric files before auditing."
57
57
  )
58
58
 
59
+ AUDIT_PROMPT_FLAVOR_AGENT = "agent"
60
+ AUDIT_PROMPT_FLAVOR_HEADLESS = "headless"
61
+ ALL_AUDIT_PROMPT_FLAVORS = frozenset({
62
+ AUDIT_PROMPT_FLAVOR_AGENT,
63
+ AUDIT_PROMPT_FLAVOR_HEADLESS,
64
+ })
65
+
66
+ AUDIT_COMMENT_POSTING_AGENT_TEXT = (
67
+ "Post findings as inline review comments on the PR via "
68
+ "the GitHub MCP add_comment_to_pending_review tool. "
69
+ "Group related findings into a single pending review."
70
+ )
71
+ AUDIT_COMMENT_POSTING_HEADLESS_TEXT = (
72
+ "Do not post reviews or comments. Use gh only when you must read "
73
+ "PR metadata. Write findings into the outcome XML named in "
74
+ "<output_format>. The lead session posts the review with "
75
+ "post_audit_thread.py after the worker exits."
76
+ )
77
+ AUDIT_OUTPUT_FORMAT_AGENT_TEXT = (
78
+ "Emit findings as JSON array of objects with keys: "
79
+ "severity (P0/P1/P2), file, line, category, message, suggestion."
80
+ )
81
+ AUDIT_OUTPUT_FORMAT_HEADLESS_TEMPLATE = (
82
+ "Write the complete audit outcome XML to {outcome_path}. "
83
+ "Leave review_url empty when the lead posts after you exit. "
84
+ "Do not use TaskCreate, MCP tools, or Artifact tools."
85
+ )
86
+
59
87
  ALL_FIX_EXECUTION_STEPS = [
60
88
  "Read the finding and verify it against the current file at file:line.",
61
89
  "Write a failing test that reproduces the bug.",
@@ -66,6 +94,14 @@ ALL_FIX_EXECUTION_STEPS = [
66
94
  "Post an inline reply on the finding thread confirming the fix.",
67
95
  ]
68
96
 
97
+ ALL_FIX_EXECUTION_STEPS_HEADLESS = [
98
+ "Read the finding and verify it against the current file at file:line.",
99
+ "Write a failing test that reproduces the bug.",
100
+ "Implement the smallest change that resolves the finding.",
101
+ "Run the full test suite — confirm the new test and all existing tests pass.",
102
+ "Leave the working tree dirty with your edits. Do not stage, commit, or push.",
103
+ ]
104
+
69
105
  ALL_FIX_CONSTRAINT_TEXTS = [
70
106
  "Work exclusively within the worktree directory.",
71
107
  "Change only the lines directly related to each finding.",
@@ -77,8 +113,50 @@ ALL_FIX_CONSTRAINT_TEXTS = [
77
113
  "forward slashes (e.g. C:/Users/...), even on Windows.",
78
114
  ]
79
115
 
116
+ ALL_FIX_CONSTRAINT_TEXTS_HEADLESS = [
117
+ "Work exclusively within the worktree directory.",
118
+ "Change only the lines directly related to each finding.",
119
+ "Every fix must have a corresponding test.",
120
+ "Remove deprecated code directly and update all call sites.",
121
+ "Handle each error case with a named exception type.",
122
+ "Do not stage, commit, or push. The lead owns git write operations.",
123
+ "Do not use TaskCreate, MCP tools, or Artifact tools.",
124
+ "Double-quote every path in shell commands and write paths with "
125
+ "forward slashes (e.g. C:/Users/...), even on Windows.",
126
+ ]
127
+
128
+ FIX_PROMPT_FLAVOR_AGENT = "agent"
129
+ FIX_PROMPT_FLAVOR_HEADLESS = "headless"
130
+ ALL_FIX_PROMPT_FLAVORS = frozenset({
131
+ FIX_PROMPT_FLAVOR_AGENT,
132
+ FIX_PROMPT_FLAVOR_HEADLESS,
133
+ })
134
+
135
+ FIX_COMMENT_POSTING_AGENT_TEXT = (
136
+ "After commit, post one reply per finding thread via the GitHub MCP "
137
+ "add_reply_to_pull_request_comment tool, then resolve the thread."
138
+ )
139
+ FIX_COMMENT_POSTING_HEADLESS_TEXT = (
140
+ "Do not post replies or resolve threads. Use gh only when you must read "
141
+ "PR metadata. Write per-finding outcomes into the outcome XML named in "
142
+ "<output_format>. The lead session posts replies after the worker exits."
143
+ )
144
+ FIX_OUTPUT_FORMAT_AGENT_TEXT = (
145
+ "After posting replies, write the complete fix outcome XML "
146
+ "(schema in PROMPTS.md)."
147
+ )
148
+ FIX_OUTPUT_FORMAT_HEADLESS_TEMPLATE = (
149
+ "Write the complete fix outcome XML to {outcome_path}. "
150
+ "Leave commit_sha empty when the lead commits after you exit. "
151
+ "Do not use TaskCreate, MCP tools, or Artifact tools."
152
+ )
153
+
80
154
  ALL_PYTHON_ONEXC_VERSION = (3, 12)
81
155
 
156
+ FINDINGS_JSON_MUST_BE_LIST_MESSAGE = (
157
+ "findings-json root must be a JSON array of finding objects, got %s"
158
+ )
159
+
82
160
  ALL_FINDING_BODY_ELEMENT_KEYS: tuple[str, ...] = (
83
161
  "title",
84
162
  "excerpt",
@@ -9,12 +9,21 @@ from pathlib import Path
9
9
  from types import ModuleType
10
10
  from xml.etree.ElementTree import Element
11
11
 
12
+ import pytest
13
+
12
14
  _SCRIPTS_DIR = Path(__file__).resolve().parent
13
15
  if str(_SCRIPTS_DIR) not in sys.path:
14
16
  sys.path.insert(0, str(_SCRIPTS_DIR))
15
17
 
16
18
  from skills_pr_loop_constants.path_resolver_constants import (
17
19
  ALL_AUDIT_CATEGORY_ENTRIES,
20
+ AUDIT_COMMENT_POSTING_AGENT_TEXT,
21
+ AUDIT_COMMENT_POSTING_HEADLESS_TEXT,
22
+ AUDIT_OUTPUT_FORMAT_AGENT_TEXT,
23
+ AUDIT_OUTPUT_FORMAT_HEADLESS_TEMPLATE,
24
+ AUDIT_PROMPT_FLAVOR_AGENT,
25
+ AUDIT_PROMPT_FLAVOR_HEADLESS,
26
+ OUTCOME_XML_TEMPLATE,
18
27
  )
19
28
 
20
29
  _CATEGORY_RUBRICS_DIR = _SCRIPTS_DIR.parents[3] / "audit-rubrics" / "category_rubrics"
@@ -257,3 +266,115 @@ def test_parse_arguments_reads_pr_body_file_path() -> None:
257
266
  "--pr-body-file", "/tmp/body.md",
258
267
  ])
259
268
  assert arguments.pr_body_file == Path("/tmp/body.md")
269
+
270
+
271
+ def test_parse_arguments_defaults_flavor_to_agent() -> None:
272
+ arguments = build_audit_prompt.parse_arguments([
273
+ "--owner", "jl-cmd",
274
+ "--repo", "claude-dev-env",
275
+ "--pr-number", "422",
276
+ "--loop", "1",
277
+ "--head-ref", "feat/branch",
278
+ "--base-ref", "main",
279
+ "--worktree-path", "/tmp/wt",
280
+ "--run-temp-dir", "/tmp/rt",
281
+ ])
282
+ assert arguments.flavor == AUDIT_PROMPT_FLAVOR_AGENT
283
+
284
+
285
+ def test_parse_arguments_reads_headless_flavor() -> None:
286
+ arguments = build_audit_prompt.parse_arguments([
287
+ "--owner", "jl-cmd",
288
+ "--repo", "claude-dev-env",
289
+ "--pr-number", "422",
290
+ "--loop", "1",
291
+ "--head-ref", "feat/branch",
292
+ "--base-ref", "main",
293
+ "--worktree-path", "/tmp/wt",
294
+ "--run-temp-dir", "/tmp/rt",
295
+ "--flavor", AUDIT_PROMPT_FLAVOR_HEADLESS,
296
+ ])
297
+ assert arguments.flavor == AUDIT_PROMPT_FLAVOR_HEADLESS
298
+
299
+
300
+ def test_agent_flavor_comment_posting_names_mcp_tool() -> None:
301
+ root = build_audit_prompt.build_audit_prompt_xml(
302
+ owner="jl-cmd",
303
+ repo="claude-dev-env",
304
+ pr_number=422,
305
+ loop=1,
306
+ head_ref="feat/branch",
307
+ base_ref="main",
308
+ worktree_path=Path("/tmp/bugteam-pr-422/worktree"),
309
+ run_temp_dir=Path("/tmp/bugteam-pr-422"),
310
+ flavor=AUDIT_PROMPT_FLAVOR_AGENT,
311
+ )
312
+ comment_posting = root.find("comment_posting")
313
+ assert comment_posting is not None
314
+ assert comment_posting.text == AUDIT_COMMENT_POSTING_AGENT_TEXT
315
+ output_format = root.find("output_format")
316
+ assert output_format is not None
317
+ assert output_format.text == AUDIT_OUTPUT_FORMAT_AGENT_TEXT
318
+
319
+
320
+ def test_headless_flavor_writes_outcome_path_and_forbids_mcp() -> None:
321
+ worktree_path = Path("/tmp/bugteam-pr-422/worktree")
322
+ pr_number = 422
323
+ loop = 3
324
+ root = build_audit_prompt.build_audit_prompt_xml(
325
+ owner="jl-cmd",
326
+ repo="claude-dev-env",
327
+ pr_number=pr_number,
328
+ loop=loop,
329
+ head_ref="feat/branch",
330
+ base_ref="main",
331
+ worktree_path=worktree_path,
332
+ run_temp_dir=Path("/tmp/bugteam-pr-422"),
333
+ flavor=AUDIT_PROMPT_FLAVOR_HEADLESS,
334
+ )
335
+ comment_posting = root.find("comment_posting")
336
+ assert comment_posting is not None
337
+ assert comment_posting.text == AUDIT_COMMENT_POSTING_HEADLESS_TEXT
338
+ assert "MCP" not in (comment_posting.text or "")
339
+ assert "TaskCreate" not in (comment_posting.text or "")
340
+ assert "gh" in (comment_posting.text or "")
341
+ assert "post_audit_thread.py" in (comment_posting.text or "")
342
+
343
+ expected_outcome_path = (
344
+ worktree_path / OUTCOME_XML_TEMPLATE.format(number=pr_number, loop=loop)
345
+ ).as_posix()
346
+ output_format = root.find("output_format")
347
+ assert output_format is not None
348
+ assert output_format.text == AUDIT_OUTPUT_FORMAT_HEADLESS_TEMPLATE.format(
349
+ outcome_path=expected_outcome_path,
350
+ )
351
+ assert expected_outcome_path in (output_format.text or "")
352
+ assert "TaskCreate" in (output_format.text or "")
353
+ assert "MCP" in (output_format.text or "")
354
+
355
+
356
+ def test_main_headless_flavor_emits_outcome_path_on_stdout(
357
+ capsys: pytest.CaptureFixture[str],
358
+ ) -> None:
359
+ worktree_path = "/tmp/bugteam-pr-99/worktree"
360
+ exit_code = build_audit_prompt.main([
361
+ "--owner", "jl-cmd",
362
+ "--repo", "claude-dev-env",
363
+ "--pr-number", "99",
364
+ "--loop", "2",
365
+ "--head-ref", "feat/branch",
366
+ "--base-ref", "main",
367
+ "--worktree-path", worktree_path,
368
+ "--run-temp-dir", "/tmp/bugteam-pr-99",
369
+ "--flavor", AUDIT_PROMPT_FLAVOR_HEADLESS,
370
+ ])
371
+ assert exit_code == 0
372
+ captured = capsys.readouterr()
373
+ expected_outcome = (
374
+ Path(worktree_path)
375
+ / OUTCOME_XML_TEMPLATE.format(number=99, loop=2)
376
+ ).as_posix()
377
+ assert expected_outcome in captured.out
378
+ assert "post_audit_thread.py" in captured.out
379
+ assert "Do not post reviews" in captured.out
380
+ assert "add_comment_to_pending_review" not in captured.out