claude-dev-env 2.35.0 → 4.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 (99) hide show
  1. package/.agents/skills/_shared/advisor/scripts/README.md +1 -1
  2. package/.agents/skills/autoconverge/SKILL.md +19 -15
  3. package/.agents/skills/autoconverge/reference/gotchas.md +3 -3
  4. package/.agents/skills/autoconverge/reference/multi-pr.md +6 -2
  5. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +11 -0
  6. package/.agents/skills/autoconverge/workflow/converge.mjs +26 -33
  7. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +26 -2
  8. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +12 -28
  9. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +16 -0
  10. package/.agents/skills/team-advisor/SKILL.md +11 -11
  11. package/_shared/advisor/advisor-protocol.md +51 -51
  12. package/_shared/advisor/reference/advisor-block.md +9 -9
  13. package/_shared/advisor/reference/astra-rung.md +39 -0
  14. package/_shared/advisor/reference/identity.md +4 -4
  15. package/_shared/advisor/reference/lifecycle.md +6 -6
  16. package/_shared/advisor/reference/spawn-walk-log.md +9 -9
  17. package/_shared/advisor/reference/third-party-bind.md +11 -11
  18. package/_shared/advisor/reference/warm-up.md +13 -13
  19. package/_shared/advisor/scripts/codex_astra_advisor.py +308 -0
  20. package/_shared/advisor/scripts/codex_astra_preflight.py +115 -0
  21. package/_shared/advisor/scripts/codex_astra_reply.py +159 -0
  22. package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +3 -3
  23. package/_shared/advisor/scripts/config/advisor_scripts_constants/astra_advisor_constants.py +38 -0
  24. package/_shared/advisor/scripts/model_tier_run_validator.py +17 -18
  25. package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +271 -0
  26. package/_shared/advisor/scripts/tests/test_codex_astra_preflight.py +24 -0
  27. package/_shared/advisor/scripts/tests/test_codex_astra_reply.py +42 -0
  28. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +55 -30
  29. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +14 -4
  30. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +14 -0
  31. package/_shared/pr-loop/scripts/terminology_sweep.py +138 -50
  32. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -1
  33. package/bin/cde.mjs +175 -0
  34. package/bin/cde.test.mjs +222 -0
  35. package/bin/install-constants.mjs +4 -0
  36. package/bin/install.cursor-rules.test.mjs +233 -2
  37. package/bin/install.mjs +49 -3
  38. package/bin/pstack-model-preferences.codex.json +23 -0
  39. package/bin/select-pstack-models.test.mjs +378 -0
  40. package/docs/references/team-advisor-skill.md +3 -3
  41. package/hooks/hooks_constants/tool_names.py +20 -0
  42. package/hooks/validators/README.md +35 -1
  43. package/hooks/validators/fast_save_validators.py +63 -89
  44. package/hooks/validators/run_all_validators.py +16 -8
  45. package/hooks/validators/test_fast_save_validators_policy_lint.py +25 -0
  46. package/hooks/validators/test_run_all_validators_policy_lint.py +52 -0
  47. package/package.json +3 -2
  48. package/rules/pstack-models.md +119 -0
  49. package/scripts/cde_lint.py +258 -0
  50. package/scripts/check.ps1 +18 -6
  51. package/scripts/policy_lint/__init__.py +41 -0
  52. package/scripts/policy_lint/adapter_configuration.py +91 -0
  53. package/scripts/policy_lint/adapter_detectors.py +368 -0
  54. package/scripts/policy_lint/adapter_pairing.py +145 -0
  55. package/scripts/policy_lint/adapter_support.py +97 -0
  56. package/scripts/policy_lint/adapters.py +376 -0
  57. package/scripts/policy_lint/config/__init__.py +1 -0
  58. package/scripts/policy_lint/config/constants.py +77 -0
  59. package/scripts/policy_lint/engine.py +224 -0
  60. package/scripts/policy_lint/model.py +388 -0
  61. package/scripts/policy_lint/registry.py +171 -0
  62. package/scripts/policy_lint/render.py +91 -0
  63. package/scripts/policy_lint/selection.py +372 -0
  64. package/scripts/policy_lint/selection_git.py +147 -0
  65. package/scripts/repository_checks/__init__.py +1 -0
  66. package/scripts/repository_checks/claude_md.py +108 -0
  67. package/scripts/repository_checks/config/__init__.py +1 -0
  68. package/scripts/repository_checks/config/constants.py +150 -0
  69. package/scripts/repository_checks/env_var_documentation.py +243 -0
  70. package/scripts/repository_checks/hook_modules.py +34 -0
  71. package/scripts/repository_checks/models.py +37 -0
  72. package/scripts/repository_checks/package_inventory.py +118 -0
  73. package/scripts/repository_checks/pytest_testpaths.py +84 -0
  74. package/scripts/repository_checks/reporting.py +39 -0
  75. package/scripts/repository_checks/runner.py +93 -0
  76. package/scripts/repository_checks/tracked_secrets.py +147 -0
  77. package/scripts/repository_checks/tracked_tree.py +28 -0
  78. package/scripts/repository_policy.py +120 -0
  79. package/scripts/select_pstack_models.mjs +304 -0
  80. package/scripts/tests/repository_policy_test_support.py +89 -0
  81. package/scripts/tests/test_cde_lint.py +398 -0
  82. package/scripts/tests/test_claude_md.py +68 -0
  83. package/scripts/tests/test_env_var_documentation.py +133 -0
  84. package/scripts/tests/test_hook_modules.py +17 -0
  85. package/scripts/tests/test_models.py +26 -0
  86. package/scripts/tests/test_package_inventory.py +96 -0
  87. package/scripts/tests/test_policy_lint_rules_engine.py +381 -0
  88. package/scripts/tests/test_policy_lint_rules_registry.py +369 -0
  89. package/scripts/tests/test_policy_lint_selection.py +373 -0
  90. package/scripts/tests/test_pytest_testpaths.py +66 -0
  91. package/scripts/tests/test_reporting.py +54 -0
  92. package/scripts/tests/test_repository_policy.py +71 -0
  93. package/scripts/tests/test_runner.py +59 -0
  94. package/scripts/tests/test_tracked_secrets.py +175 -0
  95. package/scripts/tests/test_tracked_tree.py +28 -0
  96. package/_shared/advisor/reference/sol-rung.md +0 -39
  97. package/_shared/advisor/scripts/codex_sol_advisor.py +0 -581
  98. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +0 -38
  99. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +0 -740
