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
@@ -1,740 +0,0 @@
1
- """Behavioral tests for the executable Codex Sol advisor path."""
2
-
3
- import io
4
- import importlib.util
5
- import json
6
- import subprocess
7
- import sys
8
- from collections.abc import Callable
9
- from pathlib import Path
10
- from types import ModuleType
11
-
12
- import pytest
13
-
14
- _ProcessRunner = Callable[..., subprocess.CompletedProcess[str]]
15
-
16
-
17
- def _load_sol_module() -> ModuleType:
18
- scripts_root = Path(__file__).parent.parent
19
- config_root = scripts_root / "config"
20
- sys.path.insert(0, str(config_root))
21
- specification = importlib.util.spec_from_file_location(
22
- "codex_sol_advisor", scripts_root / "codex_sol_advisor.py"
23
- )
24
- assert specification is not None and specification.loader is not None
25
- module = importlib.util.module_from_spec(specification)
26
- sys.modules[specification.name] = module
27
- specification.loader.exec_module(module)
28
- return module
29
-
30
-
31
- sol_advisor = _load_sol_module()
32
- SCRIPTS_ROOT = Path(__file__).parent.parent
33
- USAGE_PROBE_PATH = (
34
- SCRIPTS_ROOT.parents[1]
35
- / "pr-loop"
36
- / "scripts"
37
- / "codex_usage_probe.py"
38
- )
39
- WINDOWS_SHIM_PATH = r"C:\Users\me\AppData\Roaming\npm\codex.cmd"
40
- ENABLED_SETTING_BY_NAME = {sol_advisor.SOL_ENV_VAR: "1"}
41
-
42
-
43
- @pytest.fixture(autouse=True)
44
- def _codex_on_search_path(monkeypatch: pytest.MonkeyPatch) -> None:
45
- monkeypatch.setattr(
46
- sol_advisor.shutil,
47
- "which",
48
- lambda name: "codex" if name == sol_advisor.CODEX_EXECUTABLE else None,
49
- )
50
-
51
-
52
- def _probe_process(
53
- payload: object,
54
- returncode: int = 0,
55
- ) -> subprocess.CompletedProcess[str]:
56
- return subprocess.CompletedProcess(
57
- [sys.executable, str(USAGE_PROBE_PATH)],
58
- returncode,
59
- json.dumps(payload),
60
- "",
61
- )
62
-
63
-
64
- def _event_stream(
65
- session_id: str = "thread-1",
66
- guidance: str = "PLAN\ninspect the change",
67
- ) -> str:
68
- return "\n".join(
69
- [
70
- json.dumps({"type": "thread.started", "thread_id": session_id}),
71
- json.dumps(
72
- {
73
- "type": "item.completed",
74
- "item": {
75
- "type": "agent_message",
76
- "text": guidance,
77
- },
78
- }
79
- ),
80
- ]
81
- )
82
-
83
-
84
- def _two_step_process_runner(calls: list[list[str]], guidance: str) -> _ProcessRunner:
85
- def process_runner(
86
- arguments: list[str], **kwargs: object
87
- ) -> subprocess.CompletedProcess[str]:
88
- calls.append(arguments)
89
- if len(calls) == 1:
90
- return _probe_process({"percent_left": 90})
91
- return subprocess.CompletedProcess(
92
- arguments, 0, _event_stream(guidance=guidance), ""
93
- )
94
-
95
- return process_runner
96
-
97
-
98
- def test_sol_flag_accepts_documented_truthy_values() -> None:
99
- assert sol_advisor.is_sol_advisor_enabled({"ADVISOR_SOL": "yes"})
100
- assert not sol_advisor.is_sol_advisor_enabled({"ADVISOR_SOL": "0"})
101
- assert not sol_advisor.is_sol_advisor_enabled({"ADVISOR_SOL_XHIGH": "1"})
102
-
103
-
104
- def test_resolve_advisor_effort_defaults_and_accepts_documented_levels() -> None:
105
- assert sol_advisor.resolve_advisor_effort({}) == "low"
106
- assert sol_advisor.resolve_advisor_effort({"ADVISOR_EFFORT": "MEDIUM"}) == "medium"
107
- assert sol_advisor.resolve_advisor_effort({"ADVISOR_EFFORT": "nope"}) == "low"
108
- assert sol_advisor.resolve_advisor_effort({"ADVISOR_SOL_EFFORT": "high"}) == "low"
109
-
110
-
111
- @pytest.mark.parametrize(
112
- "reasoning_effort",
113
- ["low", "medium", "high", "xhigh", "max"],
114
- )
115
- def test_codex_arguments_use_selected_sol_reasoning_effort(
116
- reasoning_effort: str,
117
- ) -> None:
118
- command_arguments = sol_advisor.build_codex_arguments(
119
- "codex",
120
- reasoning_effort=reasoning_effort,
121
- )
122
- config_flag_index = command_arguments.index("--config")
123
-
124
- assert (
125
- command_arguments[config_flag_index + 1]
126
- == f'model_reasoning_effort="{reasoning_effort}"'
127
- )
128
-
129
-
130
- def test_resolve_usage_probe_path_uses_supplied_home_directory(tmp_path: Path) -> None:
131
- probe_path = sol_advisor.resolve_usage_probe_path(tmp_path)
132
-
133
- assert probe_path == (
134
- tmp_path
135
- / ".claude"
136
- / "_shared"
137
- / "pr-loop"
138
- / "scripts"
139
- / "codex_usage_probe.py"
140
- )
141
-
142
-
143
- def test_argument_parser_accepts_bind_and_resume_modes() -> None:
144
- parser = sol_advisor.build_argument_parser()
145
-
146
- bind_arguments = parser.parse_args(["--bind", "--cwd", "."])
147
- resume_arguments = parser.parse_args(
148
- ["--resume", "thread-1", "--cwd", "."]
149
- )
150
-
151
- assert bind_arguments.bind
152
- assert bind_arguments.resume is None
153
- assert resume_arguments.resume == "thread-1"
154
-
155
-
156
- def test_main_serializes_stable_result_field(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
157
- monkeypatch.setattr(
158
- sol_advisor,
159
- "run_codex_sol_advisor",
160
- lambda **kwargs: sol_advisor.CodexSolAdvisorReply(
161
- session_id="thread-1",
162
- guidance="PLAN\ninspect",
163
- successful=True,
164
- reason=None,
165
- is_fallback=False,
166
- signal="PLAN",
167
- sol_enabled=True,
168
- selected_tier=sol_advisor.ADVISOR_MODEL_TIER,
169
- outcome=sol_advisor.CODEX_BIND_SUCCESS_TOKEN,
170
- fallback_kind=None,
171
- ),
172
- )
173
- monkeypatch.setattr(sys, "stdin", io.StringIO("first consult"))
174
-
175
- exit_code = sol_advisor.main(["--bind", "--cwd", "."])
176
- payload = json.loads(capsys.readouterr().out)
177
-
178
- assert exit_code == 0
179
- assert payload["result"] == "codex"
180
- assert "outcome" not in payload
181
- assert payload["sol_enabled"] is True
182
-
183
-
184
- def test_bind_and_resume_arguments_match_installed_codex_interface() -> None:
185
- expected_common_arguments = [
186
- "codex",
187
- "exec",
188
- "--model",
189
- sol_advisor.ADVISOR_CODEX_MODEL_ID,
190
- "--config",
191
- 'model_reasoning_effort="low"',
192
- "--sandbox",
193
- "read-only",
194
- "--json",
195
- ]
196
- assert sol_advisor.build_codex_arguments("codex") == [
197
- *expected_common_arguments,
198
- "-",
199
- ]
200
- assert sol_advisor.build_codex_arguments(
201
- WINDOWS_SHIM_PATH, session_id="thread-1"
202
- ) == [
203
- WINDOWS_SHIM_PATH,
204
- *expected_common_arguments[1:],
205
- "resume",
206
- "thread-1",
207
- "-",
208
- ]
209
-
210
-
211
- def test_resolve_codex_executable_prefers_the_env_var_override(
212
- monkeypatch: pytest.MonkeyPatch,
213
- ) -> None:
214
- monkeypatch.setattr(sol_advisor.shutil, "which", lambda name: None)
215
-
216
- resolved_executable = sol_advisor.resolve_codex_executable(
217
- {sol_advisor.ADVISOR_CODEX_EXECUTABLE_ENV_VAR: WINDOWS_SHIM_PATH}
218
- )
219
-
220
- assert resolved_executable == WINDOWS_SHIM_PATH
221
-
222
-
223
- def test_resolve_codex_executable_falls_back_to_which_search(
224
- monkeypatch: pytest.MonkeyPatch,
225
- ) -> None:
226
- monkeypatch.setattr(
227
- sol_advisor.shutil,
228
- "which",
229
- lambda name: (
230
- WINDOWS_SHIM_PATH if name == sol_advisor.CODEX_EXECUTABLE else None
231
- ),
232
- )
233
-
234
- resolved_executable = sol_advisor.resolve_codex_executable({})
235
-
236
- assert resolved_executable == WINDOWS_SHIM_PATH
237
-
238
-
239
- def test_resolve_codex_executable_returns_none_when_unresolved(
240
- monkeypatch: pytest.MonkeyPatch,
241
- ) -> None:
242
- monkeypatch.setattr(sol_advisor.shutil, "which", lambda name: None)
243
-
244
- resolved_executable = sol_advisor.resolve_codex_executable({})
245
-
246
- assert resolved_executable is None
247
-
248
-
249
- def test_bind_falls_back_with_a_clear_reason_when_executable_is_missing(
250
- monkeypatch: pytest.MonkeyPatch,
251
- ) -> None:
252
- monkeypatch.setattr(sol_advisor.shutil, "which", lambda name: None)
253
- calls: list[list[str]] = []
254
-
255
- def process_runner(
256
- arguments: list[str], **kwargs: object
257
- ) -> subprocess.CompletedProcess[str]:
258
- calls.append(arguments)
259
- return _probe_process({"percent_left": 90})
260
-
261
- reply = sol_advisor.run_codex_sol_advisor(
262
- prompt="first consult",
263
- working_directory=Path("."),
264
- preflight=None,
265
- probe_path=USAGE_PROBE_PATH,
266
- session_id=None,
267
- process_runner=process_runner,
268
- setting_by_name={sol_advisor.SOL_ENV_VAR: "1"},
269
- )
270
-
271
- assert not reply.successful
272
- assert reply.is_fallback
273
- assert reply.reason is not None
274
- assert "codex" in reply.reason
275
- assert reply.fallback_kind == sol_advisor.SOL_FALLBACK_KIND_BROKEN
276
- assert calls == []
277
-
278
-
279
- def test_policy_fallbacks_are_marked_declined() -> None:
280
- def gate_closed_runner(
281
- arguments: list[str], **kwargs: object
282
- ) -> subprocess.CompletedProcess[str]:
283
- return _probe_process({"percent_left": 5})
284
-
285
- disabled_reply = sol_advisor.run_codex_sol_advisor(
286
- prompt="first consult",
287
- working_directory=Path("."),
288
- preflight=None,
289
- probe_path=USAGE_PROBE_PATH,
290
- session_id=None,
291
- process_runner=gate_closed_runner,
292
- setting_by_name={},
293
- )
294
- gate_closed_reply = sol_advisor.run_codex_sol_advisor(
295
- prompt="first consult",
296
- working_directory=Path("."),
297
- preflight=None,
298
- probe_path=USAGE_PROBE_PATH,
299
- session_id=None,
300
- process_runner=gate_closed_runner,
301
- setting_by_name=ENABLED_SETTING_BY_NAME,
302
- )
303
-
304
- assert disabled_reply.fallback_kind == sol_advisor.SOL_FALLBACK_KIND_DECLINED
305
- assert gate_closed_reply.fallback_kind == sol_advisor.SOL_FALLBACK_KIND_DECLINED
306
-
307
-
308
- def test_probe_failure_fallback_is_marked_broken() -> None:
309
- def failing_probe_runner(
310
- arguments: list[str], **kwargs: object
311
- ) -> subprocess.CompletedProcess[str]:
312
- return _probe_process({}, returncode=3)
313
-
314
- reply = sol_advisor.run_codex_sol_advisor(
315
- prompt="first consult",
316
- working_directory=Path("."),
317
- preflight=None,
318
- probe_path=USAGE_PROBE_PATH,
319
- session_id=None,
320
- process_runner=failing_probe_runner,
321
- setting_by_name=ENABLED_SETTING_BY_NAME,
322
- )
323
-
324
- assert reply.is_fallback
325
- assert reply.fallback_kind == sol_advisor.SOL_FALLBACK_KIND_BROKEN
326
-
327
-
328
- def test_enable_sol_flag_opens_the_rung_without_an_environment_flag(
329
- monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
330
- ) -> None:
331
- captured_settings: dict[str, str] = {}
332
-
333
- def fake_advisor(**kwargs: object) -> object:
334
- captured_settings.update(dict(kwargs["setting_by_name"])) # type: ignore[arg-type]
335
- return sol_advisor._reply_fallback(
336
- "probe declined",
337
- True,
338
- fallback_kind=sol_advisor.SOL_FALLBACK_KIND_DECLINED,
339
- )
340
-
341
- monkeypatch.setattr(sol_advisor, "run_codex_sol_advisor", fake_advisor)
342
- monkeypatch.delenv(sol_advisor.SOL_ENV_VAR, raising=False)
343
- monkeypatch.setattr(sys, "stdin", io.StringIO("first consult"))
344
-
345
- exit_code = sol_advisor.main(
346
- ["--bind", "--cwd", ".", sol_advisor.SOL_ENABLE_FLAG]
347
- )
348
- payload = json.loads(capsys.readouterr().out)
349
-
350
- assert exit_code == 1
351
- assert captured_settings[sol_advisor.SOL_ENV_VAR] == "1"
352
- assert payload["fallback_kind"] == sol_advisor.SOL_FALLBACK_KIND_DECLINED
353
-
354
-
355
- def test_effort_cli_flag_overrides_environment_effort(
356
- monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
357
- ) -> None:
358
- captured_settings: dict[str, str] = {}
359
-
360
- def fake_advisor(**kwargs: object) -> object:
361
- captured_settings.update(dict(kwargs["setting_by_name"])) # type: ignore[arg-type]
362
- return sol_advisor._reply_fallback(
363
- "probe declined",
364
- True,
365
- fallback_kind=sol_advisor.SOL_FALLBACK_KIND_DECLINED,
366
- )
367
-
368
- monkeypatch.setattr(sol_advisor, "run_codex_sol_advisor", fake_advisor)
369
- monkeypatch.setenv("ADVISOR_EFFORT", "high")
370
- monkeypatch.setattr(sys, "stdin", io.StringIO("first consult"))
371
-
372
- exit_code = sol_advisor.main(
373
- [
374
- "--bind",
375
- "--cwd",
376
- ".",
377
- sol_advisor.SOL_ENABLE_FLAG,
378
- sol_advisor.SOL_EFFORT_FLAG,
379
- "medium",
380
- ]
381
- )
382
- payload = json.loads(capsys.readouterr().out)
383
-
384
- assert exit_code == 1
385
- assert captured_settings["ADVISOR_EFFORT"] == "medium"
386
- assert payload["fallback_kind"] == sol_advisor.SOL_FALLBACK_KIND_DECLINED
387
-
388
-
389
- def test_successful_probe_requires_finite_meter_above_configured_gate() -> None:
390
- def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
391
- return _probe_process({"percent_left": 90})
392
-
393
- preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
394
-
395
- assert preflight.eligible
396
- assert preflight.percent_left == 90
397
-
398
-
399
- @pytest.mark.parametrize("percent_left", [10, 10.0, 10.000])
400
- def test_meter_at_configured_threshold_falls_back(
401
- percent_left: float,
402
- ) -> None:
403
- def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
404
- return _probe_process({"percent_left": percent_left})
405
-
406
- preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
407
-
408
- assert not preflight.eligible
409
- assert preflight.percent_left == percent_left
410
-
411
-
412
- @pytest.mark.parametrize(
413
- "payload",
414
- [
415
- {"percent_left": None},
416
- {"percent_left": "90"},
417
- {"percent_left": True},
418
- {"percent_left": float("nan")},
419
- ["percent_left", 90],
420
- ],
421
- )
422
- def test_unknown_and_malformed_meter_falls_back(payload: object) -> None:
423
- def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
424
- return _probe_process(payload)
425
-
426
- preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
427
-
428
- assert not preflight.eligible
429
- assert preflight.percent_left is None
430
-
431
-
432
- def test_malformed_probe_json_falls_back() -> None:
433
- def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
434
- return subprocess.CompletedProcess(arguments, 0, "{broken", "")
435
-
436
- preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
437
-
438
- assert not preflight.eligible
439
-
440
-
441
- def test_probe_nonzero_exit_falls_back() -> None:
442
- def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
443
- return _probe_process({"percent_left": 90}, returncode=2)
444
-
445
- preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
446
-
447
- assert not preflight.eligible
448
-
449
-
450
- def test_probe_timeout_falls_back() -> None:
451
- def probe_runner(arguments: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
452
- raise subprocess.TimeoutExpired(arguments, 30)
453
-
454
- preflight = sol_advisor.run_sol_preflight(USAGE_PROBE_PATH, probe_runner)
455
-
456
- assert not preflight.eligible
457
- assert "timed out" in preflight.reason
458
-
459
-
460
- @pytest.mark.parametrize("signal", ["ENDORSE", "CORRECTION", "PLAN", "STOP"])
461
- def test_jsonl_parser_accepts_each_exact_guidance_signal(signal: str) -> None:
462
- reply = sol_advisor.parse_codex_jsonl_reply(
463
- _event_stream(guidance=f"{signal}\nadditional guidance"),
464
- existing_session_id=None,
465
- is_sol_enabled=True,
466
- )
467
-
468
- assert reply.successful
469
- assert not reply.is_fallback
470
- assert reply.session_id == "thread-1"
471
- assert reply.guidance == f"{signal}\nadditional guidance"
472
-
473
-
474
- @pytest.mark.parametrize(
475
- "jsonl_text",
476
- [
477
- "{broken",
478
- json.dumps({"type": "thread.started", "thread_id": "thread-1"}),
479
- _event_stream(session_id="", guidance="PLAN\ninspect"),
480
- _event_stream(guidance="ENDORSE: ready"),
481
- _event_stream(guidance="\n unknown signal\nmore"),
482
- json.dumps(
483
- {
484
- "type": "item.completed",
485
- "item": {"type": "agent_message", "text": "PLAN"},
486
- }
487
- ),
488
- ],
489
- )
490
- def test_jsonl_parser_returns_typed_fallback_for_invalid_reply(
491
- jsonl_text: str,
492
- ) -> None:
493
- reply = sol_advisor.parse_codex_jsonl_reply(
494
- jsonl_text,
495
- existing_session_id=None,
496
- is_sol_enabled=True,
497
- )
498
-
499
- assert not reply.successful
500
- assert reply.is_fallback
501
- assert reply.session_id is None
502
- assert reply.guidance is None
503
- assert reply.reason
504
-
505
-
506
- def test_bind_runs_probe_then_codex_with_read_only_xhigh_settings() -> None:
507
- calls: list[tuple[list[str], dict[str, object]]] = []
508
-
509
- def process_runner(
510
- arguments: list[str], **kwargs: object
511
- ) -> subprocess.CompletedProcess[str]:
512
- calls.append((arguments, kwargs))
513
- if len(calls) == 1:
514
- return _probe_process({"percent_left": 90})
515
- return subprocess.CompletedProcess(
516
- arguments, 0, _event_stream(guidance="PLAN\ninspect"), ""
517
- )
518
-
519
- reply = sol_advisor.run_codex_sol_advisor(
520
- prompt="reply only",
521
- working_directory=Path("."),
522
- preflight=None,
523
- probe_path=USAGE_PROBE_PATH,
524
- session_id=None,
525
- process_runner=process_runner,
526
- setting_by_name=ENABLED_SETTING_BY_NAME,
527
- )
528
-
529
- assert reply.successful
530
- assert len(calls) == 2
531
- assert calls[1][0] == sol_advisor.build_codex_arguments("codex")
532
- assert calls[1][1]["cwd"] == "."
533
- assert calls[1][1]["shell"] is False
534
- assert calls[1][1]["timeout"]
535
-
536
-
537
- def test_protocol_docs_name_shared_effort_and_fable_sol_ladder() -> None:
538
- sol_rung_path = SCRIPTS_ROOT.parent / "reference" / "sol-rung.md"
539
- protocol_path = SCRIPTS_ROOT.parent / "advisor-protocol.md"
540
- third_party_bind_path = SCRIPTS_ROOT.parent / "reference" / "third-party-bind.md"
541
- advisor_block_path = SCRIPTS_ROOT.parent / "reference" / "advisor-block.md"
542
- sol_rung = sol_rung_path.read_text(encoding="utf-8")
543
- protocol_text = protocol_path.read_text(encoding="utf-8")
544
- third_party_bind_text = third_party_bind_path.read_text(encoding="utf-8")
545
- advisor_block_text = advisor_block_path.read_text(encoding="utf-8")
546
-
547
- assert 'model_reasoning_effort="low"' in sol_rung
548
- assert "out of usage" in protocol_text
549
- assert "ADVISOR_EFFORT" in protocol_text
550
- assert "ADVISOR_EFFORT" in sol_rung
551
- assert "ADVISOR_EFFORT" in third_party_bind_text
552
- assert "ADVISOR_SOL=1" in protocol_text
553
- assert "ADVISOR_SOL_EFFORT" not in protocol_text
554
- assert "ADVISOR_SOL_EFFORT" not in sol_rung
555
- assert "ADVISOR_SOL_XHIGH" not in protocol_text
556
- assert "ADVISOR_SOL_XHIGH" not in sol_rung
557
- assert "--effort xhigh" not in third_party_bind_text
558
- assert "--effort medium" not in third_party_bind_text
559
- assert "then Opus" not in protocol_text
560
- assert "then Claude Opus" not in advisor_block_text
561
- assert 'candidate_tiers = ["Fable", "Opus"]' not in protocol_text
562
- assert 'candidate_tiers = ["Fable", "Sol", "Opus"]' not in protocol_text
563
- team_advisor_text = (
564
- SCRIPTS_ROOT.parents[2] / ".agents" / "skills" / "team-advisor" / "SKILL.md"
565
- ).read_text(encoding="utf-8")
566
- orchestrator_text = (
567
- SCRIPTS_ROOT.parents[2] / ".agents" / "skills" / "orchestrator" / "SKILL.md"
568
- ).read_text(encoding="utf-8")
569
- refresh_text = (
570
- SCRIPTS_ROOT.parents[2]
571
- / ".agents"
572
- / "skills"
573
- / "orchestrator-refresh"
574
- / "SKILL.md"
575
- ).read_text(encoding="utf-8")
576
- assert "then Opus" not in team_advisor_text
577
- assert "then Opus" not in orchestrator_text
578
- assert "then Opus xhigh" not in refresh_text
579
-
580
-
581
- def test_team_advisor_path_preserves_sol_routing_fields() -> None:
582
- team_advisor_path = SCRIPTS_ROOT.parents[2] / ".agents" / "skills" / "team-advisor" / "SKILL.md"
583
- sol_rung_path = SCRIPTS_ROOT.parent / "reference" / "sol-rung.md"
584
- assert "advisor-protocol.md" in team_advisor_path.read_text(encoding="utf-8")
585
- sol_rung = sol_rung_path.read_text(encoding="utf-8")
586
- assert "codex_sol_advisor.py" in sol_rung
587
- assert "--resume <session_id>" in sol_rung
588
- calls: list[list[str]] = []
589
-
590
- reply = sol_advisor.run_codex_sol_advisor(
591
- prompt="first consult",
592
- working_directory=Path("."),
593
- preflight=None,
594
- probe_path=USAGE_PROBE_PATH,
595
- session_id=None,
596
- process_runner=_two_step_process_runner(calls, guidance="PLAN\ninspect"),
597
- setting_by_name=ENABLED_SETTING_BY_NAME,
598
- )
599
-
600
- assert reply.successful
601
- assert reply.sol_enabled
602
- assert reply.selected_tier == sol_advisor.ADVISOR_MODEL_TIER
603
- assert reply.outcome == sol_advisor.CODEX_BIND_SUCCESS_TOKEN
604
- assert reply.signal == "PLAN"
605
- assert reply.guidance == "PLAN\ninspect"
606
-
607
-
608
- def test_team_advisor_path_uses_fable_result_when_sol_gate_is_closed() -> None:
609
- calls: list[list[str]] = []
610
-
611
- def process_runner(
612
- arguments: list[str], **kwargs: object
613
- ) -> subprocess.CompletedProcess[str]:
614
- calls.append(arguments)
615
- return _probe_process({"percent_left": 10})
616
-
617
- reply = sol_advisor.run_codex_sol_advisor(
618
- prompt="first consult",
619
- working_directory=Path("."),
620
- preflight=None,
621
- probe_path=USAGE_PROBE_PATH,
622
- session_id=None,
623
- process_runner=process_runner,
624
- setting_by_name=ENABLED_SETTING_BY_NAME,
625
- )
626
-
627
- assert not reply.successful
628
- assert reply.is_fallback
629
- assert reply.sol_enabled
630
- assert reply.selected_tier == sol_advisor.ADVISOR_FALLBACK_TIER
631
- assert reply.outcome == sol_advisor.ADVISOR_FALLBACK_RESULT
632
- assert len(calls) == 1
633
-
634
-
635
- def test_disabled_flag_uses_default_optional_routing_inputs(
636
- monkeypatch: pytest.MonkeyPatch,
637
- ) -> None:
638
- monkeypatch.delenv(sol_advisor.SOL_ENV_VAR, raising=False)
639
- reply = sol_advisor.run_codex_sol_advisor(
640
- prompt="first consult",
641
- working_directory=Path("."),
642
- preflight=sol_advisor.SolPreflight(
643
- eligible=True,
644
- percent_left=90,
645
- reason="test preflight",
646
- ),
647
- probe_path=None,
648
- setting_by_name=None,
649
- session_id=None,
650
- process_runner=subprocess.run,
651
- )
652
-
653
- assert reply.is_fallback
654
- assert reply.reason == "Sol advisor flag is disabled"
655
-
656
-
657
- def test_resume_runs_the_usage_gate_before_codex() -> None:
658
- calls: list[list[str]] = []
659
-
660
- reply = sol_advisor.run_codex_sol_advisor(
661
- prompt="resume",
662
- working_directory=Path("."),
663
- preflight=None,
664
- session_id="thread-1",
665
- probe_path=USAGE_PROBE_PATH,
666
- process_runner=_two_step_process_runner(calls, guidance="ENDORSE\nready"),
667
- setting_by_name=ENABLED_SETTING_BY_NAME,
668
- )
669
-
670
- assert reply.successful
671
- assert calls == [
672
- [sys.executable, str(USAGE_PROBE_PATH)],
673
- sol_advisor.build_codex_arguments("codex", session_id="thread-1"),
674
- ]
675
-
676
-
677
- @pytest.mark.parametrize(
678
- "codex_failure",
679
- [
680
- "nonzero",
681
- "timeout",
682
- "malformed_jsonl",
683
- "missing_session",
684
- "missing_guidance",
685
- "invalid_signal",
686
- ],
687
- )
688
- def test_codex_failure_modes_always_return_fallback(
689
- codex_failure: str,
690
- ) -> None:
691
- def process_runner(
692
- arguments: list[str], **kwargs: object
693
- ) -> subprocess.CompletedProcess[str]:
694
- if len(arguments) == 2:
695
- return _probe_process({"percent_left": 90})
696
- if codex_failure == "timeout":
697
- raise subprocess.TimeoutExpired(arguments, 30)
698
- if codex_failure == "nonzero":
699
- return subprocess.CompletedProcess(
700
- arguments, 3, _event_stream(guidance="PLAN"), ""
701
- )
702
- if codex_failure == "malformed_jsonl":
703
- return subprocess.CompletedProcess(arguments, 0, "{broken", "")
704
- if codex_failure == "missing_session":
705
- return subprocess.CompletedProcess(
706
- arguments,
707
- 0,
708
- json.dumps(
709
- {
710
- "type": "item.completed",
711
- "item": {"type": "agent_message", "text": "PLAN"},
712
- }
713
- ),
714
- "",
715
- )
716
- if codex_failure == "missing_guidance":
717
- return subprocess.CompletedProcess(
718
- arguments,
719
- 0,
720
- json.dumps({"type": "thread.started", "thread_id": "thread-1"}),
721
- "",
722
- )
723
- return subprocess.CompletedProcess(
724
- arguments, 0, _event_stream(guidance="PLAN: inspect"), ""
725
- )
726
-
727
- reply = sol_advisor.run_codex_sol_advisor(
728
- prompt="bind",
729
- working_directory=Path("."),
730
- preflight=None,
731
- probe_path=USAGE_PROBE_PATH,
732
- session_id=None,
733
- process_runner=process_runner,
734
- setting_by_name=ENABLED_SETTING_BY_NAME,
735
- )
736
-
737
- assert not reply.successful
738
- assert reply.is_fallback
739
- assert reply.session_id is None
740
- assert reply.guidance is None