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
@@ -8,10 +8,23 @@ import sys
8
8
  from pathlib import Path
9
9
  from types import ModuleType
10
10
 
11
+ import pytest
12
+
11
13
  _SCRIPTS_DIR = Path(__file__).resolve().parent
12
14
  if str(_SCRIPTS_DIR) not in sys.path:
13
15
  sys.path.insert(0, str(_SCRIPTS_DIR))
14
16
 
17
+ from skills_pr_loop_constants.path_resolver_constants import (
18
+ FINDINGS_JSON_MUST_BE_LIST_MESSAGE,
19
+ FIX_COMMENT_POSTING_AGENT_TEXT,
20
+ FIX_COMMENT_POSTING_HEADLESS_TEXT,
21
+ FIX_OUTCOME_XML_TEMPLATE,
22
+ FIX_OUTPUT_FORMAT_AGENT_TEXT,
23
+ FIX_OUTPUT_FORMAT_HEADLESS_TEMPLATE,
24
+ FIX_PROMPT_FLAVOR_AGENT,
25
+ FIX_PROMPT_FLAVOR_HEADLESS,
26
+ )
27
+
15
28
 
16
29
  def _load_build_fix_prompt() -> ModuleType:
17
30
  module_path = _SCRIPTS_DIR / "build_fix_prompt.py"
@@ -27,12 +40,17 @@ def _load_build_fix_prompt() -> ModuleType:
27
40
  build_fix_prompt = _load_build_fix_prompt()
28
41
 
29
42
 
30
- def test_context_worktree_path_renders_with_forward_slashes(tmp_path: Path) -> None:
43
+ def _write_findings_json(tmp_path: Path) -> Path:
31
44
  findings_json_path = tmp_path / "findings.json"
32
45
  findings_json_path.write_text(
33
46
  json.dumps([{"severity": "P1", "file": "a.py", "line": 1}]),
34
47
  encoding="utf-8",
35
48
  )