@@ -0,0 +1,271 @@
1
+ """Behavioral tests for the executable Codex Astra advisor path."""
2
+ import io
3
+ import importlib.util
4
+ import json
5
+ import subprocess
6
+ import sys
7
+ from collections.abc import Callable
8
+ from pathlib import Path
9
+ from types import ModuleType
10
+ import pytest
11
+
12
+ _ProcessRunner = Callable[..., subprocess.CompletedProcess[str]]
13
+
14
+
15
+ def _load_astra_module() -> ModuleType:
16
+ scripts_root = Path(__file__).parent.parent
17
+ config_root = str(scripts_root / "config")
18
+ if config_root not in sys.path:
19
+ sys.path.insert(0, config_root)
20
+ specification = importlib.util.spec_from_file_location(
21
+ "codex_astra_advisor", scripts_root / "codex_astra_advisor.py"
22
+ )
23
+ assert specification is not None and specification.loader is not None
24
+ module = importlib.util.module_from_spec(specification)
25
+ sys.modules[specification.name] = module
26
+ specification.loader.exec_module(module)
27
+ return module
28
+
29
+
30
+ astra_advisor = _load_astra_module()
31
+ SCRIPTS_ROOT = Path(__file__).parent.parent
32
+ USAGE_PROBE_PATH = (
33
+ SCRIPTS_ROOT.parents[1] / "pr-loop" / "scripts" / "codex_usage_probe.py"
34
+ )
35
+ ENABLED_SETTINGS = {astra_advisor.ASTRA_ENV_VAR: "1"}
36
+
37
+
38
+ @pytest.fixture(autouse=True)
39
+ def _codex_on_search_path(monkeypatch: pytest.MonkeyPatch) -> None:
40
+ monkeypatch.setattr(astra_advisor.shutil, "which", lambda name: "codex")
41
+
42
+
43
+ def _probe(payload: object, returncode: int = 0) -> subprocess.CompletedProcess[str]:
44
+ return subprocess.CompletedProcess(
45
+ [sys.executable, str(USAGE_PROBE_PATH)],
46
+ returncode,
47
+ json.dumps(payload),
48
+ "",
49
+ )
50
+
51
+
52
+ def _events(session_id: str = "thread-1", guidance: str = "PLAN\ninspect") -> str:
53
+ return "\n".join(
54
+ (
55
+ json.dumps({"type": "thread.started", "thread_id": session_id}),
56
+ json.dumps(
57
+ {
58
+ "type": "item.completed",
59
+ "item": {"type": "agent_message", "text": guidance},
60
+ }
61
+ ),
62
+ )
63
+ )
64
+
65
+
66
+ def _two_step_runner(
67
+ all_calls: list[list[str]], guidance: str = "PLAN\ninspect"
68
+ ) -> _ProcessRunner:
69
+ def runner(
70
+ arguments: list[str], **kwargs: object
71
+ ) -> subprocess.CompletedProcess[str]:
72
+ all_calls.append(arguments)
73
+ if len(all_calls) == 1:
74
+ return _probe({"percent_left": 90})
75
+ return subprocess.CompletedProcess(arguments, 0, _events(guidance=guidance), "")
76
+ return runner
77
+
78
+
79
+ @pytest.mark.parametrize("truthy", ["1", "true", "TRUE", "yes", "on", " On "])
80
+ def test_astra_flag_accepts_documented_truthy_values(truthy: str) -> None:
81
+ assert astra_advisor.is_astra_advisor_enabled({"ADVISOR_ASTRA": truthy})
82
+
83
+
84
+ def test_astra_flag_rejects_legacy_sol_setting() -> None:
85
+ assert not astra_advisor.is_astra_advisor_enabled({"ADVISOR_SOL": "1"})
86
+
87
+
88
+ def test_resolve_advisor_effort_uses_shared_setting_and_default() -> None:
89
+ assert astra_advisor.resolve_advisor_effort({"ADVISOR_EFFORT": "HIGH"}) == "high"
90
+ assert astra_advisor.resolve_advisor_effort({"ADVISOR_EFFORT": "unknown"}) == "low"
91
+
92
+
93
+ def test_resolve_codex_executable_prefers_override_and_falls_back_to_path(
94
+ monkeypatch: pytest.MonkeyPatch,
95
+ ) -> None:
96
+ assert astra_advisor.resolve_codex_executable(
97
+ {astra_advisor.ADVISOR_CODEX_EXECUTABLE_ENV_VAR: "/tmp/codex-custom"}
98
+ ) == "/tmp/codex-custom"
99
+ monkeypatch.setattr(astra_advisor.shutil, "which", lambda name: "/usr/bin/codex")
100
+ assert astra_advisor.resolve_codex_executable({}) == "/usr/bin/codex"
101
+
102
+
103
+ @pytest.mark.parametrize("effort", ["low", "medium", "high", "xhigh", "max"])
104
+ def test_codex_arguments_apply_selected_effort(effort: str) -> None:
105
+ arguments = astra_advisor.build_codex_arguments("codex", reasoning_effort=effort)
106
+ config_index = arguments.index("--config")
107
+ assert arguments[config_index + 1] == f'model_reasoning_effort="{effort}"'
108
+
109
+
110
+ def test_codex_arguments_use_astra_and_read_only_sandbox() -> None:
111
+ arguments = astra_advisor.build_codex_arguments("codex", session_id="thread-1")
112
+ assert arguments[:4] == ["codex", "exec", "--model", "gpt-6-astra"]
113
+ assert ["--sandbox", "read-only"] == arguments[6:8]
114
+ assert arguments[-3:] == ["resume", "thread-1", "-"]
115
+
116
+
117
+ def test_argument_parser_accepts_astra_and_rejects_sol() -> None:
118
+ parser = astra_advisor.build_argument_parser()
119
+ parsed = parser.parse_args(["--bind", "--cwd", ".", "--enable-astra"])
120
+ assert parsed.is_astra_requested
121
+ with pytest.raises(SystemExit):
122
+ parser.parse_args(["--bind", "--cwd", ".", "--enable-sol"])
123
+
124
+
125
+ def test_usage_probe_path_uses_supplied_home(tmp_path: Path) -> None:
126
+ path = astra_advisor.resolve_usage_probe_path(tmp_path)
127
+ assert path == tmp_path / ".claude" / "_shared" / "pr-loop" / "scripts" / "codex_usage_probe.py"
128
+
129
+
130
+ def test_preflight_accepts_meter_above_gate() -> None:
131
+ preflight = astra_advisor.run_astra_preflight(
132
+ USAGE_PROBE_PATH, lambda arguments, **kwargs: _probe({"percent_left": 90})
133
+ )
134
+ assert preflight.eligible
135
+ assert preflight.percent_left == 90
136
+
137
+
138
+ def test_preflight_declines_meter_at_gate() -> None:
139
+ preflight = astra_advisor.run_astra_preflight(
140
+ USAGE_PROBE_PATH, lambda arguments, **kwargs: _probe({"percent_left": 10})
141
+ )
142
+ assert not preflight.eligible
143
+ assert preflight.fallback_kind == astra_advisor.ASTRA_FALLBACK_KIND_DECLINED
144
+
145
+
146
+ @pytest.mark.parametrize("payload", [{"percent_left": None}, {"percent_left": "90"}, ["bad"]])
147
+ def test_preflight_rejects_malformed_meter(payload: object) -> None:
148
+ preflight = astra_advisor.run_astra_preflight(
149
+ USAGE_PROBE_PATH, lambda arguments, **kwargs: _probe(payload)
150
+ )
151
+ assert not preflight.eligible
152
+ assert preflight.fallback_kind == astra_advisor.ASTRA_FALLBACK_KIND_BROKEN
153
+
154
+
155
+ @pytest.mark.parametrize("signal", ["ENDORSE", "CORRECTION", "PLAN", "STOP"])
156
+ def test_jsonl_parser_accepts_guidance_signals(signal: str) -> None:
157
+ reply = astra_advisor.parse_codex_jsonl_reply(
158
+ _events(guidance=f"{signal}\nmore"), None, True
159
+ )
160
+ assert reply.successful
161
+ assert reply.signal == signal
162
+
163
+
164
+ @pytest.mark.parametrize(
165
+ "jsonl_text",
166
+ [
167
+ "{broken",
168
+ json.dumps({"type": "thread.started", "thread_id": "thread-1"}),
169
+ _events(guidance="PLAN: inspect"),
170
+ ],
171
+ )
172
+ def test_jsonl_parser_falls_back_for_invalid_reply(jsonl_text: str) -> None:
173
+ reply = astra_advisor.parse_codex_jsonl_reply(jsonl_text, None, True)
174
+ assert reply.is_fallback
175
+ assert not reply.successful
176
+
177
+
178
+ def test_bind_runs_probe_then_codex() -> None:
179
+ calls: list[list[str]] = []
180
+ reply = astra_advisor.run_codex_astra_advisor(
181
+ "consult",
182
+ Path("."),
183
+ None,
184
+ USAGE_PROBE_PATH,
185
+ ENABLED_SETTINGS,
186
+ None,
187
+ _two_step_runner(calls),
188
+ )
189
+ assert reply.successful
190
+ assert calls[1] == astra_advisor.build_codex_arguments("codex")
191
+
192
+
193
+ def test_disabled_flag_returns_declined_fallback() -> None:
194
+ reply = astra_advisor.run_codex_astra_advisor(
195
+ "consult",
196
+ Path("."),
197
+ astra_advisor.AstraPreflight(True, 90, "ok"),
198
+ None,
199
+ {},
200
+ None,
201
+ subprocess.run,
202
+ )
203
+ assert reply.is_fallback
204
+ assert reply.fallback_kind == astra_advisor.ASTRA_FALLBACK_KIND_DECLINED
205
+
206
+
207
+ def test_missing_codex_returns_broken_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
208
+ monkeypatch.setattr(astra_advisor.shutil, "which", lambda name: None)
209
+ reply = astra_advisor.run_codex_astra_advisor(
210
+ "consult", Path("."), None, USAGE_PROBE_PATH, ENABLED_SETTINGS, None, subprocess.run
211
+ )
212
+ assert reply.is_fallback
213
+ assert reply.fallback_kind == astra_advisor.ASTRA_FALLBACK_KIND_BROKEN
214
+
215
+
216
+ def test_resume_uses_existing_session() -> None:
217
+ calls: list[list[str]] = []
218
+ reply = astra_advisor.run_codex_astra_advisor(
219
+ "resume",
220
+ Path("."),
221
+ None,
222
+ USAGE_PROBE_PATH,
223
+ ENABLED_SETTINGS,
224
+ "thread-1",
225
+ _two_step_runner(calls, "ENDORSE\nready"),
226
+ )
227
+ assert reply.successful
228
+ assert calls[1][-3:] == ["resume", "thread-1", "-"]
229
+
230
+
231
+ def test_main_serializes_result_and_astra_fields(
232
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
233
+ ) -> None:
234
+ monkeypatch.setattr(
235
+ astra_advisor,
236
+ "run_codex_astra_advisor",
237
+ lambda *args, **kwargs: astra_advisor.CodexAstraAdvisorReply(
238
+ "thread-1", "PLAN\ninspect", True, None, False, "PLAN", True, "Astra", "codex", None
239
+ ),
240
+ )
241
+ monkeypatch.setattr(sys, "stdin", io.StringIO("consult"))
242
+ assert astra_advisor.main(["--bind", "--cwd", "."]) == 0
243
+ payload = json.loads(capsys.readouterr().out)
244
+ assert payload["result"] == "codex"
245
+ assert payload["astra_enabled"] is True
246
+ assert "sol_enabled" not in payload
247
+
248
+
249
+ def test_active_docs_use_astra_names() -> None:
250
+ package_root = SCRIPTS_ROOT.parents[2]
251
+ advisor_root = SCRIPTS_ROOT.parent
252
+ all_paths = [
253
+ advisor_root / "advisor-protocol.md",
254
+ *sorted((advisor_root / "reference").glob("*.md")),
255
+ package_root / ".agents" / "skills" / "team-advisor" / "SKILL.md",
256
+ package_root / "docs" / "references" / "team-advisor-skill.md",
257
+ ]
258
+ legacy_names = ("ADVISOR_SOL", "--enable-sol", "codex_sol_advisor", "sol-rung.md")
259
+ for each_path in all_paths:
260
+ content = each_path.read_text(encoding="utf-8")
261
+ assert all(name not in content for name in legacy_names)
262
+
263
+
264
+ def test_astra_docs_name_shared_effort_and_helper() -> None:
265
+ advisor_root = SCRIPTS_ROOT.parent
266
+ rung = (advisor_root / "reference" / "astra-rung.md").read_text(encoding="utf-8")
267
+ protocol = (advisor_root / "advisor-protocol.md").read_text(encoding="utf-8")
268
+ assert "ADVISOR_EFFORT" in rung
269
+ assert "ADVISOR_ASTRA=1" in protocol
270
+ assert "codex_astra_advisor.py" in rung
271
+ assert "--resume <session_id>" in rung
@@ -0,0 +1,24 @@
1
+ """Tests for Codex Astra usage preflight."""
2
+
3
+ import json
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ SCRIPTS_ROOT = Path(__file__).parent.parent
9
+ sys.path.insert(0, str(SCRIPTS_ROOT / "config"))
10
+ sys.path.insert(0, str(SCRIPTS_ROOT))
11
+
12
+ from codex_astra_preflight import run_astra_preflight
13
+
14
+
15
+ def test_run_astra_preflight_rejects_probe_failure(tmp_path: Path) -> None:
16
+ probe_path = tmp_path / "codex_usage_probe.py"
17
+
18
+ def process_runner(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]:
19
+ return subprocess.CompletedProcess([], 1, json.dumps({}), "")
20
+
21
+ preflight = run_astra_preflight(probe_path, process_runner)
22
+
23
+ assert not preflight.eligible
24
+ assert preflight.fallback_kind == "broken"
@@ -0,0 +1,42 @@
1
+ """Tests for typed Codex Astra replies."""
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ SCRIPTS_ROOT = Path(__file__).parent.parent
8
+ sys.path.insert(0, str(SCRIPTS_ROOT / "config"))
9
+ sys.path.insert(0, str(SCRIPTS_ROOT))
10
+
11
+ from codex_astra_reply import (
12
+ build_fallback_reply,
13
+ build_success_reply,
14
+ parse_codex_jsonl_reply,
15
+ )
16
+
17
+
18
+ def test_build_fallback_reply_preserves_fallback_kind() -> None:
19
+ reply = build_fallback_reply("declined", True, "declined")
20
+
21
+ assert reply.is_fallback
22
+ assert reply.fallback_kind == "declined"
23
+
24
+
25
+ def test_build_success_reply_preserves_guidance_fields() -> None:
26
+ reply = build_success_reply("thread-1", "PLAN\ninspect", "PLAN")
27
+
28
+ assert reply.successful
29
+ assert reply.session_id == "thread-1"
30
+ assert reply.signal == "PLAN"
31
+
32
+
33
+ def test_parse_codex_jsonl_reply_preserves_fallback_kind() -> None:
34
+ reply = parse_codex_jsonl_reply(
35
+ json.dumps({"type": "thread.started", "thread_id": "thread-1"}),
36
+ None,
37
+ True,
38
+ "broken",
39
+ )
40
+
41
+ assert reply.is_fallback
42
+ assert reply.fallback_kind == "broken"
@@ -47,7 +47,7 @@ def test_clean_single_spawn_at_top_of_slice_passes() -> None:
47
47
  assert validate_model_tier_run(run) is None
