claude-dev-env 2.36.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 (66) hide show
  1. package/.agents/skills/_shared/advisor/scripts/README.md +1 -1
  2. package/.agents/skills/team-advisor/SKILL.md +11 -11
  3. package/_shared/advisor/advisor-protocol.md +51 -51
  4. package/_shared/advisor/reference/advisor-block.md +9 -9
  5. package/_shared/advisor/reference/astra-rung.md +39 -0
  6. package/_shared/advisor/reference/identity.md +4 -4
  7. package/_shared/advisor/reference/lifecycle.md +6 -6
  8. package/_shared/advisor/reference/spawn-walk-log.md +9 -9
  9. package/_shared/advisor/reference/third-party-bind.md +11 -11
  10. package/_shared/advisor/reference/warm-up.md +13 -13
  11. package/_shared/advisor/scripts/codex_astra_advisor.py +308 -0
  12. package/_shared/advisor/scripts/codex_astra_preflight.py +115 -0
  13. package/_shared/advisor/scripts/codex_astra_reply.py +159 -0
  14. package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +3 -3
  15. package/_shared/advisor/scripts/config/advisor_scripts_constants/astra_advisor_constants.py +38 -0
  16. package/_shared/advisor/scripts/model_tier_run_validator.py +17 -18
  17. package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +271 -0
  18. package/_shared/advisor/scripts/tests/test_codex_astra_preflight.py +24 -0
  19. package/_shared/advisor/scripts/tests/test_codex_astra_reply.py +42 -0
  20. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +55 -30
  21. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +14 -4
  22. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +14 -0
  23. package/_shared/pr-loop/scripts/terminology_sweep.py +138 -50
  24. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -1
  25. package/docs/references/team-advisor-skill.md +3 -3
  26. package/hooks/validators/README.md +35 -1
  27. package/package.json +1 -1
  28. package/scripts/check.ps1 +18 -6
  29. package/scripts/policy_lint/adapter_detectors.py +42 -2
  30. package/scripts/policy_lint/adapters.py +24 -0
  31. package/scripts/policy_lint/config/constants.py +11 -0
  32. package/scripts/policy_lint/engine.py +1 -1
  33. package/scripts/policy_lint/model.py +1 -0
  34. package/scripts/policy_lint/registry.py +10 -1
  35. package/scripts/repository_checks/__init__.py +1 -0
  36. package/scripts/repository_checks/claude_md.py +108 -0
  37. package/scripts/repository_checks/config/__init__.py +1 -0
  38. package/scripts/repository_checks/config/constants.py +150 -0
  39. package/scripts/repository_checks/env_var_documentation.py +243 -0
  40. package/scripts/repository_checks/hook_modules.py +34 -0
  41. package/scripts/repository_checks/models.py +37 -0
  42. package/scripts/repository_checks/package_inventory.py +118 -0
  43. package/scripts/repository_checks/pytest_testpaths.py +84 -0
  44. package/scripts/repository_checks/reporting.py +39 -0
  45. package/scripts/repository_checks/runner.py +93 -0
  46. package/scripts/repository_checks/tracked_secrets.py +147 -0
  47. package/scripts/repository_checks/tracked_tree.py +28 -0
  48. package/scripts/repository_policy.py +120 -0
  49. package/scripts/tests/repository_policy_test_support.py +89 -0
  50. package/scripts/tests/test_cde_lint.py +25 -1
  51. package/scripts/tests/test_claude_md.py +68 -0
  52. package/scripts/tests/test_env_var_documentation.py +133 -0
  53. package/scripts/tests/test_hook_modules.py +17 -0
  54. package/scripts/tests/test_models.py +26 -0
  55. package/scripts/tests/test_package_inventory.py +96 -0
  56. package/scripts/tests/test_policy_lint_rules_registry.py +117 -0
  57. package/scripts/tests/test_pytest_testpaths.py +66 -0
  58. package/scripts/tests/test_reporting.py +54 -0
  59. package/scripts/tests/test_repository_policy.py +71 -0
  60. package/scripts/tests/test_runner.py +59 -0
  61. package/scripts/tests/test_tracked_secrets.py +175 -0
  62. package/scripts/tests/test_tracked_tree.py +28 -0
  63. package/_shared/advisor/reference/sol-rung.md +0 -39
  64. package/_shared/advisor/scripts/codex_sol_advisor.py +0 -581
  65. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +0 -38
  66. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +0 -740