49
+ return findings_json_path
50
+
51
+
52
+ def test_context_worktree_path_renders_with_forward_slashes(tmp_path: Path) -> None:
53
+ findings_json_path = _write_findings_json(tmp_path)
36
54
  root = build_fix_prompt.build_fix_prompt_xml(
37
55
  owner="jl-cmd",
38
56
  repo="claude-dev-env",
@@ -50,11 +68,7 @@ def test_context_worktree_path_renders_with_forward_slashes(tmp_path: Path) -> N
50
68
 
51
69
 
52
70
  def test_emit_fix_prompt_returns_xml_string_with_worktree_path(tmp_path: Path) -> None:
53
- findings_json_path = tmp_path / "findings.json"
54
- findings_json_path.write_text(
55
- json.dumps([{"severity": "P1", "file": "a.py", "line": 1}]),
56
- encoding="utf-8",
57
- )
71
+ findings_json_path = _write_findings_json(tmp_path)
58
72
  worktree_path = Path("C:/Users/example/AppData/Local/Temp/bugteam-pr-376/worktree")
59
73
  xml_text = build_fix_prompt.emit_fix_prompt(
60
74
  owner="jl-cmd",
@@ -70,3 +84,179 @@ def test_emit_fix_prompt_returns_xml_string_with_worktree_path(tmp_path: Path) -
70
84
  assert "C:/Users/example/AppData/Local/Temp/bugteam-pr-376/worktree" in xml_text
71
85
  assert 'role="fix"' in xml_text
72
86
  assert "<spawn_prompt" in xml_text
87
+
88
+
89
+ def test_parse_arguments_defaults_flavor_to_agent() -> None:
90
+ arguments = build_fix_prompt.parse_arguments([
91
+ "--owner", "jl-cmd",
92
+ "--repo", "claude-dev-env",
93
+ "--pr-number", "422",
94
+ "--loop", "1",
95
+ "--head-ref", "feat/branch",
96
+ "--base-ref", "main",
97
+ "--worktree-path", "/tmp/wt",
98
+ "--findings-json", "/tmp/findings.json",
99
+ ])
100
+ assert arguments.flavor == FIX_PROMPT_FLAVOR_AGENT
101
+
102
+
103
+ def test_parse_arguments_reads_headless_flavor() -> None:
104
+ arguments = build_fix_prompt.parse_arguments([
105
+ "--owner", "jl-cmd",
106
+ "--repo", "claude-dev-env",
107
+ "--pr-number", "422",
108
+ "--loop", "1",
109
+ "--head-ref", "feat/branch",
110
+ "--base-ref", "main",
111
+ "--worktree-path", "/tmp/wt",
112
+ "--findings-json", "/tmp/findings.json",
113
+ "--flavor", FIX_PROMPT_FLAVOR_HEADLESS,
114
+ ])
115
+ assert arguments.flavor == FIX_PROMPT_FLAVOR_HEADLESS
116
+
117
+
118
+ def test_agent_flavor_comment_posting_and_output_format(tmp_path: Path) -> None:
119
+ findings_json_path = _write_findings_json(tmp_path)
120
+ root = build_fix_prompt.build_fix_prompt_xml(
121
+ owner="jl-cmd",
122
+ repo="claude-dev-env",
123
+ pr_number=422,
124
+ loop=1,
125
+ head_ref="feat/branch",
126
+ base_ref="main",
127
+ worktree_path=Path("/tmp/bugteam-pr-422/worktree"),
128
+ findings_json_path=findings_json_path,
129
+ flavor=FIX_PROMPT_FLAVOR_AGENT,
130
+ )
131
+ comment_posting = root.find("comment_posting")
132
+ assert comment_posting is not None
133
+ assert comment_posting.text == FIX_COMMENT_POSTING_AGENT_TEXT
134
+ output_format = root.find("output_format")
135
+ assert output_format is not None
136
+ assert output_format.text == FIX_OUTPUT_FORMAT_AGENT_TEXT
137
+ execution = root.find("execution")
138
+ assert execution is not None
139
+ all_step_texts = [each_step.text or "" for each_step in execution]
140
+ assert any("commit" in each_step.lower() for each_step in all_step_texts)
141
+ assert any("push" in each_step.lower() for each_step in all_step_texts)
142
+
143
+
144
+ def test_headless_flavor_forbids_commit_push_and_mcp(tmp_path: Path) -> None:
145
+ findings_json_path = _write_findings_json(tmp_path)
146
+ worktree_path = Path("/tmp/bugteam-pr-422/worktree")
147
+ pr_number = 422
148
+ loop = 3
149
+ root = build_fix_prompt.build_fix_prompt_xml(
150
+ owner="jl-cmd",
151
+ repo="claude-dev-env",
152
+ pr_number=pr_number,
153
+ loop=loop,
154
+ head_ref="feat/branch",
155
+ base_ref="main",
156
+ worktree_path=worktree_path,
157
+ findings_json_path=findings_json_path,
158
+ flavor=FIX_PROMPT_FLAVOR_HEADLESS,
159
+ )
160
+ comment_posting = root.find("comment_posting")
161
+ assert comment_posting is not None
162
+ assert comment_posting.text == FIX_COMMENT_POSTING_HEADLESS_TEXT
163
+ assert "MCP" not in (comment_posting.text or "")
164
+ assert "TaskCreate" not in (comment_posting.text or "")
165
+ assert "gh" in (comment_posting.text or "")
166
+
167
+ expected_outcome_path = (
168
+ worktree_path
169
+ / FIX_OUTCOME_XML_TEMPLATE.format(number=pr_number, loop=loop)
170
+ ).as_posix()
171
+ output_format = root.find("output_format")
172
+ assert output_format is not None
173
+ assert output_format.text == FIX_OUTPUT_FORMAT_HEADLESS_TEMPLATE.format(
174
+ outcome_path=expected_outcome_path,
175
+ )
176
+ assert expected_outcome_path in (output_format.text or "")
177
+ assert "TaskCreate" in (output_format.text or "")
178
+ assert "MCP" in (output_format.text or "")
179
+
180
+ execution = root.find("execution")
181
+ assert execution is not None
182
+ all_step_texts = [each_step.text or "" for each_step in execution]
183
+ joined_steps = " ".join(all_step_texts).lower()
184
+ assert "do not stage, commit, or push" in joined_steps
185
+ assert "push the commit" not in joined_steps
186
+
187
+ constraints = root.find("constraints")
188
+ assert constraints is not None
189
+ all_constraint_texts = [each_constraint.text or "" for each_constraint in constraints]
190
+ joined_constraints = " ".join(all_constraint_texts).lower()
191
+ assert "do not stage, commit, or push" in joined_constraints
192
+
193
+
194
+ def test_main_headless_flavor_emits_outcome_path_on_stdout(
195
+ tmp_path: Path,
196
+ capsys: pytest.CaptureFixture[str],
197
+ ) -> None:
198
+ findings_json_path = _write_findings_json(tmp_path)
199
+ worktree_path = "/tmp/bugteam-pr-99/worktree"
200
+ exit_code = build_fix_prompt.main([
201
+ "--owner", "jl-cmd",
202
+ "--repo", "claude-dev-env",
203
+ "--pr-number", "99",
204
+ "--loop", "2",
205
+ "--head-ref", "feat/branch",
206
+ "--base-ref", "main",
207
+ "--worktree-path", worktree_path,
208
+ "--findings-json", str(findings_json_path),
209
+ "--flavor", FIX_PROMPT_FLAVOR_HEADLESS,
210
+ ])
211
+ assert exit_code == 0
212
+ captured = capsys.readouterr()
213
+ expected_outcome = (
214
+ Path(worktree_path)
215
+ / FIX_OUTCOME_XML_TEMPLATE.format(number=99, loop=2)
216
+ ).as_posix()
217
+ assert expected_outcome in captured.out
218
+ assert "Do not post replies" in captured.out
219
+ assert "Do not stage, commit, or push" in captured.out
220
+ assert "add_reply_to_pull_request_comment" not in captured.out
221
+
222
+
223
+ def test_main_rejects_object_root_findings_json(
224
+ tmp_path: Path,
225
+ capsys: pytest.CaptureFixture[str],
226
+ ) -> None:
227
+ findings_json_path = tmp_path / "findings.json"
228
+ findings_json_path.write_text(
229
+ json.dumps({"findings": [{"severity": "P1", "file": "a.py"}]}),
230
+ encoding="utf-8",
231
+ )
232
+ exit_code = build_fix_prompt.main([
233
+ "--owner", "jl-cmd",
234
+ "--repo", "claude-dev-env",
235
+ "--pr-number", "99",
236
+ "--loop", "1",
237
+ "--head-ref", "feat/branch",
238
+ "--base-ref", "main",
239
+ "--worktree-path", "/tmp/wt",
240
+ "--findings-json", str(findings_json_path),
241
+ ])
242
+ assert exit_code == 1
243
+ captured = capsys.readouterr()
244
+ expected_message = FINDINGS_JSON_MUST_BE_LIST_MESSAGE % "dict"
245
+ assert expected_message in captured.err
246
+ assert captured.out == ""
247
+
248
+
249
+ def test_build_fix_prompt_xml_rejects_null_root(tmp_path: Path) -> None:
250
+ findings_json_path = tmp_path / "findings.json"
251
+ findings_json_path.write_text("null", encoding="utf-8")
252
+ with pytest.raises(TypeError, match="JSON array"):
253
+ build_fix_prompt.build_fix_prompt_xml(
254
+ owner="jl-cmd",
255
+ repo="claude-dev-env",
256
+ pr_number=1,
257
+ loop=1,
258
+ head_ref="feat/branch",
259
+ base_ref="main",
260
+ worktree_path=Path("/tmp/wt"),
261
+ findings_json_path=findings_json_path,
262
+ )
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Trigger:** `/autoconverge`, "autoconverge this PR", "converge this PR in one run", "run the converge workflow", "drive the PR to ready autonomously".
4
4
 
5
- Drives one draft PR to convergence in a single autonomous workflow run. Each round runs a deterministic static sweep first, then a code-review pass, a bug-audit (with its adversarial second pass), and a self-review parity pass in parallel on the same HEAD, deduplicates findings, applies every fix in one commit, and re-verifies. Once the internal lenses are clean, Cursor Bugbot and Copilot run as terminal confirmation gates, and the run marks the PR ready on convergence. State lives in the workflow journal; no `ScheduleWakeup` ticks.
5
+ Drives one draft PR to convergence in a single autonomous workflow run. Each round runs a deterministic static sweep first, then a code-review pass, a bug-audit (with its adversarial second pass), and a self-review parity pass in parallel on the same HEAD, deduplicates findings, applies every fix in one commit, and re-verifies. Once the internal lenses are clean, Cursor Bugbot, Copilot, and Codex run as terminal confirmation gates, and the run marks the PR ready on convergence. State lives in the workflow journal; no `ScheduleWakeup` ticks.
6
6
 
7
7
  ## Subdirectories
8
8
 
@@ -16,11 +16,11 @@ Drives one draft PR to convergence in a single autonomous workflow run. Each rou
16
16
  | File | Role |
17
17
  |---|---|
18
18
  | `SKILL.md` | Full entry-point protocol: pre-flight steps, worktree setup, `Workflow` call, budget-aware round boundaries, teardown, and the per-round convergence loop summary. |
19
- | `workflow/converge.mjs` | Main convergence workflow. Runs a static sweep then the internal lenses each round, applies fixes, runs Bugbot and Copilot as terminal gates, checks convergence, and marks the PR ready. |
19
+ | `workflow/converge.mjs` | Main convergence workflow. Runs a static sweep then the internal lenses each round, applies fixes, runs Bugbot, Copilot, and Codex as terminal gates, checks convergence, and marks the PR ready. |
20
20
  | `workflow/aggregate_runs.py` | Merges every autoconverge journal for a PR into one deduped journal. |
21
21
  | `workflow/convergence_summary.py` | Builds the convergence-summary agent prompt over the merged findings. |
22
22
  | `workflow/render_report.py` | Builds the closing HTML report from the merged journal and summary. |
23
- | `reference/convergence.md` | Round shape: the static sweep, the three parallel internal lenses, deduplication, fix commit, the terminal Bugbot and Copilot gates, and the ready definition. |
23
+ | `reference/convergence.md` | Round shape: the static sweep, the three parallel internal lenses, deduplication, fix commit, the terminal Bugbot, Copilot, and Codex gates, and the ready definition. |
24
24
  | `reference/stop-conditions.md` | Every way the run ends short of ready, including the budget stop. |
25
25
  | `reference/gotchas.md` | Hard-won failure lessons. |
26
26
  | `reference/closing-report.md` | The closing HTML report: data source, build steps, publishing. |
@@ -3,7 +3,7 @@ name: autoconverge
3
3
  description: >-
4
4
  Drives one draft PR to convergence in one autonomous run: a deterministic static
5
5
  sweep, then code review, bug audit, and self-review on one HEAD, all fixes in one
6
- commit, then Bugbot and Copilot as terminal confirmation gates before ready.
6
+ commit, then Bugbot, Copilot, and Codex as terminal confirmation gates before ready.
7
7
  Use when the user says '/autoconverge', 'autoconverge this PR', 'converge this
8
8
  PR in one run', 'run the converge workflow', or 'drive the PR to ready
9
9
  autonomously'.
@@ -45,6 +45,12 @@ Scan the tool list at the top of this conversation for the literal string
45
45
  aborting` and stop. The workflow also needs the `gh` CLI authenticated for the
46
46
  PR's owner.
47
47
 
48
+ ## Review-lens boundary
49
+
50
+ The code-review lens boundary (workflow agent versus the built-in `/code-review`
51
+ command) is defined on the **Code-review lens** bullet in
52
+ [`reference/convergence.md`](reference/convergence.md).
53
+
48
54
  ## Transport check (before any GitHub step)
49
55
 
50
56
  Run `command -v gh`; when it succeeds, run `gh auth status`; once the PR
@@ -290,8 +296,8 @@ ready again — then run the checkpoints.
290
296
  [`reference/convergence.md`](reference/convergence.md) defines the whole loop:
291
297
  the merge-conflict pre-flight, the reuse pass and its three landing criteria,
292
298
  the round shape (static sweep, the three parallel reading lenses, dedup, the
293
- one-commit fix flow, the standards-deferral path), the terminal Bugbot and
294
- Copilot gates, the per-role model tiers, the full-diff rule, and the exact
299
+ one-commit fix flow, the standards-deferral path), the terminal Bugbot, Copilot,
300
+ and Codex gates, the per-role model tiers, the full-diff rule, and the exact
295
301
  ready definition `check_convergence.py` enforces.
296
302
  [`reference/stop-conditions.md`](reference/stop-conditions.md) lists every way
297
303
  the run ends short of ready. Hard-won failure lessons live in
@@ -6,8 +6,8 @@ Reference documentation for the `autoconverge` skill. The `converge.mjs` workflo
6
6
 
7
7
  | File | Role |
8
8
  |---|---|
9
- | `convergence.md` | Round shape: the static sweep, the three parallel internal lenses (code-review, bug-audit, self-review), deduplication, the fix commit step, the terminal Bugbot and Copilot gates, and the definition of a clean convergence. |
10
- | `stop-conditions.md` | Every condition that ends the run short of ready: budget cap, iteration cap, blocker exit, static-sweep stall, Bugbot and Copilot bypass. |
9
+ | `convergence.md` | Round shape: the static sweep, the three parallel internal lenses (code-review, bug-audit, self-review), deduplication, the fix commit step, the terminal Bugbot, Copilot, and Codex gates, and the definition of a clean convergence. |
10
+ | `stop-conditions.md` | Every condition that ends the run short of ready: budget cap, iteration cap, blocker exit, static-sweep stall, Bugbot, Copilot, and Codex bypass. |
11
11
  | `gotchas.md` | Hard-won lessons from failed runs: PR title validation, conflicting PRs, worktree branch lock, resumed sessions rerooting, and minter issues. |
12
12
  | `closing-report.md` | The closing HTML convergence report the teardown step builds and publishes: data source, build steps, publishing. |
13
13
  | `multi-pr.md` | The several-PRs path: per-PR worktrees, the `converge_multi.mjs` launch, per-PR teardown. |
@@ -40,7 +40,7 @@ landed.
40
40
 
41
41
  ## The round loop
42
42
 
43
- The workflow holds four states and moves between them until the PR is ready or
43
+ The workflow holds five states and moves between them until the PR is ready or
44
44
  a blocker ends the run. A single iteration counter increments on every pass
45
45
  through any phase and caps the whole run at 20 loop iterations; the round counter
46
46
  tracks CONVERGE passes only and is never the cap. The internal lenses drive the
@@ -63,8 +63,14 @@ confirmation gates that are expected to return zero.
63
63
  `origin/main...HEAD` diff. Each lens receives the preflight's changed-file
64
64
  list and diffstat and reads only the files it needs from that list rather than
65
65
  re-deriving the diff; each lens forms its own review judgment.
66
- - **Code-review lens** — a correctness-focused review pass (`code-quality-agent`)
67
- that reports findings without editing.
66
+ - **Code-review lens** — a correctness-focused review pass (`code-quality-agent`),
67
+ report-only workflow agent see runCodeReviewLens in workflow/converge.mjs for its configuration.
68
+ The built-in `/code-review` command is a separate surface outside this
69
+ workflow path. Autoconverge runs only where the Workflow tool exists (the
70
+ skill hub Requirements sentence `autoconverge requires the Workflow tool`).
71
+ Harness portability of that command is a pr-converge concern: pr-converge's
72
+ CODE_REVIEW phase runs Claude Code's built-in `/code-review high --fix`
73
+ ([`../../pr-converge/reference/per-tick.md`](../../pr-converge/reference/per-tick.md)).
68
74
  - **Bug-audit lens** — the bug-audit (`code-quality-agent`) applying the
69
75
  shared A–P rubric from `_shared/pr-loop/audit-contract.md`, then its
70
76
  adversarial second pass, and the doc-parity, test-assertion, and
@@ -118,11 +124,26 @@ any bot threads with a deferral note, and reports the deferral in
118
124
  - Request a Copilot review on HEAD (skipping a duplicate request), then poll up
119
125
  to the configured cap, 360 seconds apart.
120
126
  - Copilot findings → fix them and return to CONVERGE on the new HEAD.
121
- - Copilot clean or approved → move to the convergence check.
127
+ - Copilot clean or approved → move to the Codex gate.
122
128
  - Copilot down or out of quota (an out-of-usage notice, or no review after the
123
- configured cap) → log a notice and move to the convergence check with the Copilot gate
129
+ configured cap) → log a notice and move to the Codex gate with the Copilot gate
124
130
  bypassed.
125
131
 
132
+ **CODEX** gate (conditional-required terminal confirmation):
133
+
134
+ - Runs after Bugbot and Copilot. Honors `reviews_disabled.py --reviewer codex`,
135
+ the weekly usage probe via `is_codex_review_required` (shared threshold
136
+ constant — no inline percent), and the wrapper's `codex_down` class.
137
+ - Opt-out token or `codex_down` → set `codexDown`, no stamp, move to the
138
+ convergence check with `--codex-down`.
139
+ - Usage at/below threshold or null → skip with no stamp; the machine checklist
140
+ applies the same rule.
141
+ - Above threshold → run the codex-review wrapper against the PR base branch.
142
+ Non–code-standard findings → fix and return to CONVERGE.
143
+ Standards-only findings → defer a follow-up, stamp `codexCleanAt`, and move to the
144
+ convergence check (no fix push).
145
+ Clean → stamp `codexCleanAt` and pass `--codex-clean-at` into the convergence check.
146
+
126
147
  **Convergence check**:
127
148
 
128
149
  - One agent runs `check_convergence.py` and, on a full pass, marks the PR ready
@@ -140,8 +161,8 @@ the strongest model only where judgment is dense:
140
161
  - **opus** — the reading lenses (code-review, bug-audit, self-review,
141
162
  reuse), the terminal Bugbot gate, and the code-editing steps that fix findings
142
163
  (fix-edit, conflict-edit, repair-edit, standards-edit).
143
- - **haiku** — the mechanical probes: preflight, the Copilot gate, the
144
- CLEAN-audit post, and the convergence check.
164
+ - **haiku** — the mechanical probes: preflight, the Copilot gate, the Codex
165
+ gate, the CLEAN-audit post, and the convergence check.
145
166
 
146
167
  ## Full-diff rule
147
168
 
@@ -170,7 +191,8 @@ checklist.
170
191
  ## Audit-trail design
171
192
 
172
193
  Bugbot and Copilot post their own review threads, which the fix lens replies to
173
- and resolves. The bug-audit lens keeps its findings in memory across the round
174
- and posts only the terminal CLEAN bugteam review once every lens is clean on a
175
- stable HEAD that single artifact is what gate 3 reads. This keeps thread churn
176
- to the threads the bots raise themselves.
194
+ and resolves. Codex findings are local to the gate (no GitHub review thread) and
195
+ enter the same fix path with `replyToCommentId: null`. The bug-audit lens keeps
196
+ its findings in memory across the round and posts only the terminal CLEAN bugteam
197
+ review once every lens is clean on a stable HEAD — that single artifact is what
198
+ gate 3 reads. This keeps thread churn to the threads the bots raise themselves.
@@ -34,7 +34,7 @@ skill still runs teardown (revoke permissions, final report).
34
34
  stalled HEAD. An all-stale round that makes no commit but resolves every
35
35
  finding thread (`resolvedWithoutCommit: true` with at least one thread-bearing
36
36
  finding) is not a stall — the run re-converges on the unchanged HEAD and
37
- reaches the terminal Bugbot, Copilot, and convergence gates.
37
+ reaches the terminal Bugbot, Copilot, Codex, and convergence gates.
38
38
  - **Static-sweep stalled** — the deterministic static sweep (`code_rules_gate.py
39
39
  --base origin/main`, `ruff`, `mypy`, stem-matched `pytest`) raises gate or test
40
40
  failures the fixer cannot clear on the current HEAD, so HEAD does not move. The
@@ -80,10 +80,21 @@ Each reviewer-down condition below skips its own convergence-check gate. The fla
80
80
  - **Copilot down or out of quota** — when Copilot posts an out-of-usage notice on
81
81
  the current HEAD (the user who requested the review reached their quota limit)
82
82
  rather than a code review, or surfaces no review at all after the configured cap, the
83
- Copilot gate returns `down: true`. The run logs a notice, runs the convergence
84
- check with `--copilot-down` (the Copilot review gate and the
85
- pending-requested-reviews gate bypassed), and marks the PR ready. `copilotNote`
86
- records the bypass for the final report.
83
+ Copilot gate returns `down: true`. The run logs a notice, sets `copilotDown`,
84
+ and proceeds to the Codex gate. The later convergence check receives
85
+ `--copilot-down` (the Copilot review gate and the pending-requested-reviews
86
+ gate bypassed), then mark-ready. `copilotNote` records the bypass for the
87
+ final report.
88
+ - **Terminal Codex gate skipped or down** — the conditional-required Codex gate
89
+ runs after Bugbot and Copilot. When `CLAUDE_REVIEWS_DISABLED` lists `codex`
90
+ (`reviews_disabled.py --reviewer codex` exit 0), the gate sets `codexDown` and
91
+ advances with no review. When the weekly usage probe reports `percent_left`
92
+ null or at/below the shared threshold (`is_codex_review_required` false), the
93
+ gate skips without a stamp; the convergence check applies the same rule. When
94
+ the wrapper classifies `codex_down`, the gate sets `codexDown` and advances.
95
+ A clean required run stamps `codexCleanAt` for `--codex-clean-at`. Non-code-standard
96
+ findings re-enter CONVERGE through the existing fix path. Standards-only findings
97
+ defer a follow-up, stamp `codexCleanAt`, and advance to FINALIZE (no fix push).
87
98
  - **A lens agent dies** — when one parallel reading lens returns null (a terminal
88
99
  agent failure), the round proceeds on the surviving lenses. A real defect it
89
100
  would have caught surfaces in a later round or at the convergence check. A dead
@@ -6,7 +6,7 @@ Workflow scripts and report utilities for the `autoconverge` skill.
6
6
 
7
7
  | File | Role |
8
8
  |---|---|
9
- | `converge.mjs` | Main convergence workflow script. Each round runs a deterministic static sweep first, then code-review, bug-audit, and self-review lenses in parallel, deduplicates findings, applies fixes, and pushes; Bugbot and Copilot then run as terminal confirmation gates before it checks convergence and marks the PR ready. Runs via the `Workflow` tool — not directly with Node. |
9
+ | `converge.mjs` | Main convergence workflow script. Each round runs a deterministic static sweep first, then code-review, bug-audit, and self-review lenses in parallel, deduplicates findings, applies fixes, and pushes; Bugbot, Copilot, and Codex then run as terminal confirmation gates before it checks convergence and marks the PR ready. Runs via the `Workflow` tool — not directly with Node. |
10
10
  | `aggregate_runs.py` | Merges every autoconverge journal for a given PR (matched by run id) into one merged journal. Prints a JSON line with `mergedJournal`, `roundCount`, `finalSha`, and `findingCount`. |
11
11
  | `convergence_summary.py` | Builds the convergence-summary agent prompt over the merged journal's findings. The teardown step spawns a `general-purpose` subagent on this prompt. |
12
12
  | `render_report.py` | Builds the closing HTML insights report. Takes `--journal`, `--summary-file`, `--out`, `--pr`, `--final-sha`, and `--rounds`. Writes the HTML to `--out` and prints the output path on stdout. |
@@ -14,6 +14,7 @@ Workflow scripts and report utilities for the `autoconverge` skill.
14
14
  | `converge.contract.test.mjs` | Contract tests for `converge.mjs` — verify the workflow interface and step ordering. |
15
15
  | `converge.clean-audit.test.mjs` | Tests the clean-audit path (all lenses clean on first round). |
16
16
  | `converge.copilot-gate.test.mjs` | Tests Copilot gate behavior (bypass on quota exhaustion). |
17
+ | `converge.codex-gate.test.mjs` | Tests Codex gate classification (required+clean, skip-token, skip-usage, codex_down) and phase wiring. |
17
18
  | `converge.precatch.test.mjs` | Tests the pre-catch stage: static-sweep ordering and gate coverage, the adversarial second pass, the self-review lens, the terminal Bugbot gate, the read-only/edit preamble split, and the commit-recovery cap. |
18
19
  | `converge.fix-progress.test.mjs` | Tests fix-progress tracking across rounds. |
19
20
  | `converge.fix-recovery.test.mjs` | Tests recovery when a fix commit fails. |
@@ -563,7 +563,7 @@ test('the standards-only call site relays lens provenance, the deferred standard
563
563
 
564
564
  test('every standardsDeferralNote call site passes the shared deferral state with no legacy argument', () => {
565
565
  const noteCalls = convergeSource.match(/standardsNote = standardsDeferralNote\([^\n]*/g) || [];
566
- assert.equal(noteCalls.length, 3);
566
+ assert.equal(noteCalls.length, 4);
567
567
  assert.equal(
568
568
  noteCalls.filter((eachCall) => /standardsDeferralNote\(findings\.length, standardsDeferral\)/.test(eachCall)).length,
569
569
  1,
@@ -579,6 +579,11 @@ test('every standardsDeferralNote call site passes the shared deferral state wit
579
579
  1,
580
580
  'expected the copilot call site to pass buildStandardsDeferral() inline',
581
581
  );
582
+ assert.equal(
583
+ noteCalls.filter((eachCall) => /standardsDeferralNote\(codexOutcome\.findings\.length, buildStandardsDeferral\(\)\)/.test(eachCall)).length,
584
+ 1,
585
+ 'expected the codex call site to pass buildStandardsDeferral() inline',
586
+ );
582
587
  for (const eachCall of noteCalls) {
583
588
  assert.doesNotMatch(eachCall, /hardeningPrOpened|standardsOutcome|\b(?:true|false)\b/);
584
589
  }
@@ -642,7 +647,7 @@ test('resolveCleanAuditNote records a bypass note on a refused post and resets t
642
647
  test('the finalize call derives bugteamPostBlocked from a set cleanAuditNote', () => {
643
648
  assert.match(
644
649
  convergeSource,
645
- /runConvergenceCheck\(\{ head, bugbotDown, copilotDown, bugteamPostBlocked: cleanAuditNote !== null \}\)/,
650
+ /runConvergenceCheck\(\{ head, bugbotDown, copilotDown, codexDown, codexCleanAt, bugteamPostBlocked: cleanAuditNote !== null \}\)/,
646
651
  );
647
652
  });
648
653