claude-dev-env 8.12.0 → 8.14.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.
- package/.agents/skills/pull-request/scripts/test_pull_request.py +1 -1
- package/.agents/skills/team-advisor/SKILL.md +1 -1
- package/.agents/skills-archived/pr-converge/scripts/test_check_convergence.py +4 -4
- package/_shared/advisor/advisor-protocol.md +19 -19
- package/_shared/advisor/reference/advisor-block.md +1 -1
- package/_shared/advisor/reference/astra-rung.md +11 -11
- package/_shared/advisor/reference/identity.md +4 -4
- package/_shared/advisor/reference/lifecycle.md +2 -2
- package/_shared/advisor/reference/spawn-walk-log.md +4 -4
- package/_shared/advisor/reference/third-party-bind.md +7 -7
- package/_shared/advisor/reference/warm-up.md +3 -3
- package/_shared/advisor/scripts/codex_astra_advisor.py +33 -26
- package/_shared/advisor/scripts/codex_astra_preflight.py +57 -55
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +2 -2
- package/_shared/advisor/scripts/config/advisor_scripts_constants/astra_advisor_constants.py +8 -8
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +3 -3
- package/_shared/advisor/scripts/model_tier_run_validator.py +8 -9
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +69 -23
- package/_shared/advisor/scripts/tests/test_codex_astra_preflight.py +4 -4
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +91 -91
- package/_shared/pr-loop/scripts/check_convergence.py +54 -49
- package/_shared/pr-loop/scripts/codex_review_scripts_constants/run_constants.py +4 -0
- package/_shared/pr-loop/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +10 -1
- package/_shared/pr-loop/scripts/run_codex_review.py +4 -6
- package/_shared/pr-loop/scripts/test_check_convergence.py +6 -6
- package/_shared/pr-loop/scripts/test_check_convergence_codex.py +73 -31
- package/_shared/pr-loop/scripts/test_check_convergence_contract.py +2 -2
- package/_shared/pr-loop/scripts/tests/test__claude_permissions_common.py +2 -2
- package/bin/git_hooks_installer.test.mjs +1 -1
- package/bin/install-constants.mjs +1 -6
- package/bin/install.cursor-rules.test.mjs +24 -51
- package/bin/install.mjs +16 -70
- package/docs/codex-accounts.md +79 -0
- package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +5 -5
- package/hooks/blocking/test_pii_scanner.py +1 -1
- package/hooks/blocking/test_pytest_testpaths_orphan_blocker.py +2 -2
- package/hooks/git-hooks/conftest.py +16 -0
- package/hooks/git-hooks/git_hooks_constants/verification_notice_constants.py +1 -1
- package/hooks/git-hooks/test_post_commit.py +1 -1
- package/hooks/git-hooks/test_pre_commit.py +1 -1
- package/hooks/git-hooks/test_pre_push.py +2 -2
- package/hooks/git-hooks/test_verification_start.py +1 -1
- package/hooks/git-hooks/verification_notice.py +2 -2
- package/hooks/git-hooks/verification_notice_context.py +21 -2
- package/hooks/git-hooks/verification_start.py +2 -3
- package/package.json +1 -1
- package/rules/durable-post-artifacts.md +4 -3
- package/scripts/claude_account_profile.py +21 -9
- package/scripts/codex_account_choice.py +396 -0
- package/scripts/codex_account_meters.py +372 -0
- package/scripts/dev_env_scripts_constants/codex_account_constants.py +141 -0
- package/scripts/dev_env_scripts_constants/private_term_constants.py +17 -2
- package/scripts/private_term_scan.py +6 -2
- package/scripts/repository_checks/config/constants.py +1 -0
- package/scripts/repository_checks/tracked_private_terms.py +7 -0
- package/scripts/test_claude_account_profile.py +12 -0
- package/scripts/test_codex_account_choice.py +276 -0
- package/scripts/test_codex_account_meters.py +205 -0
- package/scripts/tests/test_automatic_advisory.py +3 -3
- package/scripts/tests/test_automatic_advisory_polling.py +1 -1
- package/scripts/tests/test_automatic_advisory_recovery.py +1 -1
- package/scripts/tests/test_closed_pr_label.py +1 -1
- package/scripts/tests/test_private_term_scan.py +28 -0
- package/scripts/tests/test_review_closure.py +2 -2
- package/scripts/tests/test_review_closure_model.py +2 -2
- package/scripts/tests/test_rules.py +2 -83
- package/scripts/tests/test_tracked_private_terms.py +15 -0
- package/scripts/tests/test_verification_notice.py +29 -13
- package/.agents/skills/cert-classification-rule/SKILL.md +0 -224
- package/.agents/skills/cert-classification-rule/scripts/resort_cert_folders.py +0 -158
- package/_shared/pr-loop/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +0 -82
- package/_shared/pr-loop/scripts/codex_usage_probe.py +0 -573
- package/_shared/pr-loop/scripts/test_codex_usage_probe.py +0 -683
- package/scripts/sync_to_cursor/AGENTS.md +0 -1
- package/scripts/sync_to_cursor/CLAUDE.md +0 -1
- package/scripts/sync_to_cursor/__init__.py +0 -13
- package/scripts/sync_to_cursor/canonical_docs.py +0 -66
- package/scripts/sync_to_cursor/config/__init__.py +0 -8
- package/scripts/sync_to_cursor/engine.py +0 -219
- package/scripts/sync_to_cursor/hashing.py +0 -7
- package/scripts/sync_to_cursor/paths.py +0 -18
- package/scripts/sync_to_cursor/rules.py +0 -494
- package/scripts/sync_to_cursor.py +0 -22
- package/scripts/tests/test_engine.py +0 -101
- package/scripts/tests/test_sync_to_cursor.py +0 -315
|
@@ -218,19 +218,19 @@ def test_non_object_evidence_reports_the_same_message_at_validate_and_load(
|
|
|
218
218
|
def test_clean_single_spawn_at_top_of_slice_passes() -> None:
|
|
219
219
|
run = ModelTierRun(
|
|
220
220
|
own_tier="Opus",
|
|
221
|
-
candidate_tiers=["
|
|
222
|
-
attempts=[{"tier": "
|
|
223
|
-
selected_tier="
|
|
221
|
+
candidate_tiers=["Opus"],
|
|
222
|
+
attempts=[{"tier": "Opus", "result": "spawned"}],
|
|
223
|
+
selected_tier="Opus",
|
|
224
224
|
)
|
|
225
225
|
assert validate_model_tier_run(run) is None
|
|
226
226
|
|
|
227
227
|
|
|
228
|
-
def
|
|
228
|
+
def test_astra_codex_bind_succeeds_after_opus_when_enabled() -> None:
|
|
229
229
|
run = ModelTierRun(
|
|
230
230
|
own_tier="Opus",
|
|
231
|
-
candidate_tiers=["
|
|
231
|
+
candidate_tiers=["Opus", ADVISOR_MODEL_TIER],
|
|
232
232
|
attempts=[
|
|
233
|
-
{"tier": "
|
|
233
|
+
{"tier": "Opus", "result": "unavailable"},
|
|
234
234
|
{"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
|
|
235
235
|
],
|
|
236
236
|
selected_tier=ADVISOR_MODEL_TIER,
|
|
@@ -239,12 +239,12 @@ def test_astra_codex_bind_succeeds_after_fable_when_enabled() -> None:
|
|
|
239
239
|
assert validate_model_tier_run(run) is None
|
|
240
240
|
|
|
241
241
|
|
|
242
|
-
def
|
|
242
|
+
def test_opus_success_with_astra_enabled_stops_before_astra() -> None:
|
|
243
243
|
run = ModelTierRun(
|
|
244
244
|
own_tier="Opus",
|
|
245
|
-
candidate_tiers=["
|
|
246
|
-
attempts=[{"tier": "
|
|
247
|
-
selected_tier="
|
|
245
|
+
candidate_tiers=["Opus", ADVISOR_MODEL_TIER],
|
|
246
|
+
attempts=[{"tier": "Opus", "result": "spawned"}],
|
|
247
|
+
selected_tier="Opus",
|
|
248
248
|
is_astra_enabled=True,
|
|
249
249
|
)
|
|
250
250
|
assert validate_model_tier_run(run) is None
|
|
@@ -253,7 +253,7 @@ def test_fable_success_with_astra_enabled_stops_before_astra() -> None:
|
|
|
253
253
|
def test_astra_first_walk_raises_when_astra_is_enabled() -> None:
|
|
254
254
|
run = ModelTierRun(
|
|
255
255
|
own_tier="Opus",
|
|
256
|
-
candidate_tiers=[ADVISOR_MODEL_TIER, "
|
|
256
|
+
candidate_tiers=[ADVISOR_MODEL_TIER, "Opus"],
|
|
257
257
|
attempts=[{"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN}],
|
|
258
258
|
selected_tier=ADVISOR_MODEL_TIER,
|
|
259
259
|
is_astra_enabled=True,
|
|
@@ -262,12 +262,12 @@ def test_astra_first_walk_raises_when_astra_is_enabled() -> None:
|
|
|
262
262
|
validate_model_tier_run(run)
|
|
263
263
|
|
|
264
264
|
|
|
265
|
-
def
|
|
265
|
+
def test_astra_rung_follows_opus_on_third_party_cli_floor_when_enabled() -> None:
|
|
266
266
|
run = ModelTierRun(
|
|
267
267
|
own_tier="ThirdParty",
|
|
268
|
-
candidate_tiers=["
|
|
268
|
+
candidate_tiers=["Opus", ADVISOR_MODEL_TIER],
|
|
269
269
|
attempts=[
|
|
270
|
-
{"tier": "
|
|
270
|
+
{"tier": "Opus", "result": "unavailable"},
|
|
271
271
|
{"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
|
|
272
272
|
],
|
|
273
273
|
selected_tier=ADVISOR_MODEL_TIER,
|
|
@@ -281,9 +281,9 @@ def test_astra_rung_follows_fable_on_third_party_cli_floor_when_enabled() -> Non
|
|
|
281
281
|
def test_astra_codex_result_requires_astra_candidate() -> None:
|
|
282
282
|
run = ModelTierRun(
|
|
283
283
|
own_tier="Opus",
|
|
284
|
-
candidate_tiers=["
|
|
285
|
-
attempts=[{"tier": "
|
|
286
|
-
selected_tier="
|
|
284
|
+
candidate_tiers=["Opus"],
|
|
285
|
+
attempts=[{"tier": "Opus", "result": CODEX_BIND_SUCCESS_TOKEN}],
|
|
286
|
+
selected_tier="Opus",
|
|
287
287
|
)
|
|
288
288
|
with pytest.raises(ModelTierRunError):
|
|
289
289
|
validate_model_tier_run(run)
|
|
@@ -292,9 +292,9 @@ def test_astra_codex_result_requires_astra_candidate() -> None:
|
|
|
292
292
|
def test_astra_spawned_result_does_not_count_as_codex_success() -> None:
|
|
293
293
|
run = ModelTierRun(
|
|
294
294
|
own_tier="Opus",
|
|
295
|
-
candidate_tiers=["
|
|
295
|
+
candidate_tiers=["Opus", ADVISOR_MODEL_TIER],
|
|
296
296
|
attempts=[
|
|
297
|
-
{"tier": "
|
|
297
|
+
{"tier": "Opus", "result": "unavailable"},
|
|
298
298
|
{"tier": ADVISOR_MODEL_TIER, "result": "spawned"},
|
|
299
299
|
],
|
|
300
300
|
selected_tier=ADVISOR_MODEL_TIER,
|
|
@@ -304,13 +304,13 @@ def test_astra_spawned_result_does_not_count_as_codex_success() -> None:
|
|
|
304
304
|
validate_model_tier_run(run)
|
|
305
305
|
|
|
306
306
|
|
|
307
|
-
def
|
|
307
|
+
def test_exhausted_opus_walk_fails_closed() -> None:
|
|
308
308
|
run = ModelTierRun(
|
|
309
309
|
own_tier="Opus",
|
|
310
|
-
candidate_tiers=["
|
|
311
|
-
attempts=[{"tier": "
|
|
310
|
+
candidate_tiers=["Opus"],
|
|
311
|
+
attempts=[{"tier": "Opus", "result": "unavailable"}],
|
|
312
312
|
selected_tier=None,
|
|
313
|
-
fallback_reason="
|
|
313
|
+
fallback_reason="Opus did not bind; no advisor",
|
|
314
314
|
)
|
|
315
315
|
assert validate_model_tier_run(run) is None
|
|
316
316
|
|
|
@@ -318,8 +318,8 @@ def test_exhausted_fable_walk_fails_closed() -> None:
|
|
|
318
318
|
def test_fully_exhausted_walk_with_fallback_reason_passes() -> None:
|
|
319
319
|
run = ModelTierRun(
|
|
320
320
|
own_tier="Opus",
|
|
321
|
-
candidate_tiers=["
|
|
322
|
-
attempts=[{"tier": "
|
|
321
|
+
candidate_tiers=["Opus"],
|
|
322
|
+
attempts=[{"tier": "Opus", "result": "unavailable"}],
|
|
323
323
|
selected_tier=None,
|
|
324
324
|
fallback_reason="every candidate tier failed",
|
|
325
325
|
)
|
|
@@ -329,9 +329,9 @@ def test_fully_exhausted_walk_with_fallback_reason_passes() -> None:
|
|
|
329
329
|
def test_opus_candidate_on_advisor_walk_raises() -> None:
|
|
330
330
|
run = ModelTierRun(
|
|
331
331
|
own_tier="Opus",
|
|
332
|
-
candidate_tiers=["
|
|
333
|
-
attempts=[{"tier": "
|
|
334
|
-
selected_tier="
|
|
332
|
+
candidate_tiers=["Opus", "Opus"],
|
|
333
|
+
attempts=[{"tier": "Opus", "result": "spawned"}],
|
|
334
|
+
selected_tier="Opus",
|
|
335
335
|
)
|
|
336
336
|
with pytest.raises(ModelTierRunError):
|
|
337
337
|
validate_model_tier_run(run)
|
|
@@ -340,7 +340,7 @@ def test_opus_candidate_on_advisor_walk_raises() -> None:
|
|
|
340
340
|
def test_attempt_tier_outside_candidate_slice_raises() -> None:
|
|
341
341
|
run = ModelTierRun(
|
|
342
342
|
own_tier="Opus",
|
|
343
|
-
candidate_tiers=["
|
|
343
|
+
candidate_tiers=["Opus"],
|
|
344
344
|
attempts=[{"tier": "Haiku", "result": "spawned"}],
|
|
345
345
|
selected_tier="Haiku",
|
|
346
346
|
)
|
|
@@ -351,10 +351,10 @@ def test_attempt_tier_outside_candidate_slice_raises() -> None:
|
|
|
351
351
|
def test_attempts_out_of_ladder_order_raises() -> None:
|
|
352
352
|
run = ModelTierRun(
|
|
353
353
|
own_tier="Opus",
|
|
354
|
-
candidate_tiers=["
|
|
354
|
+
candidate_tiers=["Opus", ADVISOR_MODEL_TIER],
|
|
355
355
|
attempts=[
|
|
356
356
|
{"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
|
|
357
|
-
{"tier": "
|
|
357
|
+
{"tier": "Opus", "result": "unavailable"},
|
|
358
358
|
],
|
|
359
359
|
selected_tier=ADVISOR_MODEL_TIER,
|
|
360
360
|
is_astra_enabled=True,
|
|
@@ -366,12 +366,12 @@ def test_attempts_out_of_ladder_order_raises() -> None:
|
|
|
366
366
|
def test_selected_tier_not_first_spawned_attempt_raises() -> None:
|
|
367
367
|
run = ModelTierRun(
|
|
368
368
|
own_tier="Opus",
|
|
369
|
-
candidate_tiers=["
|
|
369
|
+
candidate_tiers=["Opus", ADVISOR_MODEL_TIER],
|
|
370
370
|
attempts=[
|
|
371
|
-
{"tier": "
|
|
371
|
+
{"tier": "Opus", "result": "unavailable"},
|
|
372
372
|
{"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
|
|
373
373
|
],
|
|
374
|
-
selected_tier="
|
|
374
|
+
selected_tier="Opus",
|
|
375
375
|
is_astra_enabled=True,
|
|
376
376
|
)
|
|
377
377
|
with pytest.raises(
|
|
@@ -387,9 +387,9 @@ def test_selected_tier_not_first_spawned_attempt_raises() -> None:
|
|
|
387
387
|
def test_exhausted_walk_with_non_null_selected_tier_raises() -> None:
|
|
388
388
|
run = ModelTierRun(
|
|
389
389
|
own_tier="Opus",
|
|
390
|
-
candidate_tiers=["
|
|
391
|
-
attempts=[{"tier": "
|
|
392
|
-
selected_tier="
|
|
390
|
+
candidate_tiers=["Opus"],
|
|
391
|
+
attempts=[{"tier": "Opus", "result": "unavailable"}],
|
|
392
|
+
selected_tier="Opus",
|
|
393
393
|
fallback_reason="every candidate tier failed",
|
|
394
394
|
)
|
|
395
395
|
with pytest.raises(ModelTierRunError):
|
|
@@ -399,8 +399,8 @@ def test_exhausted_walk_with_non_null_selected_tier_raises() -> None:
|
|
|
399
399
|
def test_exhausted_walk_missing_fallback_reason_raises() -> None:
|
|
400
400
|
run = ModelTierRun(
|
|
401
401
|
own_tier="Opus",
|
|
402
|
-
candidate_tiers=["
|
|
403
|
-
attempts=[{"tier": "
|
|
402
|
+
candidate_tiers=["Opus"],
|
|
403
|
+
attempts=[{"tier": "Opus", "result": "unavailable"}],
|
|
404
404
|
selected_tier=None,
|
|
405
405
|
)
|
|
406
406
|
with pytest.raises(ModelTierRunError):
|
|
@@ -421,7 +421,7 @@ def test_unknown_own_tier_raises() -> None:
|
|
|
421
421
|
def test_empty_attempts_with_null_selected_tier_raises() -> None:
|
|
422
422
|
run = ModelTierRun(
|
|
423
423
|
own_tier="Opus",
|
|
424
|
-
candidate_tiers=["
|
|
424
|
+
candidate_tiers=["Opus"],
|
|
425
425
|
attempts=[],
|
|
426
426
|
selected_tier=None,
|
|
427
427
|
fallback_reason="skipped straight to CLI fallback",
|
|
@@ -433,10 +433,10 @@ def test_empty_attempts_with_null_selected_tier_raises() -> None:
|
|
|
433
433
|
def test_incomplete_fallback_walk_before_astra_raises() -> None:
|
|
434
434
|
run = ModelTierRun(
|
|
435
435
|
own_tier="Opus",
|
|
436
|
-
candidate_tiers=["
|
|
437
|
-
attempts=[{"tier": "
|
|
436
|
+
candidate_tiers=["Opus", ADVISOR_MODEL_TIER],
|
|
437
|
+
attempts=[{"tier": "Opus", "result": "unavailable"}],
|
|
438
438
|
selected_tier=None,
|
|
439
|
-
fallback_reason="stopped after
|
|
439
|
+
fallback_reason="stopped after Opus without trying Astra",
|
|
440
440
|
is_astra_enabled=True,
|
|
441
441
|
)
|
|
442
442
|
with pytest.raises(ModelTierRunError):
|
|
@@ -446,9 +446,9 @@ def test_incomplete_fallback_walk_before_astra_raises() -> None:
|
|
|
446
446
|
def test_lowercase_own_tier_and_candidates_pass() -> None:
|
|
447
447
|
run = ModelTierRun(
|
|
448
448
|
own_tier="opus",
|
|
449
|
-
candidate_tiers=["
|
|
450
|
-
attempts=[{"tier": "
|
|
451
|
-
selected_tier="
|
|
449
|
+
candidate_tiers=["opus"],
|
|
450
|
+
attempts=[{"tier": "opus", "result": "spawned"}],
|
|
451
|
+
selected_tier="opus",
|
|
452
452
|
)
|
|
453
453
|
assert validate_model_tier_run(run) is None
|
|
454
454
|
|
|
@@ -459,16 +459,16 @@ def test_cli_validates_json_log_file(tmp_path: Path) -> None:
|
|
|
459
459
|
json.dumps(
|
|
460
460
|
{
|
|
461
461
|
"own_tier": "Opus",
|
|
462
|
-
"candidate_tiers": ["
|
|
463
|
-
"attempts": [{"tier": "
|
|
464
|
-
"selected_tier": "
|
|
462
|
+
"candidate_tiers": ["Opus"],
|
|
463
|
+
"attempts": [{"tier": "Opus", "result": "spawned"}],
|
|
464
|
+
"selected_tier": "Opus",
|
|
465
465
|
}
|
|
466
466
|
),
|
|
467
467
|
encoding="utf-8",
|
|
468
468
|
)
|
|
469
469
|
assert main([str(log_path)]) == 0
|
|
470
470
|
loaded_run = load_model_tier_run_from_json_path(from_path=log_path)
|
|
471
|
-
assert loaded_run.selected_tier == "
|
|
471
|
+
assert loaded_run.selected_tier == "Opus"
|
|
472
472
|
assert loaded_run.host_profile == "Claude"
|
|
473
473
|
|
|
474
474
|
|
|
@@ -478,9 +478,9 @@ def test_cli_rejects_non_string_host_profile(tmp_path: Path) -> None:
|
|
|
478
478
|
json.dumps(
|
|
479
479
|
{
|
|
480
480
|
"own_tier": "Opus",
|
|
481
|
-
"candidate_tiers": ["
|
|
482
|
-
"attempts": [{"tier": "
|
|
483
|
-
"selected_tier": "
|
|
481
|
+
"candidate_tiers": ["Opus"],
|
|
482
|
+
"attempts": [{"tier": "Opus", "result": "spawned"}],
|
|
483
|
+
"selected_tier": "Opus",
|
|
484
484
|
"host_profile": 1,
|
|
485
485
|
}
|
|
486
486
|
),
|
|
@@ -495,9 +495,9 @@ def test_cli_rejects_non_boolean_astra_enabled(tmp_path: Path) -> None:
|
|
|
495
495
|
json.dumps(
|
|
496
496
|
{
|
|
497
497
|
"own_tier": "Opus",
|
|
498
|
-
"candidate_tiers": ["
|
|
499
|
-
"attempts": [{"tier": "
|
|
500
|
-
"selected_tier": "
|
|
498
|
+
"candidate_tiers": ["Opus"],
|
|
499
|
+
"attempts": [{"tier": "Opus", "result": "spawned"}],
|
|
500
|
+
"selected_tier": "Opus",
|
|
501
501
|
"astra_enabled": "false",
|
|
502
502
|
}
|
|
503
503
|
),
|
|
@@ -512,8 +512,8 @@ def test_cli_rejects_incomplete_fallback_log(tmp_path: Path) -> None:
|
|
|
512
512
|
json.dumps(
|
|
513
513
|
{
|
|
514
514
|
"own_tier": "Opus",
|
|
515
|
-
"candidate_tiers": ["
|
|
516
|
-
"attempts": [{"tier": "
|
|
515
|
+
"candidate_tiers": ["Opus", "Astra"],
|
|
516
|
+
"attempts": [{"tier": "Opus", "result": "unavailable"}],
|
|
517
517
|
"selected_tier": None,
|
|
518
518
|
"fallback_reason": "incomplete",
|
|
519
519
|
"astra_enabled": True,
|
|
@@ -531,9 +531,9 @@ def test_cli_loads_astra_enabled_fallback_walk(tmp_path: Path) -> None:
|
|
|
531
531
|
{
|
|
532
532
|
"own_tier": "ThirdParty",
|
|
533
533
|
"host_profile": "ThirdParty",
|
|
534
|
-
"candidate_tiers": ["
|
|
534
|
+
"candidate_tiers": ["Opus", "Astra"],
|
|
535
535
|
"attempts": [
|
|
536
|
-
{"tier": "
|
|
536
|
+
{"tier": "Opus", "result": "unavailable"},
|
|
537
537
|
{"tier": "Astra", "result": "codex"},
|
|
538
538
|
],
|
|
539
539
|
"selected_tier": "Astra",
|
|
@@ -552,12 +552,12 @@ def test_cli_missing_path_returns_usage_exit_code() -> None:
|
|
|
552
552
|
assert main([]) == 2
|
|
553
553
|
|
|
554
554
|
|
|
555
|
-
def
|
|
555
|
+
def test_cli_bind_at_opus_passes() -> None:
|
|
556
556
|
run = ModelTierRun(
|
|
557
557
|
own_tier="Opus",
|
|
558
|
-
candidate_tiers=["
|
|
559
|
-
attempts=[{"tier": "
|
|
560
|
-
selected_tier="
|
|
558
|
+
candidate_tiers=["Opus"],
|
|
559
|
+
attempts=[{"tier": "Opus", "result": "cli"}],
|
|
560
|
+
selected_tier="Opus",
|
|
561
561
|
)
|
|
562
562
|
assert validate_model_tier_run(run) is None
|
|
563
563
|
|
|
@@ -565,9 +565,9 @@ def test_cli_bind_at_fable_passes() -> None:
|
|
|
565
565
|
def test_cli_bind_fallthrough_to_opus_raises() -> None:
|
|
566
566
|
run = ModelTierRun(
|
|
567
567
|
own_tier="Opus",
|
|
568
|
-
candidate_tiers=["
|
|
568
|
+
candidate_tiers=["Opus", "Opus"],
|
|
569
569
|
attempts=[
|
|
570
|
-
{"tier": "
|
|
570
|
+
{"tier": "Opus", "result": "unavailable"},
|
|
571
571
|
{"tier": "Opus", "result": "cli"},
|
|
572
572
|
],
|
|
573
573
|
selected_tier="Opus",
|
|
@@ -576,12 +576,12 @@ def test_cli_bind_fallthrough_to_opus_raises() -> None:
|
|
|
576
576
|
validate_model_tier_run(run)
|
|
577
577
|
|
|
578
578
|
|
|
579
|
-
def
|
|
579
|
+
def test_third_party_own_tier_maps_to_opus_cli_bind_passes() -> None:
|
|
580
580
|
run = ModelTierRun(
|
|
581
581
|
own_tier="ThirdParty",
|
|
582
|
-
candidate_tiers=["
|
|
583
|
-
attempts=[{"tier": "
|
|
584
|
-
selected_tier="
|
|
582
|
+
candidate_tiers=["Opus"],
|
|
583
|
+
attempts=[{"tier": "Opus", "result": "cli"}],
|
|
584
|
+
selected_tier="Opus",
|
|
585
585
|
)
|
|
586
586
|
assert validate_model_tier_run(run) is None
|
|
587
587
|
|
|
@@ -589,9 +589,9 @@ def test_third_party_own_tier_maps_to_fable_cli_bind_passes() -> None:
|
|
|
589
589
|
def test_third_party_own_tier_lowercase_cli_bind_passes() -> None:
|
|
590
590
|
run = ModelTierRun(
|
|
591
591
|
own_tier="thirdparty",
|
|
592
|
-
candidate_tiers=["
|
|
593
|
-
attempts=[{"tier": "
|
|
594
|
-
selected_tier="
|
|
592
|
+
candidate_tiers=["opus"],
|
|
593
|
+
attempts=[{"tier": "opus", "result": "cli"}],
|
|
594
|
+
selected_tier="opus",
|
|
595
595
|
)
|
|
596
596
|
assert validate_model_tier_run(run) is None
|
|
597
597
|
|
|
@@ -599,9 +599,9 @@ def test_third_party_own_tier_lowercase_cli_bind_passes() -> None:
|
|
|
599
599
|
def test_self_token_is_not_bind_success_raises() -> None:
|
|
600
600
|
run = ModelTierRun(
|
|
601
601
|
own_tier="Opus",
|
|
602
|
-
candidate_tiers=["
|
|
603
|
-
attempts=[{"tier": "
|
|
604
|
-
selected_tier="
|
|
602
|
+
candidate_tiers=["Opus"],
|
|
603
|
+
attempts=[{"tier": "Opus", "result": "self"}],
|
|
604
|
+
selected_tier="Opus",
|
|
605
605
|
)
|
|
606
606
|
with pytest.raises(ModelTierRunError):
|
|
607
607
|
validate_model_tier_run(run)
|
|
@@ -610,9 +610,9 @@ def test_self_token_is_not_bind_success_raises() -> None:
|
|
|
610
610
|
def test_third_party_self_token_is_not_bind_success_raises() -> None:
|
|
611
611
|
run = ModelTierRun(
|
|
612
612
|
own_tier="ThirdParty",
|
|
613
|
-
candidate_tiers=["
|
|
614
|
-
attempts=[{"tier": "
|
|
615
|
-
selected_tier="
|
|
613
|
+
candidate_tiers=["Opus"],
|
|
614
|
+
attempts=[{"tier": "Opus", "result": "self"}],
|
|
615
|
+
selected_tier="Opus",
|
|
616
616
|
)
|
|
617
617
|
with pytest.raises(ModelTierRunError):
|
|
618
618
|
validate_model_tier_run(run)
|
|
@@ -632,8 +632,8 @@ def test_third_party_host_legacy_single_tier_self_bind_raises() -> None:
|
|
|
632
632
|
def test_third_party_cli_exhausted_fail_closed_passes() -> None:
|
|
633
633
|
run = ModelTierRun(
|
|
634
634
|
own_tier="ThirdParty",
|
|
635
|
-
candidate_tiers=["
|
|
636
|
-
attempts=[{"tier": "
|
|
635
|
+
candidate_tiers=["Opus"],
|
|
636
|
+
attempts=[{"tier": "Opus", "result": "unavailable"}],
|
|
637
637
|
selected_tier=None,
|
|
638
638
|
fallback_reason=(
|
|
639
639
|
"third-party host CLI Claude-chain exhausted; fail closed"
|
|
@@ -645,8 +645,8 @@ def test_third_party_cli_exhausted_fail_closed_passes() -> None:
|
|
|
645
645
|
def test_third_party_cli_exhausted_without_fallback_reason_raises() -> None:
|
|
646
646
|
run = ModelTierRun(
|
|
647
647
|
own_tier="ThirdParty",
|
|
648
|
-
candidate_tiers=["
|
|
649
|
-
attempts=[{"tier": "
|
|
648
|
+
candidate_tiers=["Opus"],
|
|
649
|
+
attempts=[{"tier": "Opus", "result": "unavailable"}],
|
|
650
650
|
selected_tier=None,
|
|
651
651
|
)
|
|
652
652
|
with pytest.raises(ModelTierRunError):
|
|
@@ -656,9 +656,9 @@ def test_third_party_cli_exhausted_without_fallback_reason_raises() -> None:
|
|
|
656
656
|
def test_third_party_cli_selected_tier_mismatch_raises() -> None:
|
|
657
657
|
run = ModelTierRun(
|
|
658
658
|
own_tier="ThirdParty",
|
|
659
|
-
candidate_tiers=["
|
|
660
|
-
attempts=[{"tier": "
|
|
661
|
-
selected_tier="
|
|
659
|
+
candidate_tiers=["Opus"],
|
|
660
|
+
attempts=[{"tier": "Opus", "result": "cli"}],
|
|
661
|
+
selected_tier="Sonnet",
|
|
662
662
|
)
|
|
663
663
|
with pytest.raises(ModelTierRunError):
|
|
664
664
|
validate_model_tier_run(run)
|
|
@@ -667,9 +667,9 @@ def test_third_party_cli_selected_tier_mismatch_raises() -> None:
|
|
|
667
667
|
def test_claude_host_self_token_is_not_spawn_success_raises() -> None:
|
|
668
668
|
run = ModelTierRun(
|
|
669
669
|
own_tier="Opus",
|
|
670
|
-
candidate_tiers=["
|
|
671
|
-
attempts=[{"tier": "
|
|
672
|
-
selected_tier="
|
|
670
|
+
candidate_tiers=["Opus"],
|
|
671
|
+
attempts=[{"tier": "Opus", "result": "self"}],
|
|
672
|
+
selected_tier="Opus",
|
|
673
673
|
)
|
|
674
674
|
with pytest.raises(ModelTierRunError):
|
|
675
675
|
validate_model_tier_run(run)
|
|
@@ -699,10 +699,10 @@ def test_codex_host_astra_codex_token_counts_as_success() -> None:
|
|
|
699
699
|
assert validate_model_tier_run(run) is None
|
|
700
700
|
|
|
701
701
|
|
|
702
|
-
def
|
|
702
|
+
def test_codex_host_opus_then_astra_walk_raises() -> None:
|
|
703
703
|
run = ModelTierRun(
|
|
704
704
|
own_tier="Opus",
|
|
705
|
-
candidate_tiers=["
|
|
705
|
+
candidate_tiers=["Opus", ADVISOR_MODEL_TIER],
|
|
706
706
|
attempts=[{"tier": ADVISOR_MODEL_TIER, "result": "spawned"}],
|
|
707
707
|
selected_tier=ADVISOR_MODEL_TIER,
|
|
708
708
|
is_astra_enabled=True,
|
|
@@ -29,9 +29,10 @@ The frozen process env is only a fallback when that disk read fails (logged once
|
|
|
29
29
|
A probe error does not waive the gate; the live GitHub checks still run.
|
|
30
30
|
|
|
31
31
|
The Codex gate is conditional-required: it demands
|
|
32
|
-
``codex_clean_at == current_head`` only when the
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
``codex_clean_at == current_head`` only when the Codex account picker
|
|
33
|
+
(``scripts/codex_account_choice.py choose``) answers the ``normal`` tier, so
|
|
34
|
+
one signed-in account has room for a review. A ``luna`` or ``wait`` answer, an
|
|
35
|
+
unreadable picker, the codex token, or ``codex_down`` never blocks ready.
|
|
35
36
|
"""
|
|
36
37
|
|
|
37
38
|
from __future__ import annotations
|
|
@@ -62,13 +63,12 @@ from check_convergence_thread_gates import (
|
|
|
62
63
|
_check_no_pending_reviews,
|
|
63
64
|
_count_unresolved_bot_threads,
|
|
64
65
|
)
|
|
65
|
-
from codex_review_scripts_constants.codex_usage_probe_constants import (
|
|
66
|
-
USAGE_REPORT_KEY_PERCENT_LEFT,
|
|
67
|
-
)
|
|
68
|
-
from codex_usage_probe import is_codex_review_required, probe_weekly_usage_via_subprocess
|
|
69
66
|
from pr_converge_scripts_constants.convergence_gate_constants import (
|
|
67
|
+
ALL_CODEX_ACCOUNT_PICKER_RELATIVE_PARTS,
|
|
70
68
|
BUGBOT_DOWN_BYPASS_NOTE,
|
|
71
69
|
CLAUDE_JOB_DIR_ENV_VAR_NAME,
|
|
70
|
+
CODEX_ACCOUNT_PICK_TIMEOUT_SECONDS,
|
|
71
|
+
CODEX_ACCOUNT_PICKER_CHOOSE_COMMAND,
|
|
72
72
|
CODEX_BYPASS_DETAIL,
|
|
73
73
|
CODEX_CLEAN_AT_STATE_KEY,
|
|
74
74
|
CODEX_CLEAN_DETAIL_TEMPLATE,
|
|
@@ -76,11 +76,13 @@ from pr_converge_scripts_constants.convergence_gate_constants import (
|
|
|
76
76
|
CODEX_GATE_LABEL,
|
|
77
77
|
CODEX_MISSING_CLEAN_DETAIL_TEMPLATE,
|
|
78
78
|
CODEX_SKIPPED_USAGE_DETAIL,
|
|
79
|
+
CODEX_TIER_KEY,
|
|
80
|
+
CODEX_TIER_NORMAL,
|
|
79
81
|
COPILOT_DOWN_BYPASS_NOTE,
|
|
80
82
|
FIXTURE_DEFAULT_PENDING_DETAIL,
|
|
81
83
|
FIXTURE_DEFAULT_THREADS_DETAIL,
|
|
82
84
|
FIXTURE_KEY_CODEX_CLEAN_AT,
|
|
83
|
-
|
|
85
|
+
FIXTURE_KEY_CODEX_TIER,
|
|
84
86
|
FIXTURE_KEY_HEAD_SHA,
|
|
85
87
|
FIXTURE_KEY_PENDING_REVIEWS_DETAIL,
|
|
86
88
|
FIXTURE_KEY_PENDING_REVIEWS_PASSED,
|
|
@@ -90,6 +92,7 @@ from pr_converge_scripts_constants.convergence_gate_constants import (
|
|
|
90
92
|
FIXTURE_KEY_UNRESOLVED_BOT_THREADS_PASSED,
|
|
91
93
|
MINIMUM_ABBREVIATED_SHA_LENGTH,
|
|
92
94
|
PR_CONVERGE_STATE_FILENAME,
|
|
95
|
+
SHARED_PACKAGE_ROOT_PARENT_INDEX,
|
|
93
96
|
)
|
|
94
97
|
from pr_converge_skill_constants.constants import (
|
|
95
98
|
ALL_COPILOT_CLEAN_REVIEW_STATES,
|
|
@@ -128,7 +131,7 @@ class ConvergenceFixture(NamedTuple):
|
|
|
128
131
|
unresolved_bot_threads_detail: str
|
|
129
132
|
is_no_pending_reviews: bool
|
|
130
133
|
pending_reviews_detail: str
|
|
131
|
-
|
|
134
|
+
codex_tier: str | None
|
|
132
135
|
codex_clean_at: str | None
|
|
133
136
|
|
|
134
137
|
|
|
@@ -333,24 +336,24 @@ def _sha_matches_head(stamp_sha: str, head_sha: str) -> bool:
|
|
|
333
336
|
|
|
334
337
|
def _evaluate_codex_clean(
|
|
335
338
|
*,
|
|
336
|
-
|
|
339
|
+
read_codex_tier: Callable[[], str | None],
|
|
337
340
|
codex_clean_at: str | None,
|
|
338
341
|
head_sha: str,
|
|
339
342
|
) -> tuple[bool, str]:
|
|
340
|
-
"""Return whether the conditional Codex gate passes for the given stamp and
|
|
343
|
+
"""Return whether the conditional Codex gate passes for the given stamp and account tier.
|
|
341
344
|
|
|
342
345
|
::
|
|
343
346
|
|
|
344
|
-
clean stamp on head
|
|
345
|
-
no stamp,
|
|
346
|
-
no stamp,
|
|
347
|
+
clean stamp on head -> pass (picker never runs)
|
|
348
|
+
no stamp, tier luna, wait, or None -> skip (never blocks)
|
|
349
|
+
no stamp, tier normal -> fail
|
|
347
350
|
|
|
348
|
-
A stamp on the current HEAD settles the gate on its own, so the
|
|
349
|
-
stays behind it: the
|
|
350
|
-
already-clean tick has no reason to pay for
|
|
351
|
+
A stamp on the current HEAD settles the gate on its own, so the picker
|
|
352
|
+
stays behind it: the picker starts one Codex app server per account, and
|
|
353
|
+
the common already-clean tick has no reason to pay for them.
|
|
351
354
|
|
|
352
355
|
Args:
|
|
353
|
-
|
|
356
|
+
read_codex_tier: Returns the picker's tier, or None when the picker cannot say.
|
|
354
357
|
codex_clean_at: HEAD SHA where Codex last reported clean, or None.
|
|
355
358
|
head_sha: Current PR HEAD commit SHA.
|
|
356
359
|
|
|
@@ -359,32 +362,38 @@ def _evaluate_codex_clean(
|
|
|
359
362
|
"""
|
|
360
363
|
if codex_clean_at is not None and _sha_matches_head(codex_clean_at, head_sha):
|
|
361
364
|
return True, CODEX_CLEAN_DETAIL_TEMPLATE % _short_sha(head_sha)
|
|
362
|
-
if
|
|
365
|
+
if read_codex_tier() != CODEX_TIER_NORMAL:
|
|
363
366
|
return True, CODEX_SKIPPED_USAGE_DETAIL
|
|
364
367
|
return False, CODEX_MISSING_CLEAN_DETAIL_TEMPLATE % _short_sha(head_sha)
|
|
365
368
|
|
|
366
369
|
|
|
367
|
-
def
|
|
368
|
-
"""
|
|
370
|
+
def _codex_account_picker_path() -> Path:
|
|
371
|
+
"""Return the installed Codex account picker beside this shared tree."""
|
|
372
|
+
shared_root = Path(__file__).resolve().parents[SHARED_PACKAGE_ROOT_PARENT_INDEX]
|
|
373
|
+
return shared_root.joinpath(*ALL_CODEX_ACCOUNT_PICKER_RELATIVE_PARTS)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _read_codex_tier() -> str | None:
|
|
377
|
+
"""Ask the Codex account picker which tier a review runs on, or None when it cannot say."""
|
|
369
378
|
try:
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
379
|
+
completed = subprocess.run(
|
|
380
|
+
[
|
|
381
|
+
sys.executable,
|
|
382
|
+
str(_codex_account_picker_path()),
|
|
383
|
+
CODEX_ACCOUNT_PICKER_CHOOSE_COMMAND,
|
|
384
|
+
],
|
|
385
|
+
capture_output=True,
|
|
386
|
+
text=True,
|
|
387
|
+
check=False,
|
|
388
|
+
timeout=CODEX_ACCOUNT_PICK_TIMEOUT_SECONDS,
|
|
389
|
+
)
|
|
390
|
+
answer = json.loads(completed.stdout)
|
|
391
|
+
except (OSError, subprocess.SubprocessError, json.JSONDecodeError):
|
|
381
392
|
return None
|
|
382
|
-
|
|
383
|
-
if isinstance(raw_percent, bool):
|
|
393
|
+
if completed.returncode != 0 or not isinstance(answer, dict):
|
|
384
394
|
return None
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
return None
|
|
395
|
+
tier = answer.get(CODEX_TIER_KEY)
|
|
396
|
+
return tier if isinstance(tier, str) else None
|
|
388
397
|
|
|
389
398
|
|
|
390
399
|
def _read_job_state() -> dict[str, object]:
|
|
@@ -468,11 +477,11 @@ def _codex_condition(context: GateContext) -> GateCondition:
|
|
|
468
477
|
if context.is_codex_down:
|
|
469
478
|
return (CODEX_GATE_LABEL, (True, CODEX_BYPASS_DETAIL))
|
|
470
479
|
if context.fixture is not None:
|
|
471
|
-
|
|
480
|
+
fixture_codex_tier = context.fixture.codex_tier
|
|
472
481
|
return (
|
|
473
482
|
CODEX_GATE_LABEL,
|
|
474
483
|
_evaluate_codex_clean(
|
|
475
|
-
|
|
484
|
+
read_codex_tier=lambda: fixture_codex_tier,
|
|
476
485
|
codex_clean_at=context.fixture.codex_clean_at,
|
|
477
486
|
head_sha=context.head_sha,
|
|
478
487
|
),
|
|
@@ -480,7 +489,7 @@ def _codex_condition(context: GateContext) -> GateCondition:
|
|
|
480
489
|
return (
|
|
481
490
|
CODEX_GATE_LABEL,
|
|
482
491
|
_evaluate_codex_clean(
|
|
483
|
-
|
|
492
|
+
read_codex_tier=_read_codex_tier,
|
|
484
493
|
codex_clean_at=context.live_codex_clean_at,
|
|
485
494
|
head_sha=context.head_sha,
|
|
486
495
|
),
|
|
@@ -623,17 +632,13 @@ def _load_convergence_fixture(from_path: Path) -> ConvergenceFixture:
|
|
|
623
632
|
raise ValueError("fixture pending_reviews_passed must be a bool")
|
|
624
633
|
if not isinstance(pending_detail, str):
|
|
625
634
|
raise ValueError("fixture pending_reviews_detail must be a string")
|
|
626
|
-
|
|
635
|
+
codex_tier = payload.get(FIXTURE_KEY_CODEX_TIER)
|
|
627
636
|
codex_clean_at = payload.get(FIXTURE_KEY_CODEX_CLEAN_AT)
|
|
628
|
-
if isinstance(
|
|
629
|
-
raise ValueError("fixture
|
|
630
|
-
if codex_percent_left is not None and not isinstance(codex_percent_left, (int, float)):
|
|
631
|
-
raise ValueError("fixture codex_percent_left must be a number or null")
|
|
637
|
+
if codex_tier is not None and not isinstance(codex_tier, str):
|
|
638
|
+
raise ValueError("fixture codex_tier must be a string or null")
|
|
632
639
|
if codex_clean_at is not None and not isinstance(codex_clean_at, str):
|
|
633
640
|
raise ValueError("fixture codex_clean_at must be a string or null")
|
|
634
|
-
|
|
635
|
-
float(codex_percent_left) if isinstance(codex_percent_left, (int, float)) else None
|
|
636
|
-
)
|
|
641
|
+
typed_codex_tier: str | None = codex_tier if isinstance(codex_tier, str) else None
|
|
637
642
|
typed_codex_clean_at: str | None = codex_clean_at if isinstance(codex_clean_at, str) else None
|
|
638
643
|
return ConvergenceFixture(
|
|
639
644
|
head_sha=head_sha,
|
|
@@ -643,7 +648,7 @@ def _load_convergence_fixture(from_path: Path) -> ConvergenceFixture:
|
|
|
643
648
|
unresolved_bot_threads_detail=threads_detail,
|
|
644
649
|
is_no_pending_reviews=is_pending_ok,
|
|
645
650
|
pending_reviews_detail=pending_detail,
|
|
646
|
-
|
|
651
|
+
codex_tier=typed_codex_tier,
|
|
647
652
|
codex_clean_at=typed_codex_clean_at,
|
|
648
653
|
)
|
|
649
654
|
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
CODEX_BINARY_NAME = "codex"
|
|
6
|
+
WINDOWS_OS_NAME = "nt"
|
|
7
|
+
WINDOWS_COMMAND_SHELL = "cmd"
|
|
8
|
+
WINDOWS_COMMAND_SHELL_RUN_FLAG = "/c"
|
|
9
|
+
ALL_WINDOWS_SCRIPT_SUFFIXES = (".cmd", ".bat")
|
|
6
10
|
EXEC_SUBCOMMAND = "exec"
|
|
7
11
|
REVIEW_SUBCOMMAND = "review"
|
|
8
12
|
HELP_FLAG = "--help"
|