@@ -0,0 +1,308 @@
1
+ """Bind and consult a read-only Codex CLI session through Astra."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from collections.abc import Callable, Mapping, Sequence
12
+ from dataclasses import asdict
13
+ from pathlib import Path
14
+
15
+ _scripts_directory = Path(__file__).resolve().parent
16
+ _config_directory = _scripts_directory / "config"
17
+ for each_import_directory in (_scripts_directory, _config_directory):
18
+ each_import_directory_text = str(each_import_directory)
19
+ if each_import_directory_text not in sys.path:
20
+ sys.path[:0] = [each_import_directory_text]
21
+
22
+ from advisor_scripts_constants.astra_advisor_constants import (
23
+ ADVISOR_CODEX_EXECUTABLE_ENV_VAR,
24
+ ALL_ASTRA_TRUTHY_VALUES,
25
+ ASTRA_BIND_FAILURE_REASON,
26
+ ASTRA_CODEX_TIMEOUT_REASON,
27
+ ASTRA_CODEX_TIMEOUT_SECONDS,
28
+ ASTRA_EFFORT_FLAG,
29
+ ASTRA_ENABLE_FLAG,
30
+ ASTRA_ENV_VAR,
31
+ ASTRA_EXECUTABLE_NOT_FOUND_REASON,
32
+ ASTRA_FALLBACK_KIND_BROKEN,
33
+ ASTRA_FALLBACK_KIND_DECLINED,
34
+ ASTRA_SESSION_ID_METAVAR,
35
+ CLAUDE_CONFIG_DIRECTORY_NAME,
36
+ CODEX_CONFIG_FLAG,
37
+ CODEX_EXECUTABLE,
38
+ CODEX_EXEC_SUBCOMMAND,
39
+ CODEX_JSON_FLAG,
40
+ CODEX_MODEL_FLAG,
41
+ CODEX_PROMPT_FROM_STDIN,
42
+ CODEX_READ_ONLY_SANDBOX,
43
+ CODEX_REASONING_CONFIG_TEMPLATE,
44
+ CODEX_RESUME_SUBCOMMAND,
45
+ CODEX_SANDBOX_FLAG,
46
+ USAGE_PROBE_FILENAME,
47
+ USAGE_PROBE_PACKAGE_DIRECTORY_NAME,
48
+ USAGE_PROBE_SCRIPTS_DIRECTORY_NAME,
49
+ USAGE_PROBE_SHARED_DIRECTORY_NAME,
50
+ )
51
+ from advisor_scripts_constants.advisor_route_constants import (
52
+ ADVISOR_CODEX_MODEL_ID,
53
+ ADVISOR_EFFORT_DEFAULT,
54
+ ADVISOR_EFFORT_ENV_VAR,
55
+ ALL_ADVISOR_EFFORT_LEVELS,
56
+ SPAWN_OUTCOME_KEY,
57
+ )
58
+ from codex_astra_reply import (
59
+ CodexAstraAdvisorReply,
60
+ build_fallback_reply,
61
+ parse_codex_jsonl_reply,
62
+ )
63
+ from codex_astra_preflight import AstraPreflight, run_astra_preflight
64
+
65
+
66
+ def _resolved_settings(
67
+ all_settings: Mapping[str, str] | None,
68
+ ) -> Mapping[str, str]:
69
+ return os.environ if all_settings is None else all_settings
70
+
71
+
72
+ def is_astra_advisor_enabled(all_settings: Mapping[str, str] | None) -> bool:
73
+ """Return whether the Astra advisor flag is on.
74
+
75
+ Args:
76
+ all_settings: Environment mapping, or None to read os.environ.
77
+
78
+ Returns:
79
+ True when the flag holds a truthy value.
80
+ """
81
+ raw_setting = _resolved_settings(all_settings).get(ASTRA_ENV_VAR, "")
82
+ return raw_setting.strip().lower() in ALL_ASTRA_TRUTHY_VALUES
83
+
84
+
85
+ def resolve_advisor_effort(all_settings: Mapping[str, str] | None) -> str:
86
+ """Return ADVISOR_EFFORT, or low when the value is missing or unknown.
87
+
88
+ Args:
89
+ all_settings: Environment mapping, or None to read os.environ.
90
+
91
+ Returns:
92
+ The requested effort, or low.
93
+ """
94
+ requested = _resolved_settings(all_settings).get(ADVISOR_EFFORT_ENV_VAR, "")
95
+ normalized = requested.strip().lower()
96
+ return normalized if normalized in ALL_ADVISOR_EFFORT_LEVELS else ADVISOR_EFFORT_DEFAULT
97
+
98
+
99
+ def resolve_usage_probe_path(home_directory: Path) -> Path:
100
+ """Return the Codex usage-probe path under home_directory.
101
+
102
+ Args:
103
+ home_directory: Home directory that holds the installed probe.
104
+
105
+ Returns:
106
+ Path to the usage probe under that home.
107
+ """
108
+ return (
109
+ home_directory
110
+ / CLAUDE_CONFIG_DIRECTORY_NAME
111
+ / USAGE_PROBE_SHARED_DIRECTORY_NAME
112
+ / USAGE_PROBE_PACKAGE_DIRECTORY_NAME
113
+ / USAGE_PROBE_SCRIPTS_DIRECTORY_NAME
114
+ / USAGE_PROBE_FILENAME
115
+ )
116
+
117
+
118
+ def resolve_codex_executable(all_settings: Mapping[str, str] | None) -> str | None:
119
+ """Return the Codex executable from the override or PATH.
120
+
121
+ Args:
122
+ all_settings: Environment mapping, or None to read os.environ.
123
+
124
+ Returns:
125
+ Executable path or name, or None if neither is set.
126
+ """
127
+ override = _resolved_settings(all_settings).get(ADVISOR_CODEX_EXECUTABLE_ENV_VAR, "").strip()
128
+ return override or shutil.which(CODEX_EXECUTABLE)
129
+
130
+
131
+ def build_codex_arguments(
132
+ codex_executable: str,
133
+ session_id: str | None = None,
134
+ reasoning_effort: str = ADVISOR_EFFORT_DEFAULT,
135
+ ) -> list[str]:
136
+ """Build the Codex command list for bind or resume.
137
+
138
+ Args:
139
+ codex_executable: Codex executable path or name.
140
+ session_id: Session to resume, or None for a new bind.
141
+ reasoning_effort: Codex reasoning effort.
142
+
143
+ Returns:
144
+ Command arguments for Codex exec.
145
+ """
146
+ arguments = [
147
+ codex_executable,
148
+ CODEX_EXEC_SUBCOMMAND,
149
+ CODEX_MODEL_FLAG,
150
+ ADVISOR_CODEX_MODEL_ID,
151
+ CODEX_CONFIG_FLAG,
152
+ CODEX_REASONING_CONFIG_TEMPLATE.format(effort=reasoning_effort),
153
+ CODEX_SANDBOX_FLAG,
154
+ CODEX_READ_ONLY_SANDBOX,
155
+ CODEX_JSON_FLAG,
156
+ ]
157
+ if session_id is not None:
158
+ arguments.extend([CODEX_RESUME_SUBCOMMAND, session_id])
159
+ arguments.append(CODEX_PROMPT_FROM_STDIN)
160
+ return arguments
161
+
162
+
163
+ def _resolve_preflight(
164
+ preflight: AstraPreflight | None,
165
+ probe_path: Path | None,
166
+ process_runner: Callable[..., subprocess.CompletedProcess[str]],
167
+ ) -> AstraPreflight:
168
+ if preflight is not None:
169
+ return preflight
170
+ resolved_path = resolve_usage_probe_path(Path.home()) if probe_path is None else probe_path
171
+ return run_astra_preflight(resolved_path, process_runner)
172
+
173
+
174
+ def _run_codex(
175
+ prompt: str,
176
+ working_directory: Path,
177
+ session_id: str | None,
178
+ all_settings: Mapping[str, str] | None,
179
+ executable: str,
180
+ process_runner: Callable[..., subprocess.CompletedProcess[str]],
181
+ ) -> subprocess.CompletedProcess[str]:
182
+ return process_runner(
183
+ build_codex_arguments(
184
+ executable,
185
+ session_id=session_id,
186
+ reasoning_effort=resolve_advisor_effort(all_settings),
187
+ ),
188
+ cwd=str(working_directory),
189
+ input=prompt,
190
+ capture_output=True,
191
+ text=True,
192
+ check=False,
193
+ shell=False,
194
+ timeout=ASTRA_CODEX_TIMEOUT_SECONDS,
195
+ )
196
+
197
+
198
+ def _run_enabled_advisor(
199
+ prompt: str,
200
+ working_directory: Path,
201
+ setting_by_name: Mapping[str, str] | None,
202
+ session_id: str | None,
203
+ executable: str,
204
+ resolved_preflight: AstraPreflight,
205
+ process_runner: Callable[..., subprocess.CompletedProcess[str]],
206
+ ) -> CodexAstraAdvisorReply:
207
+ if not resolved_preflight.eligible:
208
+ return build_fallback_reply(resolved_preflight.reason, True, resolved_preflight.fallback_kind)
209
+ try:
210
+ completed = _run_codex(
211
+ prompt, working_directory, session_id, setting_by_name, executable, process_runner
212
+ )
213
+ except subprocess.TimeoutExpired as error:
214
+ return build_fallback_reply(f"{ASTRA_CODEX_TIMEOUT_REASON}: {error}", True)
215
+ except (OSError, subprocess.SubprocessError) as error:
216
+ return build_fallback_reply(f"{ASTRA_BIND_FAILURE_REASON}: {error}", True)
217
+ if completed.returncode != 0:
218
+ return build_fallback_reply(
219
+ f"{ASTRA_BIND_FAILURE_REASON}: process exit {completed.returncode}", True
220
+ )
221
+ return parse_codex_jsonl_reply(completed.stdout, session_id, True)
222
+
223
+
224
+ def run_codex_astra_advisor(
225
+ prompt: str,
226
+ working_directory: Path,
227
+ preflight: AstraPreflight | None,
228
+ probe_path: Path | None,
229
+ setting_by_name: Mapping[str, str] | None,
230
+ session_id: str | None,
231
+ process_runner: Callable[..., subprocess.CompletedProcess[str]],
232
+ ) -> CodexAstraAdvisorReply:
233
+ """Bind or resume a read-only Codex Astra advisor.
234
+
235
+ Args:
236
+ prompt: Advisor prompt text.
237
+ working_directory: Working directory for Codex.
238
+ preflight: Usage probe result, or None to run the probe.
239
+ probe_path: Usage-probe path, or None for the installed path.
240
+ setting_by_name: Environment mapping, or None to read os.environ.
241
+ session_id: Session to resume, or None for a new bind.
242
+ process_runner: Callable that runs the probe and Codex.
243
+
244
+ Returns:
245
+ Advisor reply, or a fallback.
246
+ """
247
+ if not is_astra_advisor_enabled(setting_by_name):
248
+ return build_fallback_reply("Astra advisor flag is disabled", False, ASTRA_FALLBACK_KIND_DECLINED)
249
+ executable = resolve_codex_executable(setting_by_name)
250
+ if executable is None:
251
+ return build_fallback_reply(ASTRA_EXECUTABLE_NOT_FOUND_REASON, True)
252
+ resolved_preflight = _resolve_preflight(preflight, probe_path, process_runner)
253
+ return _run_enabled_advisor(prompt, working_directory, setting_by_name, session_id, executable, resolved_preflight, process_runner)
254
+
255
+
256
+ def build_argument_parser() -> argparse.ArgumentParser:
257
+ """Build the command-line parser for bind and resume.
258
+
259
+ Returns:
260
+ Argument parser for this helper.
261
+ """
262
+ parser = argparse.ArgumentParser(description="Bind or consult a read-only Codex Astra advisor.")
263
+ mode_group = parser.add_mutually_exclusive_group(required=True)
264
+ mode_group.add_argument("--bind", action="store_true")
265
+ mode_group.add_argument("--resume", metavar=ASTRA_SESSION_ID_METAVAR)
266
+ parser.add_argument("--cwd", required=True, type=Path)
267
+ parser.add_argument(ASTRA_ENABLE_FLAG, dest="is_astra_requested", action="store_true")
268
+ parser.add_argument(
269
+ ASTRA_EFFORT_FLAG,
270
+ dest="astra_effort",
271
+ choices=ALL_ADVISOR_EFFORT_LEVELS,
272
+ default=None,
273
+ )
274
+ return parser
275
+
276
+
277
+ def main(all_cli_arguments: Sequence[str]) -> int:
278
+ """Run one bind or resume request from stdin.
279
+
280
+ Args:
281
+ all_cli_arguments: CLI arguments after the program name.
282
+
283
+ Returns:
284
+ 0 when the advisor reply succeeds, 1 on fallback.
285
+ """
286
+ parsed = build_argument_parser().parse_args(list(all_cli_arguments))
287
+ all_settings = dict(os.environ)
288
+ if parsed.is_astra_requested:
289
+ all_settings[ASTRA_ENV_VAR] = "1"
290
+ if parsed.astra_effort is not None:
291
+ all_settings[ADVISOR_EFFORT_ENV_VAR] = parsed.astra_effort
292
+ reply = run_codex_astra_advisor(
293
+ sys.stdin.read(),
294
+ parsed.cwd,
295
+ None,
296
+ None,
297
+ all_settings,
298
+ parsed.resume if not parsed.bind else None,
299
+ subprocess.run,
300
+ )
301
+ payload = asdict(reply)
302
+ payload[SPAWN_OUTCOME_KEY] = payload.pop("outcome")
303
+ sys.stdout.write(f"{json.dumps(payload, sort_keys=True)}\n")
304
+ return 0 if reply.successful else 1
305
+
306
+
307
+ if __name__ == "__main__":
308
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,115 @@
1
+ """Evaluate Codex usage before an Astra advisor request."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import json
7
+ import math
8
+ import subprocess
9
+ import sys
10
+ from collections.abc import Callable
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+
14
+ from advisor_scripts_constants.astra_advisor_constants import (
15
+ ASTRA_FALLBACK_KIND_BROKEN,
16
+ ASTRA_FALLBACK_KIND_DECLINED,
17
+ ASTRA_PREFLIGHT_FAILURE_REASON,
18
+ ASTRA_PROBE_TIMEOUT_REASON,
19
+ ASTRA_USAGE_PROBE_TIMEOUT_SECONDS,
20
+ )
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class AstraPreflight:
25
+ eligible: bool
26
+ percent_left: float | None
27
+ reason: str
28
+ fallback_kind: str | None = None
29
+
30
+
31
+ def _preflight_fallback(
32
+ reason: str, percent_left: float | None, fallback_kind: str
33
+ ) -> AstraPreflight:
34
+ return AstraPreflight(False, percent_left, reason, fallback_kind)
35
+
36
+
37
+ def _load_usage_gate(probe_path: Path) -> Callable[[float], bool]:
38
+ probe_directory = str(probe_path.parent)
39
+ if probe_directory not in sys.path:
40
+ sys.path.insert(0, probe_directory)
41
+ return importlib.import_module("codex_usage_probe").is_codex_review_required
42
+
43
+
44
+ def _parse_probe_percent(stdout_text: str) -> tuple[float | None, str | None]:
45
+ try:
46
+ report = json.loads(stdout_text)
47
+ except (TypeError, json.JSONDecodeError):
48
+ return None, "usage report is malformed"
49
+ if not isinstance(report, dict):
50
+ return None, "usage report is malformed"
51
+ raw_percent = report.get("percent_left")
52
+ if isinstance(raw_percent, bool) or not isinstance(raw_percent, (int, float)):
53
+ return None, "usage meter is unknown" if raw_percent is None else "usage meter is malformed"
54
+ percent_left = float(raw_percent)
55
+ if not math.isfinite(percent_left):
56
+ return None, "usage meter is malformed"
57
+ return percent_left, None
58
+
59
+
60
+ def _run_probe(
61
+ probe_path: Path,
62
+ process_runner: Callable[..., subprocess.CompletedProcess[str]],
63
+ ) -> subprocess.CompletedProcess[str]:
64
+ return process_runner(
65
+ [sys.executable, str(probe_path)],
66
+ capture_output=True,
67
+ text=True,
68
+ check=False,
69
+ shell=False,
70
+ timeout=ASTRA_USAGE_PROBE_TIMEOUT_SECONDS,
71
+ )
72
+
73
+
74
+ def _preflight_from_probe(
75
+ probe_path: Path, completed: subprocess.CompletedProcess[str]
76
+ ) -> AstraPreflight:
77
+ if completed.returncode != 0:
78
+ reason = f"{ASTRA_PREFLIGHT_FAILURE_REASON}: probe exit {completed.returncode}"
79
+ return _preflight_fallback(reason, None, ASTRA_FALLBACK_KIND_BROKEN)
80
+ percent_left, parse_reason = _parse_probe_percent(completed.stdout)
81
+ if parse_reason is not None or percent_left is None:
82
+ reason = f"{ASTRA_PREFLIGHT_FAILURE_REASON}: {parse_reason or 'usage meter is unknown'}"
83
+ return _preflight_fallback(reason, None, ASTRA_FALLBACK_KIND_BROKEN)
84
+ if not _load_usage_gate(probe_path)(percent_left):
85
+ reason = f"{ASTRA_PREFLIGHT_FAILURE_REASON}: usage meter is at or below the gate"
86
+ return _preflight_fallback(reason, percent_left, ASTRA_FALLBACK_KIND_DECLINED)
87
+ return AstraPreflight(True, percent_left, "usage meter is above the Astra gate")
88
+
89
+
90
+ def run_astra_preflight(
91
+ probe_path: Path,
92
+ process_runner: Callable[..., subprocess.CompletedProcess[str]],
93
+ ) -> AstraPreflight:
94
+ """Run the usage probe and decide whether Astra may bind.
95
+
96
+ Args:
97
+ probe_path: Path to the installed usage probe.
98
+ process_runner: Callable that runs the probe.
99
+
100
+ Returns:
101
+ Eligibility, remaining usage percent, reason, and fallback kind.
102
+ """
103
+ try:
104
+ completed = _run_probe(probe_path, process_runner)
105
+ return _preflight_from_probe(probe_path, completed)
106
+ except subprocess.TimeoutExpired as error:
107
+ return _preflight_fallback(
108
+ f"{ASTRA_PROBE_TIMEOUT_REASON}: {error}", None, ASTRA_FALLBACK_KIND_BROKEN
109
+ )
110
+ except (OSError, subprocess.SubprocessError, ImportError, AttributeError, TypeError, ValueError) as error:
111
+ return _preflight_fallback(
112
+ f"{ASTRA_PREFLIGHT_FAILURE_REASON}: {error}",
113
+ None,
114
+ ASTRA_FALLBACK_KIND_BROKEN,
115
+ )
@@ -0,0 +1,159 @@
1
+ """Parse Codex JSONL and build Astra advisor replies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+
8
+ from advisor_scripts_constants.advisor_route_constants import (
9
+ ADVISOR_FALLBACK_RESULT,
10
+ ADVISOR_FALLBACK_TIER,
11
+ ADVISOR_MODEL_TIER,
12
+ ALL_ADVISOR_GUIDANCE_SIGNALS,
13
+ CODEX_BIND_SUCCESS_TOKEN,
14
+ )
15
+ from advisor_scripts_constants.astra_advisor_constants import (
16
+ ASTRA_FALLBACK_KIND_BROKEN,
17
+ ASTRA_INVALID_SIGNAL_REASON,
18
+ ASTRA_MALFORMED_JSONL_REASON,
19
+ ASTRA_MISSING_SESSION_REASON,
20
+ ASTRA_REPLY_FAILURE_REASON,
21
+ )
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class CodexAstraAdvisorReply:
26
+ session_id: str | None
27
+ guidance: str | None
28
+ successful: bool
29
+ reason: str | None
30
+ is_fallback: bool
31
+ signal: str | None
32
+ astra_enabled: bool
33
+ selected_tier: str
34
+ outcome: str
35
+ fallback_kind: str | None
36
+
37
+
38
+ def build_fallback_reply(
39
+ reason: str,
40
+ is_astra_enabled: bool,
41
+ fallback_kind: str | None = ASTRA_FALLBACK_KIND_BROKEN,
42
+ ) -> CodexAstraAdvisorReply:
43
+ """Build a failed advisor reply.
44
+
45
+ Args:
46
+ reason: Why the request fell back.
47
+ is_astra_enabled: True when the Astra flag was on.
48
+ fallback_kind: Kind of fallback.
49
+
50
+ Returns:
51
+ Failed reply with no session or guidance.
52
+ """
53
+ return CodexAstraAdvisorReply(
54
+ None,
55
+ None,
56
+ False,
57
+ reason,
58
+ True,
59
+ None,
60
+ is_astra_enabled,
61
+ ADVISOR_FALLBACK_TIER,
62
+ ADVISOR_FALLBACK_RESULT,
63
+ fallback_kind,
64
+ )
65
+
66
+
67
+ def build_success_reply(
68
+ session_id: str, guidance: str, signal: str
69
+ ) -> CodexAstraAdvisorReply:
70
+ """Build a successful advisor reply.
71
+
72
+ Args:
73
+ session_id: Codex session id.
74
+ guidance: Advisor text.
75
+ signal: First-line guidance signal.
76
+
77
+ Returns:
78
+ Successful reply with Astra enabled.
79
+ """
80
+ return CodexAstraAdvisorReply(
81
+ session_id,
82
+ guidance,
83
+ True,
84
+ None,
85
+ False,
86
+ signal,
87
+ True,
88
+ ADVISOR_MODEL_TIER,
89
+ CODEX_BIND_SUCCESS_TOKEN,
90
+ None,
91
+ )
92
+
93
+
94
+ def _guidance_signal(guidance: str) -> str | None:
95
+ for each_line in guidance.splitlines():
96
+ candidate = each_line.strip()
97
+ if candidate:
98
+ return candidate if candidate in ALL_ADVISOR_GUIDANCE_SIGNALS else None
99
+ return None
100
+
101
+
102
+ def _parse_event_line(each_line: str) -> tuple[str | None, str | None]:
103
+ event = json.loads(each_line)
104
+ if not isinstance(event, dict):
105
+ raise TypeError("event must be an object")
106
+ discovered_session_id: str | None = None
107
+ discovered_guidance: str | None = None
108
+ if event.get("type") == "thread.started":
109
+ raw_session = event.get("thread_id")
110
+ if isinstance(raw_session, str) and raw_session.strip():
111
+ discovered_session_id = raw_session.strip()
112
+ completed_event = event.get("item")
113
+ if event.get("type") == "item.completed" and isinstance(completed_event, dict):
114
+ message_text = completed_event.get("text")
115
+ if completed_event.get("type") == "agent_message" and isinstance(message_text, str):
116
+ discovered_guidance = message_text.strip()
117
+ return discovered_session_id, discovered_guidance
118
+
119
+
120
+ def _collect_reply_parts(jsonl_text: str) -> tuple[str | None, str | None]:
121
+ session_id: str | None = None
122
+ guidance: str | None = None
123
+ for each_line in jsonl_text.splitlines():
124
+ if each_line.strip():
125
+ discovered_session_id, discovered_guidance = _parse_event_line(each_line)
126
+ session_id = discovered_session_id or session_id
127
+ guidance = discovered_guidance or guidance
128
+ return session_id, guidance
129
+
130
+
131
+ def parse_codex_jsonl_reply(
132
+ jsonl_text: str,
133
+ existing_session_id: str | None,
134
+ is_astra_enabled: bool,
135
+ fallback_kind: str | None = None,
136
+ ) -> CodexAstraAdvisorReply:
137
+ """Parse Codex JSONL into an Astra advisor reply.
138
+
139
+ Args:
140
+ jsonl_text: JSONL from Codex stdout.
141
+ existing_session_id: Session expected on resume, or None on bind.
142
+ is_astra_enabled: True when the Astra flag was on.
143
+ fallback_kind: Kind of fallback on a failed reply.
144
+
145
+ Returns:
146
+ Advisor reply, or a fallback.
147
+ """
148
+ try:
149
+ session_id, guidance = _collect_reply_parts(jsonl_text)
150
+ except (TypeError, json.JSONDecodeError):
151
+ return build_fallback_reply(ASTRA_MALFORMED_JSONL_REASON, is_astra_enabled, fallback_kind)
152
+ if session_id is None or (existing_session_id is not None and session_id != existing_session_id):
153
+ return build_fallback_reply(ASTRA_MISSING_SESSION_REASON, is_astra_enabled, fallback_kind)
154
+ if not guidance:
155
+ return build_fallback_reply(ASTRA_REPLY_FAILURE_REASON, is_astra_enabled, fallback_kind)
156
+ signal = _guidance_signal(guidance)
157
+ if signal is None:
158
+ return build_fallback_reply(ASTRA_INVALID_SIGNAL_REASON, is_astra_enabled, fallback_kind)
159
+ return build_success_reply(session_id, guidance, signal)
@@ -1,9 +1,9 @@
1
- """Shared advisor_route_constants for advisor selection and replies."""
1
+ """Shared constants for advisor selection and replies."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- ADVISOR_MODEL_TIER: str = "Sol"
6
- ADVISOR_CODEX_MODEL_ID: str = "gpt-5.6-sol"
5
+ ADVISOR_MODEL_TIER: str = "Astra"
6
+ ADVISOR_CODEX_MODEL_ID: str = "gpt-6-astra"
7
7
  ADVISOR_FALLBACK_TIER: str = "Fable"