48
48
 
49
49
 
50
- def test_sol_codex_bind_succeeds_after_fable_when_enabled() -> None:
50
+ def test_astra_codex_bind_succeeds_after_fable_when_enabled() -> None:
51
51
  run = ModelTierRun(
52
52
  own_tier="Opus",
53
53
  candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
@@ -56,35 +56,35 @@ def test_sol_codex_bind_succeeds_after_fable_when_enabled() -> None:
56
56
  {"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
57
57
  ],
58
58
  selected_tier=ADVISOR_MODEL_TIER,
59
- is_sol_enabled=True,
59
+ is_astra_enabled=True,
60
60
  )
61
61
  assert validate_model_tier_run(run) is None
62
62
 
63
63
 
64
- def test_fable_success_with_sol_enabled_stops_before_sol() -> None:
64
+ def test_fable_success_with_astra_enabled_stops_before_astra() -> None:
65
65
  run = ModelTierRun(
66
66
  own_tier="Opus",
67
67
  candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
68
68
  attempts=[{"tier": "Fable", "result": "spawned"}],
69
69
  selected_tier="Fable",
70
- is_sol_enabled=True,
70
+ is_astra_enabled=True,
71
71
  )
72
72
  assert validate_model_tier_run(run) is None
73
73
 
74
74
 
