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.
- package/.agents/skills/_shared/advisor/scripts/README.md +1 -1
- package/.agents/skills/autoconverge/SKILL.md +19 -15
- package/.agents/skills/autoconverge/reference/gotchas.md +3 -3
- package/.agents/skills/autoconverge/reference/multi-pr.md +6 -2
- package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +11 -0
- package/.agents/skills/autoconverge/workflow/converge.mjs +26 -33
- package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +26 -2
- package/.agents/skills/autoconverge/workflow/converge_multi.mjs +12 -28
- package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +16 -0
- package/.agents/skills/team-advisor/SKILL.md +11 -11
- package/_shared/advisor/advisor-protocol.md +51 -51
- package/_shared/advisor/reference/advisor-block.md +9 -9
- package/_shared/advisor/reference/astra-rung.md +39 -0
- package/_shared/advisor/reference/identity.md +4 -4
- package/_shared/advisor/reference/lifecycle.md +6 -6
- package/_shared/advisor/reference/spawn-walk-log.md +9 -9
- package/_shared/advisor/reference/third-party-bind.md +11 -11
- package/_shared/advisor/reference/warm-up.md +13 -13
- package/_shared/advisor/scripts/codex_astra_advisor.py +308 -0
- package/_shared/advisor/scripts/codex_astra_preflight.py +115 -0
- package/_shared/advisor/scripts/codex_astra_reply.py +159 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +3 -3
- package/_shared/advisor/scripts/config/advisor_scripts_constants/astra_advisor_constants.py +38 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +17 -18
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +271 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_preflight.py +24 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_reply.py +42 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +55 -30
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +14 -4
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +14 -0
- package/_shared/pr-loop/scripts/terminology_sweep.py +138 -50
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -1
- package/bin/cde.mjs +175 -0
- package/bin/cde.test.mjs +222 -0
- package/bin/install-constants.mjs +4 -0
- package/bin/install.cursor-rules.test.mjs +233 -2
- package/bin/install.mjs +49 -3
- package/bin/pstack-model-preferences.codex.json +23 -0
- package/bin/select-pstack-models.test.mjs +378 -0
- package/docs/references/team-advisor-skill.md +3 -3
- package/hooks/hooks_constants/tool_names.py +20 -0
- package/hooks/validators/README.md +35 -1
- package/hooks/validators/fast_save_validators.py +63 -89
- package/hooks/validators/run_all_validators.py +16 -8
- package/hooks/validators/test_fast_save_validators_policy_lint.py +25 -0
- package/hooks/validators/test_run_all_validators_policy_lint.py +52 -0
- package/package.json +3 -2
- package/rules/pstack-models.md +119 -0
- package/scripts/cde_lint.py +258 -0
- package/scripts/check.ps1 +18 -6
- package/scripts/policy_lint/__init__.py +41 -0
- package/scripts/policy_lint/adapter_configuration.py +91 -0
- package/scripts/policy_lint/adapter_detectors.py +368 -0
- package/scripts/policy_lint/adapter_pairing.py +145 -0
- package/scripts/policy_lint/adapter_support.py +97 -0
- package/scripts/policy_lint/adapters.py +376 -0
- package/scripts/policy_lint/config/__init__.py +1 -0
- package/scripts/policy_lint/config/constants.py +77 -0
- package/scripts/policy_lint/engine.py +224 -0
- package/scripts/policy_lint/model.py +388 -0
- package/scripts/policy_lint/registry.py +171 -0
- package/scripts/policy_lint/render.py +91 -0
- package/scripts/policy_lint/selection.py +372 -0
- package/scripts/policy_lint/selection_git.py +147 -0
- package/scripts/repository_checks/__init__.py +1 -0
- package/scripts/repository_checks/claude_md.py +108 -0
- package/scripts/repository_checks/config/__init__.py +1 -0
- package/scripts/repository_checks/config/constants.py +150 -0
- package/scripts/repository_checks/env_var_documentation.py +243 -0
- package/scripts/repository_checks/hook_modules.py +34 -0
- package/scripts/repository_checks/models.py +37 -0
- package/scripts/repository_checks/package_inventory.py +118 -0
- package/scripts/repository_checks/pytest_testpaths.py +84 -0
- package/scripts/repository_checks/reporting.py +39 -0
- package/scripts/repository_checks/runner.py +93 -0
- package/scripts/repository_checks/tracked_secrets.py +147 -0
- package/scripts/repository_checks/tracked_tree.py +28 -0
- package/scripts/repository_policy.py +120 -0
- package/scripts/select_pstack_models.mjs +304 -0
- package/scripts/tests/repository_policy_test_support.py +89 -0
- package/scripts/tests/test_cde_lint.py +398 -0
- package/scripts/tests/test_claude_md.py +68 -0
- package/scripts/tests/test_env_var_documentation.py +133 -0
- package/scripts/tests/test_hook_modules.py +17 -0
- package/scripts/tests/test_models.py +26 -0
- package/scripts/tests/test_package_inventory.py +96 -0
- package/scripts/tests/test_policy_lint_rules_engine.py +381 -0
- package/scripts/tests/test_policy_lint_rules_registry.py +369 -0
- package/scripts/tests/test_policy_lint_selection.py +373 -0
- package/scripts/tests/test_pytest_testpaths.py +66 -0
- package/scripts/tests/test_reporting.py +54 -0
- package/scripts/tests/test_repository_policy.py +71 -0
- package/scripts/tests/test_runner.py +59 -0
- package/scripts/tests/test_tracked_secrets.py +175 -0
- package/scripts/tests/test_tracked_tree.py +28 -0
- package/_shared/advisor/reference/sol-rung.md +0 -39
- package/_shared/advisor/scripts/codex_sol_advisor.py +0 -581
- package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +0 -38
- 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
|
|
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
|
-
|
|
59
|
+
is_astra_enabled=True,
|
|
60
60
|
)
|
|
61
61
|
assert validate_model_tier_run(run) is None
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def
|
|
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
|
-
|
|
70
|
+
is_astra_enabled=True,
|
|
71
71
|
)
|
|
72
72
|
assert validate_model_tier_run(run) is None
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
261
|
-
|
|
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
|
|
314
|
-
log_path = tmp_path / "invalid-
|
|
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
|
-
"
|
|
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", "
|
|
337
|
+
"candidate_tiers": ["Fable", "Astra"],
|
|
337
338
|
"attempts": [{"tier": "Fable", "result": "unavailable"}],
|
|
338
339
|
"selected_tier": None,
|
|
339
340
|
"fallback_reason": "incomplete",
|
|
340
|
-
"
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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": ["
|
|
531
|
-
"attempts": [{"tier": "
|
|
532
|
-
"selected_tier": "
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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:"
|