8
8
  ADVISOR_FALLBACK_RESULT: str = "fable"
9
9
  ADVISOR_EFFORT_ENV_VAR: str = "ADVISOR_EFFORT"
@@ -0,0 +1,38 @@
1
+ """Constants for the optional Codex Astra advisor bind."""
2
+
3
+ from __future__ import annotations
4
+
5
+ CODEX_EXECUTABLE: str = "codex"
6
+ ADVISOR_CODEX_EXECUTABLE_ENV_VAR: str = "ADVISOR_CODEX_EXECUTABLE"
7
+ CODEX_READ_ONLY_SANDBOX: str = "read-only"
8
+ CODEX_JSON_FLAG: str = "--json"
9
+ CODEX_MODEL_FLAG: str = "--model"
10
+ CODEX_CONFIG_FLAG: str = "--config"
11
+ CODEX_REASONING_CONFIG_TEMPLATE: str = 'model_reasoning_effort="{effort}"'
12
+ CODEX_PROMPT_FROM_STDIN: str = "-"
13
+ CODEX_EXEC_SUBCOMMAND: str = "exec"
14
+ CODEX_RESUME_SUBCOMMAND: str = "resume"
15
+ CODEX_SANDBOX_FLAG: str = "--sandbox"
16
+ CLAUDE_CONFIG_DIRECTORY_NAME: str = ".claude"
17
+ USAGE_PROBE_SHARED_DIRECTORY_NAME: str = "_shared"
18
+ USAGE_PROBE_PACKAGE_DIRECTORY_NAME: str = "pr-loop"
19
+ USAGE_PROBE_SCRIPTS_DIRECTORY_NAME: str = "scripts"
20
+ USAGE_PROBE_FILENAME: str = "codex_usage_probe.py"
21
+ ASTRA_SESSION_ID_METAVAR: str = "SESSION_ID"
22
+ ASTRA_CODEX_TIMEOUT_SECONDS: float = 120.0
23
+ ASTRA_USAGE_PROBE_TIMEOUT_SECONDS: float = 30.0
24
+ ASTRA_ENV_VAR: str = "ADVISOR_ASTRA"
25
+ ALL_ASTRA_TRUTHY_VALUES: frozenset[str] = frozenset({"1", "true", "yes", "on"})
26
+ ASTRA_PREFLIGHT_FAILURE_REASON: str = "astra preflight did not establish an eligible Codex meter"
27
+ ASTRA_BIND_FAILURE_REASON: str = "Codex Astra bind failed"
28
+ ASTRA_REPLY_FAILURE_REASON: str = "Codex Astra returned no advisor guidance"
29
+ ASTRA_PROBE_TIMEOUT_REASON: str = "Codex Astra meter check timed out"
30
+ ASTRA_CODEX_TIMEOUT_REASON: str = "Codex Astra request timed out"
31
+ ASTRA_MALFORMED_JSONL_REASON: str = "Codex Astra returned malformed JSONL"
32
+ ASTRA_MISSING_SESSION_REASON: str = "Codex Astra returned no session id"
33
+ ASTRA_INVALID_SIGNAL_REASON: str = "Codex Astra returned an invalid guidance signal"
34
+ ASTRA_EXECUTABLE_NOT_FOUND_REASON: str = "Codex Astra could not find the codex executable on PATH"
35
+ ASTRA_FALLBACK_KIND_DECLINED: str = "declined"
36
+ ASTRA_FALLBACK_KIND_BROKEN: str = "broken"
37
+ ASTRA_ENABLE_FLAG: str = "--enable-astra"
38
+ ASTRA_EFFORT_FLAG: str = "--effort"