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
@@ -0,0 +1,300 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import { readFileSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, join } from 'node:path';
6
+
7
+ const workflowDirectory = dirname(fileURLToPath(import.meta.url));
8
+ const convergeSource = readFileSync(join(workflowDirectory, 'converge.mjs'), 'utf8');
9
+ const skillSource = readFileSync(join(workflowDirectory, '..', 'SKILL.md'), 'utf8');
10
+ const stopConditionsSource = readFileSync(
11
+ join(workflowDirectory, '..', 'reference', 'stop-conditions.md'),
12
+ 'utf8',
13
+ );
14
+
15
+ function functionBody(functionName) {
16
+ const functionStart = convergeSource.indexOf(`function ${functionName}(`);
17
+ assert.notEqual(functionStart, -1, `expected ${functionName} to exist`);
18
+ const nextFunctionStart = convergeSource.indexOf('\nfunction ', functionStart + 1);
19
+ const functionEnd = nextFunctionStart === -1 ? convergeSource.length : nextFunctionStart;
20
+ return convergeSource.slice(functionStart, functionEnd);
21
+ }
22
+
23
+ const productionModule = new Function(
24
+ `${functionBody('classifyCodexGateOutcome')}\n` +
25
+ 'return { classifyCodexGateOutcome };',
26
+ )();
27
+ const { classifyCodexGateOutcome } = productionModule;
28
+
29
+ function codexResult(overrides) {
30
+ return {
31
+ sha: 'abcdef0',
32
+ clean: false,
33
+ down: false,
34
+ skipped: false,
35
+ skipReason: '',
36
+ findings: [],
37
+ ...overrides,
38
+ };
39
+ }
40
+
41
+ function codexFinding(overrides) {
42
+ return {
43
+ file: 'a.py',
44
+ line: 1,
45
+ severity: 'P1',
46
+ category: 'bug',
47
+ title: 't',
48
+ detail: 'd',
49
+ replyToCommentId: null,
50
+ ...overrides,
51
+ };
52
+ }
53
+
54
+ test('a required clean Codex result routes to clean so the run stamps codex-clean HEAD', () => {
55
+ const outcome = classifyCodexGateOutcome(codexResult({ clean: true }));
56
+ assert.equal(outcome.kind, 'clean');
57
+ });
58
+
59
+ test('an opt-out token skip routes to skip-token', () => {
60
+ const outcome = classifyCodexGateOutcome(
61
+ codexResult({ clean: true, down: true, skipped: true, skipReason: 'token' }),
62
+ );
63
+ assert.equal(outcome.kind, 'skip-token');
64
+ });
65
+
66
+ test('a usage-threshold skip routes to skip-usage', () => {
67
+ const outcome = classifyCodexGateOutcome(
68
+ codexResult({ clean: true, down: false, skipped: true, skipReason: 'usage' }),
69
+ );
70
+ assert.equal(outcome.kind, 'skip-usage');
71
+ });
72
+
73
+ test('a codex_down result routes to down', () => {
74
+ const outcome = classifyCodexGateOutcome(codexResult({ clean: false, down: true }));
75
+ assert.equal(outcome.kind, 'down');
76
+ });
77
+
78
+ test('Codex findings route to a fix when the review completed with findings', () => {
79
+ const outcome = classifyCodexGateOutcome(
80
+ codexResult({
81
+ findings: [codexFinding()],
82
+ }),
83
+ );
84
+ assert.equal(outcome.kind, 'fix');
85
+ assert.equal(outcome.findings.length, 1);
86
+ });
87
+
88
+ test('a dead Codex gate agent retries rather than passing', () => {
89
+ assert.equal(classifyCodexGateOutcome(null).kind, 'retry');
90
+ });
91
+
92
+ test('a reachable Codex gate with no findings and no clean verdict retries', () => {
93
+ const outcome = classifyCodexGateOutcome(codexResult({ clean: false, down: false }));
94
+ assert.equal(outcome.kind, 'retry');
95
+ });
96
+
97
+ test('skipped with an unknown skipReason retries rather than silently advancing', () => {
98
+ const outcome = classifyCodexGateOutcome(
99
+ codexResult({ skipped: true, skipReason: 'mystery' }),
100
+ );
101
+ assert.equal(outcome.kind, 'retry');
102
+ });
103
+
104
+ test('CODEX_SCHEMA requires skipped and skipReason drawn from the skip-reason constant', () => {
105
+ const schemaStart = convergeSource.indexOf('const CODEX_SCHEMA =');
106
+ const schemaEnd = convergeSource.indexOf('const COPILOT_VERIFY_VERDICTS =');
107
+ assert.notEqual(schemaStart, -1, 'expected CODEX_SCHEMA to exist');
108
+ const schemaSource = convergeSource.slice(schemaStart, schemaEnd);
109
+ assert.match(schemaSource, /skipped:\s*\{\s*type:\s*'boolean'/);
110
+ assert.match(schemaSource, /enum:\s*CODEX_SKIP_REASONS/);
111
+ assert.match(schemaSource, /required:\s*\[[^\]]*'skipped'[^\]]*\]/);
112
+ assert.match(schemaSource, /required:\s*\[[^\]]*'skipReason'[^\]]*\]/);
113
+ });
114
+
115
+ test('the Codex gate prompt honors reviews_disabled.py with only --reviewer codex', () => {
116
+ const codexPrompt = functionBody('runCodexGate');
117
+ const reviewsDisabledIndex = codexPrompt.indexOf('reviews_disabled.py');
118
+ assert.notEqual(reviewsDisabledIndex, -1, 'expected reviews_disabled.py invocation');
119
+ const invocationLineEnd = codexPrompt.indexOf('\\n', reviewsDisabledIndex);
120
+ const invocationLine = codexPrompt.slice(reviewsDisabledIndex, invocationLineEnd);
121
+ assert.match(invocationLine, /--reviewer codex/);
122
+ assert.doesNotMatch(invocationLine, /--owner|--repo/);
123
+ });
124
+
125
+ test('the Codex gate prompt decides required vs skip only through is_codex_review_required', () => {
126
+ const codexPrompt = functionBody('runCodexGate');
127
+ assert.match(codexPrompt, /codex_usage_probe\.py/, 'expected the usage probe CLI');
128
+ assert.match(
129
+ codexPrompt,
130
+ /is_codex_review_required/,
131
+ 'expected the shared probe helper rather than an inline percent threshold',
132
+ );
133
+ assert.doesNotMatch(
134
+ codexPrompt,
135
+ /WEEKLY_USAGE_GATE_THRESHOLD_PERCENT|\b10\s*%|\bpercent_left\s*[><=]/,
136
+ 'expected no duplicate threshold literal or inline comparison in the gate prompt',
137
+ );
138
+ });
139
+
140
+ test('the Codex gate prompt treats a non-zero usage-probe exit as codex_down not usage skip', () => {
141
+ const codexPrompt = functionBody('runCodexGate');
142
+ assert.match(
143
+ codexPrompt,
144
+ /Non-zero exit means the Codex CLI or probe is broken/,
145
+ 'expected an explicit non-zero probe exit branch',
146
+ );
147
+ assert.match(
148
+ codexPrompt,
149
+ /Never map a failed probe to skipReason:'usage'/,
150
+ 'expected failed probe to be barred from the usage-skip path',
151
+ );
152
+ assert.match(
153
+ codexPrompt,
154
+ /clean:false, down:true, skipped:false, skipReason:''/,
155
+ 'expected failed probe to return the codex_down schema shape',
156
+ );
157
+ });
158
+
159
+ test('the Codex gate prompt sys.path insert uses CONFIG.codexScripts not a literal $HOME token', () => {
160
+ const codexPrompt = functionBody('runCodexGate');
161
+ assert.match(
162
+ codexPrompt,
163
+ /sys\.path\.insert\(0, r'\$\{CONFIG\.codexScripts\}'\)/,
164
+ 'expected sys.path.insert to interpolate CONFIG.codexScripts (home-expanded at CONFIG build)',
165
+ );
166
+ assert.doesNotMatch(
167
+ codexPrompt,
168
+ /sys\.path\.insert\(0, r'\$HOME/,
169
+ 'expected no unexpanded $HOME inside the Python raw-string path',
170
+ );
171
+ });
172
+
173
+ test('the Codex gate prompt runs the review wrapper against the PR base branch', () => {
174
+ const codexPrompt = functionBody('runCodexGate');
175
+ assert.match(codexPrompt, /run_codex_review/, 'expected the codex-review wrapper');
176
+ assert.match(codexPrompt, /base_branch/, 'expected the wrapper to receive the PR base');
177
+ assert.match(codexPrompt, /\.base\.ref/, 'expected the agent to resolve the PR base ref');
178
+ assert.match(codexPrompt, /parse_codex_findings/, 'expected findings to come from the shared parser');
179
+ });
180
+
181
+ test('the CODEX phase stamps codexCleanAt on clean and clears it on skip/down', () => {
182
+ const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {");
183
+ const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", codexPhaseStart);
184
+ assert.notEqual(codexPhaseStart, -1, 'expected a CODEX phase block');
185
+ assert.notEqual(finalizePhaseStart, -1, 'expected FINALIZE after CODEX');
186
+ const codexPhase = convergeSource.slice(codexPhaseStart, finalizePhaseStart);
187
+ assert.match(codexPhase, /await runCodexGate\(head\)/);
188
+ assert.match(codexPhase, /classifyCodexGateOutcome\(codex\)/);
189
+ assert.match(codexPhase, /codexCleanAt = head/, 'expected a clean pass to stamp the HEAD');
190
+ assert.match(codexPhase, /skip-token[\s\S]*codexDown = true[\s\S]*codexCleanAt = null/);
191
+ assert.match(codexPhase, /skip-usage[\s\S]*codexDown = false[\s\S]*codexCleanAt = null/);
192
+ assert.match(codexPhase, /kind === 'down'[\s\S]*codexDown = true[\s\S]*codexCleanAt = null/);
193
+ });
194
+
195
+ test('the CODEX phase routes findings through applyFixes and re-enters CONVERGE', () => {
196
+ const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {");
197
+ const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", codexPhaseStart);
198
+ const codexPhase = convergeSource.slice(codexPhaseStart, finalizePhaseStart);
199
+ assert.match(codexPhase, /applyFixes\(head, codexOutcome\.findings, 'codex'\)/);
200
+ assert.match(codexPhase, /phase = 'CONVERGE'/);
201
+ assert.match(codexPhase, /head = null/);
202
+ });
203
+
204
+ test('the COPILOT phase advances to CODEX rather than FINALIZE on a clean or bypass path', () => {
205
+ const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
206
+ const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {", copilotPhaseStart);
207
+ assert.notEqual(copilotPhaseStart, -1, 'expected a COPILOT phase block');
208
+ assert.notEqual(codexPhaseStart, -1, 'expected a CODEX phase after COPILOT');
209
+ const copilotPhase = convergeSource.slice(copilotPhaseStart, codexPhaseStart);
210
+ assert.match(copilotPhase, /phase = 'CODEX'/);
211
+ assert.doesNotMatch(copilotPhase, /phase = 'FINALIZE'/);
212
+ });
213
+
214
+ test('the merged FINALIZE check receives codexDown and codexCleanAt and passes matching flags', () => {
215
+ const finalizeStart = convergeSource.indexOf("if (phase === 'FINALIZE') {");
216
+ assert.notEqual(finalizeStart, -1, 'expected a FINALIZE phase block');
217
+ const checkCall = convergeSource.indexOf('runConvergenceCheck(', finalizeStart);
218
+ assert.notEqual(checkCall, -1, 'expected the FINALIZE phase to call runConvergenceCheck');
219
+ const checkLine = convergeSource.slice(checkCall, convergeSource.indexOf('\n', checkCall));
220
+ assert.match(checkLine, /codexDown/);
221
+ assert.match(checkLine, /codexCleanAt/);
222
+ const checkBody = functionBody('runConvergenceCheck');
223
+ assert.match(checkBody, /context\.codexDown \? ' --codex-down' : ''/);
224
+ assert.match(checkBody, /context\.codexCleanAt \? ` --codex-clean-at \$\{context\.codexCleanAt\}` : ''/);
225
+ assert.match(checkBody, /if \(context\.codexDown\) reviewerOptOutTokens\.push\('codex'\)/);
226
+ });
227
+
228
+ test('runConvergenceCheck tells the finalize agent to persist codex_clean_at for the flagless mark-ready re-check', () => {
229
+ const checkBody = functionBody('runConvergenceCheck');
230
+ assert.match(
231
+ checkBody,
232
+ /codexCleanAtNote/,
233
+ 'expected a dedicated note when the Codex gate stamped clean on HEAD',
234
+ );
235
+ assert.match(
236
+ checkBody,
237
+ /pr-converge-state\.json/,
238
+ 'expected the note to name the job-dir state file the mark-ready blocker re-reads',
239
+ );
240
+ assert.match(
241
+ checkBody,
242
+ /codex_clean_at/,
243
+ 'expected the note to write the codex_clean_at key the flagless re-check resolves',
244
+ );
245
+ assert.match(
246
+ checkBody,
247
+ /CLAUDE_JOB_DIR/,
248
+ 'expected the note to locate the state file under CLAUDE_JOB_DIR',
249
+ );
250
+ assert.match(
251
+ checkBody,
252
+ /needsCodexCleanExportFallback/,
253
+ 'expected a single-export fallback path when CLAUDE_JOB_DIR is unset',
254
+ );
255
+ assert.match(
256
+ checkBody,
257
+ /Emit exactly one export/,
258
+ 'expected the opt-out instruction to forbid a second overwriting export',
259
+ );
260
+ assert.doesNotMatch(
261
+ checkBody,
262
+ /export CLAUDE_REVIEWS_DISABLED="codex"/,
263
+ 'expected no second solo codex export that would overwrite other tokens',
264
+ );
265
+ });
266
+
267
+ test('CONFIG expands home for the codex-review scripts directory', () => {
268
+ assert.match(
269
+ convergeSource,
270
+ /const homeDirectory = \(process\.env\.HOME \|\| process\.env\.USERPROFILE/,
271
+ 'expected home to be resolved from process.env before CONFIG path use',
272
+ );
273
+ assert.match(
274
+ convergeSource,
275
+ /codexScripts:\s*`\$\{homeDirectory\}\/\.claude\/skills\/codex-review\/scripts`/,
276
+ 'expected codexScripts to expand homeDirectory rather than embed $HOME',
277
+ );
278
+ assert.doesNotMatch(
279
+ convergeSource,
280
+ /codexScripts:\s*'\$HOME\//,
281
+ 'expected no literal $HOME token in codexScripts (Python raw strings do not expand it)',
282
+ );
283
+ });
284
+
285
+ test('meta lists the Codex gate phase between Copilot and Finalize', () => {
286
+ const copilotPhaseIndex = convergeSource.indexOf("title: 'Copilot gate'");
287
+ const codexPhaseIndex = convergeSource.indexOf("title: 'Codex gate'");
288
+ const finalizePhaseIndex = convergeSource.indexOf("title: 'Finalize'");
289
+ assert.notEqual(codexPhaseIndex, -1, 'expected a Codex gate phase in meta');
290
+ assert.ok(copilotPhaseIndex < codexPhaseIndex && codexPhaseIndex < finalizePhaseIndex);
291
+ });
292
+
293
+ test('SKILL.md names Codex among the terminal confirmation gates', () => {
294
+ assert.match(skillSource, /Codex/i);
295
+ });
296
+
297
+ test('stop-conditions.md documents the Codex gate skip paths as non-blockers', () => {
298
+ assert.match(stopConditionsSource, /Codex/i);
299
+ assert.match(stopConditionsSource, /codex/i);
300
+ });
@@ -312,8 +312,8 @@ test('each fix push, each lens-retry, and the convergence repair invalidate the
312
312
  const invalidationMatches = convergeSource.match(/^ +head = null$/gm) || [];
313
313
  assert.equal(
314
314
  invalidationMatches.length,
315
- 8,
316
- 'expected head invalidation after the static-sweep fix push, the CONVERGE fix push, the terminal-Bugbot fix push, the COPILOT fix push, the convergence repair, the all-lenses-dead retry, the not-clean-no-findings retry, and the all-clean no-lens-ran clean-audit refusal',
315
+ 9,
316
+ 'expected head invalidation after the static-sweep fix push, the CONVERGE fix push, the terminal-Bugbot fix push, the COPILOT fix push, the CODEX fix push, the convergence repair, the all-lenses-dead retry, the not-clean-no-findings retry, and the all-clean no-lens-ran clean-audit refusal',
317
317
  );
318
318
  });
319
319
 
@@ -617,8 +617,8 @@ test('every standards-deferral call site builds standardsNote from the spawnStan
617
617
  const callSiteUses = convergeSource.match(/standardsNote = standardsDeferralNote\(/g) || [];
618
618
  assert.equal(
619
619
  callSiteUses.length,
620
- 3,
621
- 'expected the converge-round, terminal-Bugbot, and Copilot standards call sites to each build standardsNote via standardsDeferralNote(...)',
620
+ 4,
621
+ 'expected the converge-round, terminal-Bugbot, Copilot, and Codex standards call sites to each build standardsNote via standardsDeferralNote(...)',
622
622
  );
623
623
  assert.doesNotMatch(
624
624
  convergeSource,
@@ -1234,7 +1234,7 @@ test('the FINALIZE phase drives the merged check and reads ready from its result
1234
1234
  const finalizeStart = convergeSource.indexOf("if (phase === 'FINALIZE') {");
1235
1235
  assert.notEqual(finalizeStart, -1, 'expected a FINALIZE phase block');
1236
1236
  const finalizeBody = convergeSource.slice(finalizeStart, finalizeStart + 1000);
1237
- assert.match(finalizeBody, /runConvergenceCheck\(\{ head, bugbotDown, copilotDown, bugteamPostBlocked: cleanAuditNote !== null \}\)/);
1237
+ assert.match(finalizeBody, /runConvergenceCheck\(\{ head, bugbotDown, copilotDown, codexDown, codexCleanAt, bugteamPostBlocked: cleanAuditNote !== null \}\)/);
1238
1238
  assert.match(finalizeBody, /classifyReadyOutcome\(finalizeResult\)/);
1239
1239
  assert.doesNotMatch(
1240
1240
  finalizeBody,
@@ -276,8 +276,8 @@ test('the Copilot gate prompt tiers each finding and returns the review URL', ()
276
276
 
277
277
  test('the COPILOT phase returns blocker user-review only when inconclusive findings remain after verification, carrying the verifier-noted payload with no auto-fix in the branch', () => {
278
278
  const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
279
- const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", copilotPhaseStart);
280
- const copilotPhase = convergeSource.slice(copilotPhaseStart, finalizePhaseStart);
279
+ const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {", copilotPhaseStart);
280
+ const copilotPhase = convergeSource.slice(copilotPhaseStart, codexPhaseStart);
281
281
  const userReviewGuardStart = copilotPhase.indexOf('inconclusive.length > 0');
282
282
  assert.notEqual(userReviewGuardStart, -1, 'expected the user-review return to be guarded by inconclusive.length > 0');
283
283
  const userReviewBranch = copilotPhase.slice(userReviewGuardStart, userReviewGuardStart + 600);
@@ -296,8 +296,8 @@ test('the COPILOT phase returns blocker user-review only when inconclusive findi
296
296
 
297
297
  test('the COPILOT phase awaits verifyCodeConcernFindings exactly once, inside the code-concern guard', () => {
298
298
  const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
299
- const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", copilotPhaseStart);
300
- const copilotPhase = convergeSource.slice(copilotPhaseStart, finalizePhaseStart);
299
+ const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {", copilotPhaseStart);
300
+ const copilotPhase = convergeSource.slice(copilotPhaseStart, codexPhaseStart);
301
301
  const guardStart = copilotPhase.indexOf('if (codeConcern.length > 0)');
302
302
  assert.notEqual(guardStart, -1, 'expected the verification to sit behind a codeConcern.length > 0 guard');
303
303
  const verifyCalls = convergeSource.match(/await verifyCodeConcernFindings\(/g) || [];
@@ -306,21 +306,21 @@ test('the COPILOT phase awaits verifyCodeConcernFindings exactly once, inside th
306
306
  assert.ok(verifyCallIndex > guardStart, 'expected the verifyCodeConcernFindings call to sit inside the code-concern guard');
307
307
  });
308
308
 
309
- test('the all-refuted / empty-roundFindings COPILOT path advances to FINALIZE with the gate passed', () => {
309
+ test('the all-refuted / empty-roundFindings COPILOT path advances to CODEX with the gate passed', () => {
310
310
  const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
311
- const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", copilotPhaseStart);
312
- const copilotPhase = convergeSource.slice(copilotPhaseStart, finalizePhaseStart);
311
+ const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {", copilotPhaseStart);
312
+ const copilotPhase = convergeSource.slice(copilotPhaseStart, codexPhaseStart);
313
313
  const emptyRoundStart = copilotPhase.indexOf('roundFindings.length === 0');
314
314
  assert.notEqual(emptyRoundStart, -1, 'expected the COPILOT phase to handle an empty roundFindings set after verification');
315
315
  const emptyRoundBranch = copilotPhase.slice(emptyRoundStart, emptyRoundStart + 300);
316
316
  assert.match(emptyRoundBranch, /copilotDown = false/, 'expected the empty-roundFindings path to clear copilotDown');
317
- assert.match(emptyRoundBranch, /phase = 'FINALIZE'/, 'expected the empty-roundFindings path to advance to FINALIZE');
317
+ assert.match(emptyRoundBranch, /phase = 'CODEX'/, 'expected the empty-roundFindings path to advance to CODEX');
318
318
  });
319
319
 
320
320
  test('the COPILOT phase folds confirmed findings through attachVerifiedRepro into the round fix list', () => {
321
321
  const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
322
- const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", copilotPhaseStart);
323
- const copilotPhase = convergeSource.slice(copilotPhaseStart, finalizePhaseStart);
322
+ const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {", copilotPhaseStart);
323
+ const copilotPhase = convergeSource.slice(copilotPhaseStart, codexPhaseStart);
324
324
  assert.match(
325
325
  copilotPhase,
326
326
  /roundFindings = \[\.\.\.selfHealing, \.\.\.confirmed\.map\(\(each\) => attachVerifiedRepro\(each\)\)\]/,
@@ -405,15 +405,15 @@ test('runConvergenceCheck wires the --copilot-down flag from the copilotDown con
405
405
  );
406
406
  });
407
407
 
408
- test('the COPILOT phase routes a down outcome to FINALIZE with the gate bypassed', () => {
408
+ test('the COPILOT phase routes a down outcome to CODEX with the gate bypassed', () => {
409
409
  const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
410
410
  assert.notEqual(copilotPhaseStart, -1, 'expected a COPILOT phase block');
411
411
  const downBranchStart = convergeSource.indexOf("copilotOutcome.kind === 'down'", copilotPhaseStart);
412
412
  assert.notEqual(downBranchStart, -1, 'expected the COPILOT phase to handle a down outcome');
413
- const downBranch = convergeSource.slice(downBranchStart, downBranchStart + 400);
413
+ const downBranch = convergeSource.slice(downBranchStart, downBranchStart + 700);
414
414
  assert.match(downBranch, /copilotDown = true/);
415
415
  assert.match(downBranch, /copilotNote =/);
416
- assert.match(downBranch, /phase = 'FINALIZE'/);
416
+ assert.match(downBranch, /phase = 'CODEX'/);
417
417
  });
418
418
 
419
419
  test('resolveCopilotDown reports down only for a down outcome', () => {
@@ -448,7 +448,7 @@ test('resolveCopilotDown clears the bypass for a retry outcome', () => {
448
448
  assert.equal(resolveCopilotDown({ kind: 'retry' }), false);
449
449
  });
450
450
 
451
- test('the standards-only Copilot sub-path resets copilotDown before FINALIZE', () => {
451
+ test('the standards-only Copilot sub-path resets copilotDown before CODEX', () => {
452
452
  const standardsBranchStart = convergeSource.indexOf(
453
453
  'isStandardsOnlyRound(roundFindings)',
454
454
  );
@@ -459,19 +459,19 @@ test('the standards-only Copilot sub-path resets copilotDown before FINALIZE', (
459
459
  );
460
460
  const standardsBranch = convergeSource.slice(standardsBranchStart, standardsBranchStart + 800);
461
461
  const resetIndex = standardsBranch.indexOf('copilotDown = false');
462
- const finalizeIndex = standardsBranch.indexOf("phase = 'FINALIZE'");
462
+ const codexIndex = standardsBranch.indexOf("phase = 'CODEX'");
463
463
  assert.notEqual(
464
464
  resetIndex,
465
465
  -1,
466
466
  'expected the standards-only sub-path to reset copilotDown so a recovered Copilot is not bypassed',
467
467
  );
468
- assert.notEqual(finalizeIndex, -1, 'expected the standards-only sub-path to reach FINALIZE');
468
+ assert.notEqual(codexIndex, -1, 'expected the standards-only sub-path to reach CODEX');
469
469
  assert.ok(
470
- resetIndex < finalizeIndex,
471
- 'expected copilotDown to be cleared before the transition to FINALIZE',
470
+ resetIndex < codexIndex,
471
+ 'expected copilotDown to be cleared before the transition to CODEX',
472
472
  );
473
473
  assert.match(
474
- standardsBranch.slice(0, finalizeIndex),
474
+ standardsBranch.slice(0, codexIndex),
475
475
  /copilotNote = null/,
476
476
  'expected the standards-only sub-path to clear the stale copilotNote alongside copilotDown',
477
477
  );
@@ -480,12 +480,12 @@ test('the standards-only Copilot sub-path resets copilotDown before FINALIZE', (
480
480
  test('the COPILOT phase recomputes copilotDown from each gate outcome via resolveCopilotDown', () => {
481
481
  const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
482
482
  assert.notEqual(copilotPhaseStart, -1, 'expected a COPILOT phase block');
483
- const finalizePhaseStart = convergeSource.indexOf(
484
- "if (phase === 'FINALIZE') {",
483
+ const codexPhaseStart = convergeSource.indexOf(
484
+ "if (phase === 'CODEX') {",
485
485
  copilotPhaseStart,
486
486
  );
487
- assert.notEqual(finalizePhaseStart, -1, 'expected a FINALIZE phase block after COPILOT');
488
- const copilotPhase = convergeSource.slice(copilotPhaseStart, finalizePhaseStart);
487
+ assert.notEqual(codexPhaseStart, -1, 'expected a CODEX phase block after COPILOT');
488
+ const copilotPhase = convergeSource.slice(copilotPhaseStart, codexPhaseStart);
489
489
  assert.match(
490
490
  copilotPhase,
491
491
  /copilotDown = resolveCopilotDown\(copilotOutcome\)/,
@@ -552,7 +552,7 @@ test('the COPILOT phase short-circuits on the unified reviewer-down gate before
552
552
  );
553
553
  assert.match(beforeGate, /copilotDown = true/, 'expected the bypass to mark copilotDown');
554
554
  assert.match(beforeGate, /copilotNote =/, 'expected the bypass to set a copilotNote');
555
- assert.match(beforeGate, /phase = 'FINALIZE'/, 'expected the bypass to advance to FINALIZE');
555
+ assert.match(beforeGate, /phase = 'CODEX'/, 'expected the bypass to advance to CODEX');
556
556
  assert.match(beforeGate, /continue/, 'expected the bypass to continue without spawning the gate agent');
557
557
  });
558
558
 
@@ -574,7 +574,7 @@ test('copilotDown initializes from input.copilotDisabled so the pre-check decisi
574
574
  );
575
575
  });
576
576
 
577
- test('a copilotDisabled run reaches FINALIZE with --copilot-down', () => {
577
+ test('a copilotDisabled run reaches CODEX with --copilot-down', () => {
578
578
  const copilotPhaseStart = convergeSource.indexOf("if (phase === 'COPILOT') {");
579
579
  const bypassStart = convergeSource.indexOf(
580
580
  'if (resolveReviewerDown(reviewerAvailability?.copilot, input.copilotDisabled || false))',
@@ -583,12 +583,12 @@ test('a copilotDisabled run reaches FINALIZE with --copilot-down', () => {
583
583
  assert.notEqual(bypassStart, -1, 'expected the unified resolveReviewerDown bypass in the COPILOT phase');
584
584
  const bypassBlock = convergeSource.slice(bypassStart, bypassStart + 800);
585
585
  assert.match(bypassBlock, /copilotDown = true/, 'expected the bypass to set copilotDown');
586
- assert.match(bypassBlock, /phase = 'FINALIZE'/, 'expected the bypass to advance to FINALIZE');
586
+ assert.match(bypassBlock, /phase = 'CODEX'/, 'expected the bypass to advance to CODEX');
587
587
  const convergenceCheckBody = functionBody('runConvergenceCheck');
588
588
  assert.match(
589
589
  convergenceCheckBody,
590
590
  /context\.copilotDown \? ' --copilot-down' : ''/,
591
- 'expected the convergence check to pass --copilot-down when the bypassed copilotDown reaches FINALIZE',
591
+ 'expected the convergence check to pass --copilot-down when the bypassed copilotDown reaches FINALIZE via CODEX',
592
592
  );
593
593
  });
594
594
 
@@ -673,8 +673,8 @@ test('every standards-deferral call site routes the create through openStandards
673
673
  const onceCalls = convergeSource.match(/await openStandardsFollowUpOnce\(/g) || [];
674
674
  assert.equal(
675
675
  onceCalls.length,
676
- 3,
677
- 'expected the converge-round, terminal-Bugbot, and Copilot standards call sites to all defer to openStandardsFollowUpOnce',
676
+ 4,
677
+ 'expected the converge-round, terminal-Bugbot, Copilot, and Codex standards call sites to all defer to openStandardsFollowUpOnce',
678
678
  );
679
679
  const directCreates = convergeSource.match(/await spawnStandardsFollowUp\(/g) || [];
680
680
  assert.equal(
@@ -100,7 +100,7 @@ test('the converge call site sets a fix-stalled blocker when an all-null-thread
100
100
  test('the copilot call site sets a fix-stalled blocker when an all-null-thread resolvedWithoutCommit round cannot progress', () => {
101
101
  const copilotBranch = convergeSource.slice(
102
102
  convergeSource.indexOf("const fixResult = await applyFixes(head, roundFindings, 'copilot')"),
103
- convergeSource.indexOf("phase = 'CONVERGE'\n continue\n }\n phase = 'FINALIZE'"),
103
+ convergeSource.indexOf("phase = 'CONVERGE'\n continue\n }\n phase = 'CODEX'"),
104
104
  );
105
105
  assert.match(copilotBranch, /collectFindingThreadIds/);
106
106
  assert.match(copilotBranch, /fix stalled/i);