75
- def test_sol_first_walk_raises_when_sol_is_enabled() -> None:
75
+ def test_astra_first_walk_raises_when_astra_is_enabled() -> None:
76
76
  run = ModelTierRun(
77
77
  own_tier="Opus",
78
78
  candidate_tiers=[ADVISOR_MODEL_TIER, "Fable"],
79
79
  attempts=[{"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN}],
80
80
  selected_tier=ADVISOR_MODEL_TIER,
81
- is_sol_enabled=True,
81
+ is_astra_enabled=True,
82
82
  )
83
83
  with pytest.raises(ModelTierRunError):
84
84
  validate_model_tier_run(run)
85
85
 
86
86
 
87
- def test_sol_rung_follows_fable_on_third_party_cli_floor_when_enabled() -> None:
87
+ def test_astra_rung_follows_fable_on_third_party_cli_floor_when_enabled() -> None:
88
88
  run = ModelTierRun(
89
89
  own_tier="ThirdParty",
90
90
  candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
@@ -93,13 +93,14 @@ def test_sol_rung_follows_fable_on_third_party_cli_floor_when_enabled() -> None:
93
93
  {"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
94
94
  ],
95
95
  selected_tier=ADVISOR_MODEL_TIER,
96
- is_sol_enabled=True,
96
+ is_astra_enabled=True,
97
+ host_profile="ThirdParty",
97
98
  )
98
99
 
99
100
  assert validate_model_tier_run(run) is None
100
101
 
101
102
 
102
- def test_sol_codex_result_requires_sol_candidate() -> None:
103
+ def test_astra_codex_result_requires_astra_candidate() -> None:
103
104
  run = ModelTierRun(
104
105
  own_tier="Opus",
105
106
  candidate_tiers=["Fable"],
@@ -110,7 +111,7 @@ def test_sol_codex_result_requires_sol_candidate() -> None:
110
111
  validate_model_tier_run(run)
111
112
 
112
113
 
113
- def test_sol_spawned_result_does_not_count_as_codex_success() -> None:
114
+ def test_astra_spawned_result_does_not_count_as_codex_success() -> None:
114
115
  run = ModelTierRun(
115
116
  own_tier="Opus",
116
117
  candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
@@ -119,7 +120,7 @@ def test_sol_spawned_result_does_not_count_as_codex_success() -> None:
119
120
  {"tier": ADVISOR_MODEL_TIER, "result": "spawned"},
120
121
  ],
121
122
  selected_tier=ADVISOR_MODEL_TIER,
122
- is_sol_enabled=True,
123
+ is_astra_enabled=True,
123
124
  )
124
125
  with pytest.raises(ModelTierRunError):
125
126
  validate_model_tier_run(run)
@@ -178,7 +179,7 @@ def test_attempts_out_of_ladder_order_raises() -> None:
178
179
  {"tier": "Fable", "result": "unavailable"},
179
180
  ],
180
181
  selected_tier=ADVISOR_MODEL_TIER,
181
- is_sol_enabled=True,
182
+ is_astra_enabled=True,
182
183
  )
183
184
  with pytest.raises(ModelTierRunError):
184
185
  validate_model_tier_run(run)
@@ -193,7 +194,7 @@ def test_selected_tier_not_first_spawned_attempt_raises() -> None:
193
194
  {"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
194
195
  ],
195
196
  selected_tier="Fable",
196
- is_sol_enabled=True,
197
+ is_astra_enabled=True,
197
198
  )
198
199
  with pytest.raises(
199
200
  ModelTierRunError,
@@ -251,14 +252,14 @@ def test_empty_attempts_with_null_selected_tier_raises() -> None:
251
252
  validate_model_tier_run(run)
252
253
 
253
254
 
254
- def test_incomplete_fallback_walk_before_sol_raises() -> None:
255
+ def test_incomplete_fallback_walk_before_astra_raises() -> None:
255
256
  run = ModelTierRun(
256
257
  own_tier="Opus",
257
258
  candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
258
259
  attempts=[{"tier": "Fable", "result": "unavailable"}],
259
260
  selected_tier=None,
260
- fallback_reason="stopped after Fable without trying Sol",
261
- is_sol_enabled=True,
261
+ fallback_reason="stopped after Fable without trying Astra",
262
+ is_astra_enabled=True,
262
263
  )
263
264
  with pytest.raises(ModelTierRunError):
264
265
  validate_model_tier_run(run)
@@ -310,8 +311,8 @@ def test_cli_rejects_non_string_host_profile(tmp_path: Path) -> None:
310
311
  assert main([str(log_path)]) == 2
311
312
 
312
313
 
313
- def test_cli_rejects_non_boolean_sol_enabled(tmp_path: Path) -> None:
314
- log_path = tmp_path / "invalid-sol-enabled.json"
314
+ def test_cli_rejects_non_boolean_astra_enabled(tmp_path: Path) -> None:
315
+ log_path = tmp_path / "invalid-astra-enabled.json"
315
316
  log_path.write_text(
316
317
  json.dumps(
317
318
  {
@@ -319,7 +320,7 @@ def test_cli_rejects_non_boolean_sol_enabled(tmp_path: Path) -> None:
319
320
  "candidate_tiers": ["Fable"],
320
321
  "attempts": [{"tier": "Fable", "result": "spawned"}],
321
322
  "selected_tier": "Fable",
322
- "sol_enabled": "false",
323
+ "astra_enabled": "false",
323
324
  }
324
325
  ),
325
326
  encoding="utf-8",
@@ -333,11 +334,11 @@ def test_cli_rejects_incomplete_fallback_log(tmp_path: Path) -> None:
333
334
  json.dumps(
334
335
  {
335
336
  "own_tier": "Opus",
336
- "candidate_tiers": ["Fable", "Sol"],
337
+ "candidate_tiers": ["Fable", "Astra"],
337
338
  "attempts": [{"tier": "Fable", "result": "unavailable"}],
338
339
  "selected_tier": None,
339
340
  "fallback_reason": "incomplete",
340
- "sol_enabled": True,
341
+ "astra_enabled": True,
341
342
  }
342
343
  ),
343
344
  encoding="utf-8",
@@ -345,6 +346,30 @@ def test_cli_rejects_incomplete_fallback_log(tmp_path: Path) -> None:
345
346
  assert main([str(log_path)]) == 1
346
347
 
347
348
 
349
+ def test_cli_loads_astra_enabled_fallback_walk(tmp_path: Path) -> None:
350
+ log_path = tmp_path / "astra-enabled-walk.json"
351
+ log_path.write_text(
352
+ json.dumps(
353
+ {
354
+ "own_tier": "ThirdParty",
355
+ "host_profile": "ThirdParty",
356
+ "candidate_tiers": ["Fable", "Astra"],
357
+ "attempts": [
358
+ {"tier": "Fable", "result": "unavailable"},
359
+ {"tier": "Astra", "result": "codex"},
360
+ ],
361
+ "selected_tier": "Astra",
362
+ "astra_enabled": True,
363
+ }
364
+ ),
365
+ encoding="utf-8",
366
+ )
367
+ assert main([str(log_path)]) == 0
368
+ loaded_run = load_model_tier_run_from_json_path(from_path=log_path)
369
+ assert loaded_run.is_astra_enabled
370
+ assert loaded_run.selected_tier == "Astra"
371
+
372
+
348
373
  def test_cli_missing_path_returns_usage_exit_code() -> None:
349
374
  assert main([]) == 2
350
375
 
@@ -472,7 +497,7 @@ def test_claude_host_self_token_is_not_spawn_success_raises() -> None:
472
497
  validate_model_tier_run(run)
473
498
 
474
499
 
475
- def test_codex_host_sol_in_session_spawn_passes() -> None:
500
+ def test_codex_host_astra_in_session_spawn_passes() -> None:
476
501
  run = ModelTierRun(
477
502
  own_tier="Opus",
478
503
  candidate_tiers=[ADVISOR_MODEL_TIER],
@@ -483,7 +508,7 @@ def test_codex_host_sol_in_session_spawn_passes() -> None:
483
508
  assert validate_model_tier_run(run) is None
484
509
 
485
510
 
486
- def test_codex_host_sol_codex_token_counts_as_success() -> None:
511
+ def test_codex_host_astra_codex_token_counts_as_success() -> None:
487
512
  run = ModelTierRun(
488
513
  own_tier="Opus",
489
514
  candidate_tiers=[ADVISOR_MODEL_TIER],
@@ -496,26 +521,26 @@ def test_codex_host_sol_codex_token_counts_as_success() -> None:
496
521
  assert validate_model_tier_run(run) is None
497
522
 
498
523
 
499
- def test_codex_host_fable_then_sol_walk_raises() -> None:
524
+ def test_codex_host_fable_then_astra_walk_raises() -> None:
500
525
  run = ModelTierRun(
501
526
  own_tier="Opus",
502
527
  candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
503
528
  attempts=[{"tier": ADVISOR_MODEL_TIER, "result": "spawned"}],
504
529
  selected_tier=ADVISOR_MODEL_TIER,
505
- is_sol_enabled=True,
530
+ is_astra_enabled=True,
506
531
  host_profile="Codex",
507
532
  )
508
533
  with pytest.raises(ModelTierRunError):
509
534
  validate_model_tier_run(run)
510
535
 
511
536
 
512
- def test_codex_host_exhausted_sol_fails_closed() -> None:
537
+ def test_codex_host_exhausted_astra_fails_closed() -> None:
513
538
  run = ModelTierRun(
514
539
  own_tier="Opus",
515
540
  candidate_tiers=[ADVISOR_MODEL_TIER],
516
541
  attempts=[{"tier": ADVISOR_MODEL_TIER, "result": "unavailable"}],
517
542
  selected_tier=None,
518
- fallback_reason="Codex in-session Sol spawn did not bind",
543
+ fallback_reason="Codex in-session Astra spawn did not bind",
519
544
  host_profile="Codex",
520
545
  )
521
546
  assert validate_model_tier_run(run) is None
@@ -527,9 +552,9 @@ def test_cli_loads_codex_host_profile(tmp_path: Path) -> None:
527
552
  json.dumps(
528
553
  {
529
554
  "own_tier": "Opus",
530
- "candidate_tiers": ["Sol"],
531
- "attempts": [{"tier": "Sol", "result": "spawned"}],
532
- "selected_tier": "Sol",
555
+ "candidate_tiers": ["Astra"],
556
+ "attempts": [{"tier": "Astra", "result": "spawned"}],
557
+ "selected_tier": "Astra",
533
558
  "host_profile": "Codex",
534
559
  }
535
560
  ),
@@ -105,10 +105,19 @@ def test_sendmessage_reply_wait_is_positive_bound() -> None:
105
105
  assert ADVISOR_SENDMESSAGE_REPLY_WAIT_SECONDS == 120
106
106
 
107
107
 
108
- def test_resolve_codex_model_id_maps_sol() -> None:
109
- assert resolve_codex_model_id(f" {ADVISOR_MODEL_TIER.lower()} ") == (
110
- ADVISOR_CODEX_MODEL_ID
111
- )
108
+ @pytest.mark.parametrize("tier_name", ["Astra", "astra", "ASTRA", " Astra "])
109
+ def test_resolve_codex_model_id_maps_astra(tier_name: str) -> None:
110
+ assert resolve_codex_model_id(tier_name) == "gpt-6-astra"
111
+
112
+
113
+ def test_resolve_codex_model_id_rejects_old_sol_tier() -> None:
114
+ with pytest.raises(ValueError, match="not a known model tier"):
115
+ resolve_codex_model_id("Sol")
116
+
117
+
118
+ def test_resolve_cli_model_id_rejects_astra_as_a_claude_alias() -> None:
119
+ with pytest.raises(ValueError, match="not a known model tier"):
120
+ resolve_cli_model_id("Astra")
112
121
 
113
122
 
114
123
  def test_resolve_codex_model_id_rejects_claude_tier() -> None:
@@ -132,6 +141,7 @@ def test_cli_model_alias_map_keys_match_known_tiers() -> None:
132
141
  def test_canonical_tier_name_strips_and_normalizes() -> None:
133
142
  assert canonical_tier_name(" opus ") == "Opus"
134
143
  assert canonical_tier_name("thirdparty") == "ThirdParty"
144
+ assert canonical_tier_name(" astra ") == "Astra"
135
145
  assert canonical_tier_name("") is None
136
146
  assert canonical_tier_name("Titan") is None
137
147
 
@@ -109,6 +109,20 @@ ALL_GIT_DIFF_CACHED_UNIFIED_ZERO_COMMAND: tuple[str, ...] = (
109
109
 
110
110
  GIT_DIFF_SUBPROCESS_TIMEOUT_SECONDS: int = 30
111
111
 
112
+ GIT_NO_MATCH_EXIT_CODE: int = 1
113
+
114
+ GIT_OPERATION_FAILURE_TEMPLATE: str = "{operation} failed: {details}"
115
+
116
+ GIT_DIFF_OPERATION: str = "git diff --cached"
117
+
118
+ GIT_GREP_OPERATION_TEMPLATE: str = "git grep for {identifier}"
119
+
120
+ GIT_FAILURE_WITHOUT_DETAILS: str = "no diagnostic text"
121
+
122
+ GIT_TEXT_ENCODING: str = "utf-8"
123
+
124
+ GIT_DECODE_ERRORS: str = "replace"
125
+
112
126
  TERMINOLOGY_SWEEP_GATE_HEADER: str = (
113
127
  "terminology_sweep: {finding_count} cross-surface terminology near-miss(es) "
114
128
  "on staged prose:"