claude-dev-env 2.9.0 → 2.11.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 (318) hide show
  1. package/CLAUDE.md +2 -2
  2. package/_shared/advisor/CLAUDE.md +3 -2
  3. package/_shared/advisor/advisor-protocol.md +74 -108
  4. package/_shared/advisor/reference/advisor-block.md +37 -0
  5. package/_shared/advisor/reference/cli-chain.md +45 -0
  6. package/_shared/advisor/reference/consult-format.md +41 -0
  7. package/_shared/advisor/reference/lifecycle.md +21 -0
  8. package/_shared/advisor/reference/sol-rung.md +31 -0
  9. package/_shared/advisor/reference/spawn-walk-log.md +31 -0
  10. package/_shared/advisor/reference/third-party-bind.md +30 -0
  11. package/_shared/advisor/reference/warm-up.md +33 -0
  12. package/_shared/advisor/scripts/codex_sol_advisor.py +449 -0
  13. package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +21 -0
  14. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +19 -17
  15. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +28 -0
  16. package/_shared/advisor/scripts/model_tier_run_validator.py +32 -9
  17. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +474 -0
  18. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +79 -0
  19. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +39 -17
  20. package/_shared/advisor/scripts/tier_model_ids.py +24 -0
  21. package/_shared/pr-loop/CLAUDE.md +1 -1
  22. package/_shared/pr-loop/audit-contract.md +17 -6
  23. package/_shared/pr-loop/audit-reply-template.md +4 -4
  24. package/_shared/pr-loop/code-rules-gate.md +3 -5
  25. package/_shared/pr-loop/fix-protocol.md +2 -3
  26. package/_shared/pr-loop/gh-payloads.md +1 -1
  27. package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
  28. package/_shared/pr-loop/scripts/README.md +1 -1
  29. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
  30. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
  31. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
  32. package/_shared/pr-loop/scripts/preflight.py +9 -4
  33. package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
  34. package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
  35. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
  36. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
  37. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
  38. package/_shared/pr-loop/state-schema.md +5 -14
  39. package/agents/CLAUDE.md +2 -2
  40. package/agents/clean-coder.md +58 -548
  41. package/agents/code-quality-agent.md +10 -2
  42. package/agents/code-verifier.md +1 -1
  43. package/agents/test_agent_frontmatter.py +32 -40
  44. package/audit-rubrics/CLAUDE.md +2 -1
  45. package/audit-rubrics/audit-categories.json +704 -0
  46. package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
  47. package/bin/CLAUDE.md +16 -5
  48. package/bin/ever-shipped-skills.mjs +2 -0
  49. package/bin/install-plan.mjs +402 -0
  50. package/bin/install-transaction.mjs +455 -0
  51. package/bin/install.mjs +593 -147
  52. package/bin/install.plan.test.mjs +194 -0
  53. package/bin/install.profile-root.test.mjs +154 -0
  54. package/bin/install.profiles.test.mjs +253 -0
  55. package/bin/install.settings-defaults.test.mjs +200 -0
  56. package/bin/install.transaction.test.mjs +400 -0
  57. package/bin/install.uninstall-transaction.test.mjs +418 -0
  58. package/bin/merge_managed_permissions.mjs +130 -0
  59. package/bin/resolve-install-root.mjs +181 -0
  60. package/bin/select-install-targets.mjs +401 -0
  61. package/commands/CLAUDE.md +0 -2
  62. package/docs/references/CLAUDE.md +3 -1
  63. package/docs/references/advisor-tool.md +25 -7
  64. package/docs/references/prose-style-enforcement.md +25 -0
  65. package/docs/references/team-advisor-skill.md +3 -3
  66. package/docs/references/weak-executor-advisor.md +91 -0
  67. package/hooks/blocking/CLAUDE.md +6 -6
  68. package/hooks/blocking/_path_setup.py +9 -5
  69. package/hooks/blocking/code_rules_docstrings.py +124 -30
  70. package/hooks/blocking/code_rules_enforcer.py +161 -16
  71. package/hooks/blocking/code_rules_shared.py +40 -23
  72. package/hooks/blocking/config/CLAUDE.md +3 -5
  73. package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
  74. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
  75. package/hooks/blocking/eli11_reply_enforcer.py +70 -113
  76. package/hooks/blocking/hedging_language_blocker.py +103 -20
  77. package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
  78. package/hooks/blocking/intent_only_ending_blocker.py +6 -0
  79. package/hooks/blocking/plain_language_blocker.py +139 -20
  80. package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
  81. package/hooks/blocking/state_description_blocker.py +7 -1
  82. package/hooks/blocking/tdd_enforcer.py +8 -0
  83. package/hooks/blocking/test__path_setup.py +28 -0
  84. package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
  85. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
  86. package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
  87. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
  88. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
  89. package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
  90. package/hooks/blocking/test_fable_spawn_gate.py +18 -11
  91. package/hooks/blocking/test_hedging_language_blocker.py +120 -1
  92. package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
  93. package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
  94. package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
  95. package/hooks/blocking/test_plain_language_blocker.py +129 -19
  96. package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
  97. package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
  98. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
  99. package/hooks/blocking/test_state_description_blocker.py +45 -2
  100. package/hooks/blocking/test_stop_dispatcher.py +11 -7
  101. package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
  102. package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
  103. package/hooks/hooks.json +15 -0
  104. package/hooks/hooks_constants/CLAUDE.md +14 -3
  105. package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
  106. package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
  107. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
  108. package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
  109. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
  110. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
  111. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
  112. package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
  113. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
  114. package/hooks/hooks_constants/project_paths_reader.py +31 -4
  115. package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
  116. package/hooks/hooks_constants/pytest_invocation.py +354 -0
  117. package/hooks/hooks_constants/session_start_injector.py +163 -0
  118. package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
  119. package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
  120. package/hooks/hooks_constants/shell_command_segments.py +5 -0
  121. package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
  122. package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
  123. package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
  124. package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
  125. package/hooks/hooks_constants/test_session_start_injector.py +168 -0
  126. package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
  127. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
  128. package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
  129. package/hooks/observability/CLAUDE.md +2 -0
  130. package/hooks/observability/prose_matcher_advisory.py +237 -0
  131. package/hooks/observability/test_prose_matcher_advisory.py +143 -0
  132. package/hooks/session/CLAUDE.md +9 -1
  133. package/hooks/session/_path_setup.py +13 -0
  134. package/hooks/session/issue_tracker_session_starter.py +135 -0
  135. package/hooks/session/orchestrator_auto_starter.py +100 -0
  136. package/hooks/session/test__path_setup.py +28 -0
  137. package/hooks/session/test_issue_tracker_session_starter.py +104 -0
  138. package/hooks/session/test_orchestrator_auto_starter.py +99 -0
  139. package/hooks/session/test_working_style_prompt.py +47 -0
  140. package/hooks/session/untracked_repo_detector.py +1 -24
  141. package/hooks/session/working_style_prompt.py +36 -0
  142. package/hooks/validators/_path_setup.py +19 -0
  143. package/hooks/validators/run_all_validators.py +8 -13
  144. package/installable-surfaces.manifest.json +21 -0
  145. package/output-styles/CLAUDE.md +1 -3
  146. package/package.json +4 -2
  147. package/rules/CLAUDE.md +1 -0
  148. package/rules/durable-post-artifacts.md +2 -2
  149. package/rules/eli11-replies.md +6 -1
  150. package/rules/hedging-claims.md +4 -2
  151. package/rules/long-horizon-autonomy.md +3 -1
  152. package/rules/opus5-communication-contract.md +45 -0
  153. package/rules/plain-language.md +2 -2
  154. package/rules/research-mode.md +1 -1
  155. package/scripts/CLAUDE.md +11 -0
  156. package/scripts/Sync-RepoMain.ps1 +215 -0
  157. package/scripts/active_capability_references.py +218 -0
  158. package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
  159. package/scripts/claude_chain_runner.py +394 -6
  160. package/scripts/claude_chain_usage.py +1 -1
  161. package/scripts/codex_compat_materializer.py +105 -85
  162. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -0
  163. package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
  164. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
  165. package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
  166. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
  167. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
  168. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
  169. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
  170. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
  171. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
  172. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
  173. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
  174. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
  175. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
  176. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
  177. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
  178. package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
  179. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
  180. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
  181. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
  182. package/scripts/spawn_grok_batch.py +3 -0
  183. package/scripts/test_active_capability_references.py +108 -0
  184. package/scripts/test_claude_chain_runner.py +414 -82
  185. package/scripts/test_claude_chain_usage.py +12 -12
  186. package/scripts/test_resolve_worker_spawn.py +2 -2
  187. package/scripts/test_verify_installable_package.py +208 -0
  188. package/scripts/tests/test_codex_compat_materializer.py +33 -0
  189. package/scripts/verify_installable_package.py +612 -0
  190. package/settings.json +10 -0
  191. package/skills/CLAUDE.md +2 -0
  192. package/skills/_shared/advisor/CLAUDE.md +1 -1
  193. package/skills/_shared/advisor/scripts/README.md +2 -0
  194. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  195. package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
  196. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  197. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
  198. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
  199. package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
  200. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
  201. package/skills/autoconverge/reference/convergence.md +2 -1
  202. package/skills/autoconverge/reference/stop-conditions.md +5 -3
  203. package/skills/beat-sheet/SKILL.md +54 -0
  204. package/skills/beat-sheet/reference/visual-beats.md +29 -0
  205. package/skills/bugteam/CONSTRAINTS.md +4 -4
  206. package/skills/bugteam/EXAMPLES.md +1 -1
  207. package/skills/bugteam/reference/README.md +1 -1
  208. package/skills/e-code-review/SKILL.md +26 -5
  209. package/skills/e-code-review/reference/effort-evaluation.md +35 -0
  210. package/skills/e-code-review/reference/medium.md +15 -4
  211. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
  212. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
  213. package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
  214. package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
  215. package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
  216. package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
  217. package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
  218. package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
  219. package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
  220. package/skills/e-code-review/scripts/grok_code_review.py +16 -7
  221. package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
  222. package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
  223. package/skills/e-code-review/scripts/test_grok_code_review.py +77 -0
  224. package/skills/grokify/SKILL.md +1 -1
  225. package/skills/grokify/templates/handoff-template.md +2 -2
  226. package/skills/orchestrator/SKILL.md +5 -4
  227. package/skills/plan-to-pr/scripts/create_packet.py +4 -4
  228. package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
  229. package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
  230. package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
  231. package/skills/plan-to-pr/scripts/validate_run.py +4 -1
  232. package/skills/pr-converge/scripts/check_convergence.py +21 -19
  233. package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
  234. package/skills/pr-converge/scripts/conftest.py +35 -0
  235. package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
  236. package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
  237. package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
  238. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  239. package/skills/rebase/SKILL.md +15 -3
  240. package/skills/reviewer-gates/SKILL.md +2 -2
  241. package/skills/show/SKILL.md +51 -0
  242. package/skills/show/references/accessibility.md +7 -0
  243. package/skills/show/references/art.md +3 -0
  244. package/skills/show/references/charts.md +3 -0
  245. package/skills/show/references/core-design.md +14 -0
  246. package/skills/show/references/erds.md +3 -0
  247. package/skills/show/references/flowcharts.md +3 -0
  248. package/skills/show/references/host-and-html.md +3 -0
  249. package/skills/show/references/illustrative-diagrams.md +10 -0
  250. package/skills/show/references/interaction.md +3 -0
  251. package/skills/show/references/mockups.md +3 -0
  252. package/skills/show/references/quality-gates.md +7 -0
  253. package/skills/show/references/structural-diagrams.md +3 -0
  254. package/skills/show/references/subject-inventory.md +21 -0
  255. package/skills/show/references/svg-contract.md +22 -0
  256. package/skills/show/routing.yaml +30 -0
  257. package/skills/show/samples/pr1262-v2.svg +222 -0
  258. package/skills/show/scripts/README.md +6 -0
  259. package/skills/show/scripts/validate-artifact.py +91 -0
  260. package/skills/show/scripts/validate-package.py +18 -0
  261. package/skills/show/templates/html-widget.html +4 -0
  262. package/skills/show/templates/svg-base.svg +19 -0
  263. package/skills/show/tests/fixtures/css-var.svg +6 -0
  264. package/skills/show/tests/fixtures/dead-ref.svg +7 -0
  265. package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
  266. package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
  267. package/skills/show/tests/fixtures/invalid.svg +1 -0
  268. package/skills/show/tests/fixtures/large-canvas.svg +21 -0
  269. package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
  270. package/skills/show/tests/fixtures/valid.html +1 -0
  271. package/skills/show/tests/test_validate-artifact.py +74 -0
  272. package/skills/show/tests/test_validators.py +59 -0
  273. package/skills/show/workflows/create-visual.md +13 -0
  274. package/skills/show/workflows/review-visual.md +20 -0
  275. package/skills/split-pr/SKILL.md +85 -0
  276. package/skills/split-pr/reference/path-layers.md +16 -0
  277. package/skills/split-pr/reference/proposal-format.md +15 -0
  278. package/skills/split-pr/reference/split-further-loop.md +10 -0
  279. package/skills/split-pr/reference/splitting-principles.md +26 -0
  280. package/skills/split-pr/scripts/analyze_pr.py +279 -0
  281. package/skills/split-pr/scripts/categorize_files.py +106 -0
  282. package/skills/split-pr/scripts/config/__init__.py +1 -0
  283. package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
  284. package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
  285. package/skills/split-pr/scripts/config/packing_constants.py +61 -0
  286. package/skills/split-pr/scripts/config/plan_constants.py +49 -0
  287. package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
  288. package/skills/split-pr/scripts/execute_split_slices.py +82 -0
  289. package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
  290. package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
  291. package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
  292. package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
  293. package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
  294. package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
  295. package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
  296. package/skills/split-pr/scripts/split_pr_title.py +41 -0
  297. package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
  298. package/skills/split-pr/scripts/test_categorize_files.py +55 -0
  299. package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
  300. package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
  301. package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
  302. package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
  303. package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
  304. package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
  305. package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
  306. package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
  307. package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
  308. package/skills/split-pr/scripts/test_verify_plan.py +56 -0
  309. package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
  310. package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
  311. package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
  312. package/skills/split-pr/scripts/verify_plan.py +139 -0
  313. package/skills/team-advisor/SKILL.md +7 -4
  314. package/skills/team-advisor/reference/advisor-docs-review.md +207 -0
  315. package/system-prompts/software-engineer.xml +11 -2
  316. package/commands/initialize.md +0 -90
  317. package/commands/stubcheck.md +0 -88
  318. package/output-styles/caveman-agent.md +0 -37
@@ -0,0 +1,449 @@
1
+ """Bind and consult a read-only Codex CLI session at Sol xhigh."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import importlib
7
+ import json
8
+ import math
9
+ import os
10
+ import subprocess
11
+ import sys
12
+ from collections.abc import Callable, Mapping, Sequence
13
+ from dataclasses import asdict, dataclass
14
+ from pathlib import Path
15
+
16
+ _scripts_directory = Path(__file__).resolve().parent
17
+ _config_directory = _scripts_directory / "config"
18
+ _config_directory_text = str(_config_directory)
19
+ if _config_directory_text not in sys.path:
20
+ sys.path.insert(0, _config_directory_text)
21
+
22
+ from advisor_scripts_constants.sol_advisor_constants import ( # noqa: E402
23
+ CODEX_CONFIG_FLAG,
24
+ CODEX_EXECUTABLE,
25
+ CODEX_EXEC_SUBCOMMAND,
26
+ CODEX_JSON_FLAG,
27
+ CODEX_MODEL_FLAG,
28
+ CODEX_PROMPT_FROM_STDIN,
29
+ CODEX_READ_ONLY_SANDBOX,
30
+ CODEX_REASONING_CONFIG,
31
+ CODEX_RESUME_SUBCOMMAND,
32
+ CODEX_SANDBOX_FLAG,
33
+ CLAUDE_CONFIG_DIRECTORY_NAME,
34
+ SOL_BIND_FAILURE_REASON,
35
+ SOL_CODEX_TIMEOUT_REASON,
36
+ SOL_CODEX_TIMEOUT_SECONDS,
37
+ SOL_ENV_VAR,
38
+ SOL_INVALID_SIGNAL_REASON,
39
+ SOL_MALFORMED_JSONL_REASON,
40
+ SOL_MISSING_SESSION_REASON,
41
+ SOL_PREFLIGHT_FAILURE_REASON,
42
+ SOL_PROBE_TIMEOUT_REASON,
43
+ SOL_REPLY_FAILURE_REASON,
44
+ SOL_SESSION_ID_METAVAR,
45
+ ALL_SOL_TRUTHY_VALUES,
46
+ SOL_USAGE_PROBE_TIMEOUT_SECONDS,
47
+ )
48
+ from advisor_scripts_constants.advisor_route_constants import ( # noqa: E402
49
+ ADVISOR_CODEX_MODEL_ID,
50
+ ADVISOR_FALLBACK_RESULT,
51
+ ADVISOR_FALLBACK_TIER,
52
+ ADVISOR_MODEL_TIER,
53
+ ALL_ADVISOR_GUIDANCE_SIGNALS,
54
+ CODEX_BIND_SUCCESS_TOKEN,
55
+ SPAWN_OUTCOME_KEY,
56
+ )
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class SolPreflight:
61
+ """Record the weekly-meter decision made before a Sol attempt."""
62
+
63
+ eligible: bool
64
+ percent_left: float | None
65
+ reason: str
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class CodexSolAdvisorReply:
70
+ """Record a parsed Codex advisor response or an explicit fallback."""
71
+
72
+ session_id: str | None
73
+ guidance: str | None
74
+ successful: bool
75
+ reason: str | None
76
+ is_fallback: bool
77
+ signal: str | None
78
+ sol_enabled: bool
79
+ selected_tier: str
80
+ outcome: str
81
+
82
+
83
+ def _preflight_fallback(
84
+ reason: str, percent_left: float | None
85
+ ) -> SolPreflight:
86
+ return SolPreflight(False, percent_left, reason)
87
+
88
+
89
+ def _reply_fallback(
90
+ reason: str,
91
+ is_sol_enabled: bool,
92
+ ) -> CodexSolAdvisorReply:
93
+ return CodexSolAdvisorReply(
94
+ session_id=None,
95
+ guidance=None,
96
+ successful=False,
97
+ reason=reason,
98
+ is_fallback=True,
99
+ signal=None,
100
+ sol_enabled=is_sol_enabled,
101
+ selected_tier=ADVISOR_FALLBACK_TIER,
102
+ outcome=ADVISOR_FALLBACK_RESULT,
103
+ )
104
+
105
+
106
+ def _reply_success(
107
+ session_id: str,
108
+ guidance: str,
109
+ signal: str,
110
+ ) -> CodexSolAdvisorReply:
111
+ return CodexSolAdvisorReply(
112
+ session_id=session_id,
113
+ guidance=guidance,
114
+ successful=True,
115
+ reason=None,
116
+ is_fallback=False,
117
+ signal=signal,
118
+ sol_enabled=True,
119
+ selected_tier=ADVISOR_MODEL_TIER,
120
+ outcome=CODEX_BIND_SUCCESS_TOKEN,
121
+ )
122
+
123
+
124
+ def is_sol_advisor_enabled(
125
+ setting_by_name: Mapping[str, str] | None,
126
+ ) -> bool:
127
+ """Return whether the optional Sol xhigh rung is enabled.
128
+
129
+ Args:
130
+ setting_by_name: Optional environment-like settings mapping.
131
+
132
+ Returns:
133
+ Whether the Sol feature flag contains a recognized truthy value.
134
+ """
135
+ resolved_setting_by_name = os.environ if setting_by_name is None else setting_by_name
136
+ return (
137
+ resolved_setting_by_name.get(SOL_ENV_VAR, "").strip().lower()
138
+ in ALL_SOL_TRUTHY_VALUES
139
+ )
140
+
141
+
142
+ def resolve_usage_probe_path(home_directory: Path) -> Path:
143
+ """Return the installed Codex weekly usage probe path.
144
+
145
+ Args:
146
+ home_directory: Home directory used to construct the path.
147
+
148
+ Returns:
149
+ The path to the installed weekly usage probe.
150
+ """
151
+ return (
152
+ home_directory
153
+ / CLAUDE_CONFIG_DIRECTORY_NAME
154
+ / "skills"
155
+ / "codex-review"
156
+ / "scripts"
157
+ / "codex_usage_probe.py"
158
+ )
159
+
160
+
161
+ def _load_usage_gate(probe_path: Path) -> Callable[[float], bool]:
162
+ probe_directory = str(probe_path.parent)
163
+ if probe_directory not in sys.path:
164
+ sys.path.insert(0, probe_directory)
165
+ usage_probe_module = importlib.import_module("codex_usage_probe")
166
+ return usage_probe_module.is_codex_review_required
167
+
168
+
169
+ def _parse_probe_percent(stdout_text: str) -> tuple[float | None, str | None]:
170
+ try:
171
+ usage_report = json.loads(stdout_text)
172
+ except (TypeError, json.JSONDecodeError):
173
+ return None, "usage report is malformed"
174
+ if not isinstance(usage_report, dict):
175
+ return None, "usage report is malformed"
176
+ raw_percent_left = usage_report.get("percent_left")
177
+ if raw_percent_left is None:
178
+ return None, "usage meter is unknown"
179
+ if isinstance(raw_percent_left, bool) or not isinstance(
180
+ raw_percent_left, (int, float)
181
+ ):
182
+ return None, "usage meter is malformed"
183
+ percent_left = float(raw_percent_left)
184
+ if not math.isfinite(percent_left):
185
+ return None, "usage meter is malformed"
186
+ return percent_left, None
187
+
188
+
189
+ def run_sol_preflight(
190
+ probe_path: Path,
191
+ process_runner: Callable[..., subprocess.CompletedProcess[str]],
192
+ ) -> SolPreflight:
193
+ """Run the existing usage probe and require a finite meter above its gate.
194
+
195
+ Args:
196
+ probe_path: Path to the installed usage probe.
197
+ process_runner: Callable used to execute the probe.
198
+
199
+ Returns:
200
+ The usage-meter eligibility decision and any fallback reason.
201
+ """
202
+ try:
203
+ completed_process = process_runner(
204
+ [sys.executable, str(probe_path)],
205
+ capture_output=True,
206
+ text=True,
207
+ check=False,
208
+ shell=False,
209
+ timeout=SOL_USAGE_PROBE_TIMEOUT_SECONDS,
210
+ )
211
+ if completed_process.returncode != 0:
212
+ return _preflight_fallback(
213
+ f"{SOL_PREFLIGHT_FAILURE_REASON}: probe exit {completed_process.returncode}",
214
+ None,
215
+ )
216
+ percent_left, parse_reason = _parse_probe_percent(completed_process.stdout)
217
+ if parse_reason is not None:
218
+ return _preflight_fallback(
219
+ f"{SOL_PREFLIGHT_FAILURE_REASON}: {parse_reason}", None
220
+ )
221
+ usage_gate = _load_usage_gate(probe_path)
222
+ if not callable(usage_gate) or percent_left is None:
223
+ return _preflight_fallback(
224
+ f"{SOL_PREFLIGHT_FAILURE_REASON}: usage meter is unknown", None
225
+ )
226
+ usage_gate = _load_usage_gate(probe_path)
227
+ if not usage_gate(percent_left):
228
+ return _preflight_fallback(
229
+ f"{SOL_PREFLIGHT_FAILURE_REASON}: usage meter is at or below the gate",
230
+ percent_left,
231
+ )
232
+ except subprocess.TimeoutExpired as probe_error:
233
+ return _preflight_fallback(f"{SOL_PROBE_TIMEOUT_REASON}: {probe_error}", None)
234
+ except (
235
+ OSError,
236
+ subprocess.SubprocessError,
237
+ ImportError,
238
+ AttributeError,
239
+ TypeError,
240
+ ValueError,
241
+ ) as probe_error:
242
+ return _preflight_fallback(
243
+ f"{SOL_PREFLIGHT_FAILURE_REASON}: {probe_error}", None
244
+ )
245
+ return SolPreflight(True, percent_left, "usage meter is above the Sol gate")
246
+
247
+
248
+ def build_codex_arguments(session_id: str | None = None) -> list[str]:
249
+ """Build the installed CLI's shell-free bind or resume argv.
250
+
251
+ Args:
252
+ session_id: Optional existing session to resume.
253
+
254
+ Returns:
255
+ The shell-free Codex command argument vector.
256
+ """
257
+ command_arguments = [
258
+ CODEX_EXECUTABLE,
259
+ CODEX_EXEC_SUBCOMMAND,
260
+ CODEX_MODEL_FLAG,
261
+ ADVISOR_CODEX_MODEL_ID,
262
+ CODEX_CONFIG_FLAG,
263
+ CODEX_REASONING_CONFIG,
264
+ CODEX_SANDBOX_FLAG,
265
+ CODEX_READ_ONLY_SANDBOX,
266
+ CODEX_JSON_FLAG,
267
+ ]
268
+ if session_id is not None:
269
+ command_arguments.extend([CODEX_RESUME_SUBCOMMAND, session_id])
270
+ command_arguments.append(CODEX_PROMPT_FROM_STDIN)
271
+ return command_arguments
272
+
273
+
274
+ def _guidance_signal(guidance: str) -> str | None:
275
+ for each_line in guidance.splitlines():
276
+ stripped_line = each_line.strip()
277
+ if not stripped_line:
278
+ continue
279
+ return stripped_line if stripped_line in ALL_ADVISOR_GUIDANCE_SIGNALS else None
280
+ return None
281
+
282
+
283
+ def parse_codex_jsonl_reply(
284
+ jsonl_text: str,
285
+ existing_session_id: str | None,
286
+ is_sol_enabled: bool,
287
+ ) -> CodexSolAdvisorReply:
288
+ """Parse strict Codex JSONL into a session id and final guidance.
289
+
290
+ Args:
291
+ jsonl_text: JSONL emitted by the Codex CLI.
292
+ existing_session_id: Optional session id required on resume.
293
+ is_sol_enabled: Whether the attempted route had Sol enabled.
294
+
295
+ Returns:
296
+ The parsed guidance or an explicit Fable fallback reply.
297
+ """
298
+ discovered_session_id: str | None = None
299
+ final_guidance: str | None = None
300
+ try:
301
+ for each_line in jsonl_text.splitlines():
302
+ if not each_line.strip():
303
+ continue
304
+ event = json.loads(each_line)
305
+ if not isinstance(event, dict):
306
+ return _reply_fallback(SOL_MALFORMED_JSONL_REASON, is_sol_enabled)
307
+ if event.get("type") == "thread.started":
308
+ thread_id = event.get("thread_id")
309
+ if isinstance(thread_id, str) and thread_id.strip():
310
+ discovered_session_id = thread_id.strip()
311
+ completed_event = event.get("item")
312
+ if (
313
+ event.get("type") == "item.completed"
314
+ and isinstance(completed_event, dict)
315
+ and completed_event.get("type") == "agent_message"
316
+ and isinstance(completed_event.get("text"), str)
317
+ ):
318
+ final_guidance = completed_event["text"].strip()
319
+ except (TypeError, json.JSONDecodeError):
320
+ return _reply_fallback(SOL_MALFORMED_JSONL_REASON, is_sol_enabled)
321
+ if discovered_session_id is None:
322
+ return _reply_fallback(SOL_MISSING_SESSION_REASON, is_sol_enabled)
323
+ if (
324
+ existing_session_id is not None
325
+ and discovered_session_id != existing_session_id
326
+ ):
327
+ return _reply_fallback(SOL_MISSING_SESSION_REASON, is_sol_enabled)
328
+ if not final_guidance:
329
+ return _reply_fallback(SOL_REPLY_FAILURE_REASON, is_sol_enabled)
330
+ guidance_signal = _guidance_signal(final_guidance)
331
+ if guidance_signal is None:
332
+ return _reply_fallback(SOL_INVALID_SIGNAL_REASON, is_sol_enabled)
333
+ return _reply_success(discovered_session_id, final_guidance, guidance_signal)
334
+
335
+
336
+ def run_codex_sol_advisor(
337
+ prompt: str,
338
+ working_directory: Path,
339
+ preflight: SolPreflight | None,
340
+ probe_path: Path | None,
341
+ setting_by_name: Mapping[str, str] | None,
342
+ session_id: str | None,
343
+ process_runner: Callable[..., subprocess.CompletedProcess[str]],
344
+ ) -> CodexSolAdvisorReply:
345
+ """Run one usage-gated read-only Sol bind or resume attempt.
346
+
347
+ Args:
348
+ prompt: Advisor charter or delta consult sent to Codex.
349
+ working_directory: Repository directory supplied to the CLI.
350
+ preflight: Optional precomputed usage-meter decision.
351
+ probe_path: Optional installed usage-probe path.
352
+ setting_by_name: Optional environment-like settings mapping.
353
+ session_id: Existing session id for a resume attempt.
354
+ process_runner: Callable used to execute the probe and Codex.
355
+
356
+ Returns:
357
+ The parsed Sol guidance or an explicit Fable fallback reply.
358
+ """
359
+ is_sol_enabled = is_sol_advisor_enabled(setting_by_name)
360
+ if not is_sol_enabled:
361
+ return _reply_fallback("Sol advisor flag is disabled", False)
362
+ resolved_preflight = (
363
+ run_sol_preflight(
364
+ probe_path=(
365
+ resolve_usage_probe_path(Path.home())
366
+ if probe_path is None
367
+ else probe_path
368
+ ),
369
+ process_runner=process_runner,
370
+ )
371
+ if preflight is None
372
+ else preflight
373
+ )
374
+ if not resolved_preflight.eligible:
375
+ return _reply_fallback(resolved_preflight.reason, is_sol_enabled)
376
+ try:
377
+ completed_process = process_runner(
378
+ build_codex_arguments(session_id=session_id),
379
+ cwd=str(working_directory),
380
+ input=prompt,
381
+ capture_output=True,
382
+ text=True,
383
+ check=False,
384
+ shell=False,
385
+ timeout=SOL_CODEX_TIMEOUT_SECONDS,
386
+ )
387
+ except subprocess.TimeoutExpired as bind_error:
388
+ return _reply_fallback(
389
+ f"{SOL_CODEX_TIMEOUT_REASON}: {bind_error}", is_sol_enabled
390
+ )
391
+ except (OSError, subprocess.SubprocessError) as bind_error:
392
+ return _reply_fallback(
393
+ f"{SOL_BIND_FAILURE_REASON}: {bind_error}", is_sol_enabled
394
+ )
395
+ if completed_process.returncode != 0:
396
+ return _reply_fallback(
397
+ f"{SOL_BIND_FAILURE_REASON}: process exit {completed_process.returncode}",
398
+ is_sol_enabled,
399
+ )
400
+ return parse_codex_jsonl_reply(
401
+ completed_process.stdout,
402
+ existing_session_id=session_id,
403
+ is_sol_enabled=is_sol_enabled,
404
+ )
405
+
406
+
407
+ def build_argument_parser() -> argparse.ArgumentParser:
408
+ """Build the command-line parser for Sol bind and resume.
409
+
410
+ Returns:
411
+ The parser for the helper's bind and resume modes.
412
+ """
413
+ argument_parser = argparse.ArgumentParser(
414
+ description="Bind or consult a read-only Codex Sol xhigh advisor."
415
+ )
416
+ mode_group = argument_parser.add_mutually_exclusive_group(required=True)
417
+ mode_group.add_argument("--bind", action="store_true")
418
+ mode_group.add_argument("--resume", metavar=SOL_SESSION_ID_METAVAR)
419
+ argument_parser.add_argument("--cwd", required=True, type=Path)
420
+ return argument_parser
421
+
422
+
423
+ def main(all_cli_arguments: Sequence[str]) -> int:
424
+ """Run one bind or resume from stdin and print a JSON response.
425
+
426
+ Args:
427
+ all_cli_arguments: Command-line arguments without the program name.
428
+
429
+ Returns:
430
+ Zero for a successful Sol response, or one for an explicit fallback.
431
+ """
432
+ parsed_arguments = build_argument_parser().parse_args(list(all_cli_arguments))
433
+ advisor_reply = run_codex_sol_advisor(
434
+ prompt=sys.stdin.read(),
435
+ working_directory=parsed_arguments.cwd,
436
+ preflight=None,
437
+ probe_path=None,
438
+ setting_by_name=os.environ,
439
+ session_id=parsed_arguments.resume if not parsed_arguments.bind else None,
440
+ process_runner=subprocess.run,
441
+ )
442
+ reply_payload = asdict(advisor_reply)
443
+ reply_payload[SPAWN_OUTCOME_KEY] = reply_payload.pop("outcome")
444
+ print(json.dumps(reply_payload, sort_keys=True))
445
+ return 0 if advisor_reply.successful else 1
446
+
447
+
448
+ if __name__ == "__main__":
449
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,21 @@
1
+ """Shared advisor_route_constants for advisor selection and replies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ ADVISOR_MODEL_TIER: str = "Sol"
6
+ ADVISOR_CODEX_MODEL_ID: str = "gpt-5.6-sol"
7
+ ADVISOR_FALLBACK_TIER: str = "Fable"
8
+ ADVISOR_FALLBACK_RESULT: str = "fable"
9
+ ALL_ADVISOR_GUIDANCE_SIGNALS: frozenset[str] = frozenset(
10
+ {"ENDORSE", "CORRECTION", "PLAN", "STOP"}
11
+ )
12
+
13
+ TIER_KEY: str = "tier"
14
+ SPAWN_OUTCOME_KEY: str = "result"
15
+ SPAWN_SUCCESS_TOKEN: str = "spawned"
16
+ CLI_BIND_SUCCESS_TOKEN: str = "cli"
17
+ CODEX_BIND_SUCCESS_TOKEN: str = "codex"
18
+
19
+ ALL_CODEX_MODEL_ID_BY_TIER: dict[str, str] = {
20
+ ADVISOR_MODEL_TIER: ADVISOR_CODEX_MODEL_ID,
21
+ }
@@ -1,17 +1,11 @@
1
1
  """Constants for the model-tier-run validator and CLI / Agent alias map.
2
2
 
3
3
  They name the parts of a spawn-walk log: the tier ladder (strongest
4
- first), the two host profiles, the spawn-log dict keys, and the tokens
5
- that mark a bind.
4
+ first), the two host profiles, and the validation messages. Shared log
5
+ keys and bind-result tokens live in ``advisor_route_constants``.
6
6
 
7
7
  ::
8
8
 
9
- attempt = {"tier": "Opus", "result": "spawned"}
10
- ^^^^^^ ^^^^^^^^^
11
- a ladder tier SPAWN_SUCCESS_TOKEN
12
- ok: "cli" marks a CLI Claude-chain bind (CLI_BIND_SUCCESS_TOKEN)
13
- flag: any other result token counts as no bind
14
-
15
9
  The alias map turns each tier into its short CLI / Agent name (``opus``,
16
10
  ``third-party``), never a dated full model ID.
17
11
 
@@ -24,6 +18,11 @@ Host-profile detection (see ``detect_host_profile``):
24
18
 
25
19
  from __future__ import annotations
26
20
 
21
+ from advisor_scripts_constants.advisor_route_constants import (
22
+ ADVISOR_FALLBACK_TIER,
23
+ ADVISOR_MODEL_TIER,
24
+ )
25
+
27
26
  HOST_PROFILE_CLAUDE: str = "Claude"
28
27
  HOST_PROFILE_THIRD_PARTY: str = "ThirdParty"
29
28
  ALL_HOST_PROFILES: tuple[str, ...] = (
@@ -31,32 +30,35 @@ ALL_HOST_PROFILES: tuple[str, ...] = (
31
30
  HOST_PROFILE_THIRD_PARTY,
32
31
  )
33
32
 
34
- ALL_MODEL_TIERS: tuple[str, ...] = ("Fable", "Opus", "Sonnet", "Haiku")
33
+ ALL_MODEL_TIERS: tuple[str, ...] = (
34
+ ADVISOR_FALLBACK_TIER,
35
+ "Opus",
36
+ "Sonnet",
37
+ "Haiku",
38
+ )
35
39
  THIRD_PARTY_MODEL_TIER: str = "ThirdParty"
36
- ALL_KNOWN_TIER_NAMES: tuple[str, ...] = (*ALL_MODEL_TIERS, THIRD_PARTY_MODEL_TIER)
40
+ ALL_KNOWN_TIER_NAMES: tuple[str, ...] = (
41
+ ADVISOR_MODEL_TIER,
42
+ *ALL_MODEL_TIERS,
43
+ THIRD_PARTY_MODEL_TIER,
44
+ )
37
45
  THIRD_PARTY_CLI_ADVISOR_FLOOR_TIER: str = "Opus"
38
46
 
39
47
  ADVISOR_SENDMESSAGE_REPLY_WAIT_SECONDS: int = 120
40
48
 
41
49
  ALL_CLI_MODEL_ID_BY_TIER: dict[str, str] = {
42
- "Fable": "fable",
50
+ ADVISOR_FALLBACK_TIER: "fable",
43
51
  "Opus": "opus",
44
52
  "Sonnet": "sonnet",
45
53
  "Haiku": "haiku",
46
54
  THIRD_PARTY_MODEL_TIER: "third-party",
47
55
  }
48
-
49
56
  HOST_PROFILE_ENV_VAR: str = "ADVISOR_HOST_PROFILE"
50
57
  THIRD_PARTY_ENV_VAR: str = "THIRD_PARTY"
51
58
  ALL_THIRD_PARTY_TRUTHY_VALUES: frozenset[str] = frozenset(
52
59
  {"1", "true", "yes", "on"}
53
60
  )
54
61
 
55
- TIER_KEY: str = "tier"
56
- SPAWN_OUTCOME_KEY: str = "result"
57
- SPAWN_SUCCESS_TOKEN: str = "spawned"
58
- CLI_BIND_SUCCESS_TOKEN: str = "cli"
59
-
60
62
  UNKNOWN_OWN_TIER_MESSAGE: str = "own_tier is not a known model tier"
61
63
  UNKNOWN_LADDER_NAME_ERROR: str = "ladder name is not a known model tier: {!r}"
62
64
  UNKNOWN_HOST_PROFILE_ERROR: str = "host profile is not a known profile: {!r}"
@@ -0,0 +1,28 @@
1
+ """Constants for the optional Codex Sol xhigh advisor bind."""
2
+
3
+ from __future__ import annotations
4
+
5
+ CODEX_EXECUTABLE: str = "codex"
6
+ CODEX_READ_ONLY_SANDBOX: str = "read-only"
7
+ CODEX_JSON_FLAG: str = "--json"
8
+ CODEX_MODEL_FLAG: str = "--model"
9
+ CODEX_CONFIG_FLAG: str = "--config"
10
+ CODEX_REASONING_CONFIG: str = 'model_reasoning_effort="xhigh"'
11
+ CODEX_PROMPT_FROM_STDIN: str = "-"
12
+ CODEX_EXEC_SUBCOMMAND: str = "exec"
13
+ CODEX_RESUME_SUBCOMMAND: str = "resume"
14
+ CODEX_SANDBOX_FLAG: str = "--sandbox"
15
+ CLAUDE_CONFIG_DIRECTORY_NAME: str = ".claude"
16
+ SOL_SESSION_ID_METAVAR: str = "SESSION_ID"
17
+ SOL_CODEX_TIMEOUT_SECONDS: float = 120.0
18
+ SOL_USAGE_PROBE_TIMEOUT_SECONDS: float = 30.0
19
+ SOL_ENV_VAR: str = "ADVISOR_SOL_XHIGH"
20
+ ALL_SOL_TRUTHY_VALUES: frozenset[str] = frozenset({"1", "true", "yes", "on"})
21
+ SOL_PREFLIGHT_FAILURE_REASON: str = "sol preflight did not establish an eligible Codex meter"
22
+ SOL_BIND_FAILURE_REASON: str = "Codex Sol xhigh bind failed"
23
+ SOL_REPLY_FAILURE_REASON: str = "Codex Sol xhigh returned no advisor guidance"
24
+ SOL_PROBE_TIMEOUT_REASON: str = "Codex Sol meter check timed out"
25
+ SOL_CODEX_TIMEOUT_REASON: str = "Codex Sol xhigh request timed out"
26
+ SOL_MALFORMED_JSONL_REASON: str = "Codex Sol xhigh returned malformed JSONL"
27
+ SOL_MISSING_SESSION_REASON: str = "Codex Sol xhigh returned no session id"
28
+ SOL_INVALID_SIGNAL_REASON: str = "Codex Sol xhigh returned an invalid guidance signal"
@@ -52,7 +52,6 @@ from advisor_scripts_constants.model_tier_run_validator_constants import ( # no
52
52
  ATTEMPT_ORDER_MISMATCH_MESSAGE,
53
53
  ATTEMPT_TIER_OUT_OF_SLICE_MESSAGE,
54
54
  CANDIDATE_TIERS_MISMATCH_MESSAGE,
55
- CLI_BIND_SUCCESS_TOKEN,
56
55
  CLI_INVALID_JSON_EXIT_CODE,
57
56
  CLI_MISSING_PATH_EXIT_CODE,
58
57
  CLI_SUCCESS_EXIT_CODE,
@@ -62,13 +61,18 @@ from advisor_scripts_constants.model_tier_run_validator_constants import ( # no
62
61
  MISSING_FALLBACK_REASON_MESSAGE,
63
62
  SELECTED_TIER_MISMATCH_MESSAGE,
64
63
  SELECTED_TIER_NOT_NULL_MESSAGE,
65
- SPAWN_OUTCOME_KEY,
66
- SPAWN_SUCCESS_TOKEN,
67
64
  THIRD_PARTY_CLI_ADVISOR_FLOOR_TIER,
68
65
  THIRD_PARTY_MODEL_TIER,
69
- TIER_KEY,
70
66
  UNKNOWN_OWN_TIER_MESSAGE,
71
67
  )
68
+ from advisor_scripts_constants.advisor_route_constants import ( # noqa: E402
69
+ ADVISOR_MODEL_TIER,
70
+ CODEX_BIND_SUCCESS_TOKEN,
71
+ CLI_BIND_SUCCESS_TOKEN,
72
+ SPAWN_OUTCOME_KEY,
73
+ SPAWN_SUCCESS_TOKEN,
74
+ TIER_KEY,
75
+ )
72
76
  from tier_model_ids import canonical_tier_name # noqa: E402
73
77
 
74
78
 
@@ -79,6 +83,7 @@ class ModelTierRun:
79
83
  attempts: list[dict[str, str]]
80
84
  selected_tier: str | None
81
85
  fallback_reason: str | None = None
86
+ is_sol_enabled: bool = False
82
87
 
83
88
 
84
89
  class ModelTierRunError(ValueError):
@@ -95,15 +100,22 @@ def _canonical_tier_list(all_tier_names: list[str]) -> list[str] | None:
95
100
  return all_canonical_tiers
96
101
 
97
102
 
98
- def _expected_candidate_tiers(own_tier: str) -> list[str]:
103
+ def _expected_candidate_tiers(
104
+ own_tier: str, is_sol_enabled: bool = False
105
+ ) -> list[str]:
99
106
  maybe_canonical_own_tier = canonical_tier_name(own_tier)
100
107
  if maybe_canonical_own_tier is None:
101
108
  raise ModelTierRunError(f"{UNKNOWN_OWN_TIER_MESSAGE}: {own_tier!r}")
109
+ if maybe_canonical_own_tier == ADVISOR_MODEL_TIER:
110
+ raise ModelTierRunError(f"{UNKNOWN_OWN_TIER_MESSAGE}: {own_tier!r}")
102
111
  if maybe_canonical_own_tier == THIRD_PARTY_MODEL_TIER:
103
112
  floor_index = ALL_MODEL_TIERS.index(THIRD_PARTY_CLI_ADVISOR_FLOOR_TIER)
104
- return list(ALL_MODEL_TIERS[: floor_index + 1])
105
- floor_index = ALL_MODEL_TIERS.index(maybe_canonical_own_tier)
106
- return list(ALL_MODEL_TIERS[: floor_index + 1])
113
+ else:
114
+ floor_index = ALL_MODEL_TIERS.index(maybe_canonical_own_tier)
115
+ all_expected_candidates = list(ALL_MODEL_TIERS[: floor_index + 1])
116
+ if is_sol_enabled:
117
+ all_expected_candidates.insert(0, ADVISOR_MODEL_TIER)
118
+ return all_expected_candidates
107
119
 
108
120
 
109
121
  def _is_successful_attempt_outcome(
@@ -112,6 +124,10 @@ def _is_successful_attempt_outcome(
112
124
  ) -> bool:
113
125
  if canonical_tier == THIRD_PARTY_MODEL_TIER:
114
126
  return False
127
+ if canonical_tier == ADVISOR_MODEL_TIER:
128
+ return outcome_token == CODEX_BIND_SUCCESS_TOKEN
129
+ if outcome_token == CODEX_BIND_SUCCESS_TOKEN:
130
+ return False
115
131
  if outcome_token == SPAWN_SUCCESS_TOKEN:
116
132
  return True
117
133
  if outcome_token == CLI_BIND_SUCCESS_TOKEN:
@@ -144,7 +160,10 @@ def validate_model_tier_run(run: ModelTierRun) -> None:
144
160
  Raises:
145
161
  ModelTierRunError: When any invariant is violated.
146
162
  """
147
- all_expected_candidates = _expected_candidate_tiers(run.own_tier)
163
+ all_expected_candidates = _expected_candidate_tiers(
164
+ run.own_tier,
165
+ is_sol_enabled=run.is_sol_enabled,
166
+ )
148
167
  maybe_canonical_candidates = _canonical_tier_list(run.candidate_tiers)
149
168
  if maybe_canonical_candidates != all_expected_candidates:
150
169
  raise ModelTierRunError(CANDIDATE_TIERS_MISMATCH_MESSAGE)
@@ -215,12 +234,16 @@ def load_model_tier_run_from_json_path(from_path: Path) -> ModelTierRun:
215
234
  TypeError: When a field has the wrong shape.
216
235
  """
217
236
  parsed_payload = json.loads(from_path.read_text(encoding="utf-8"))
237
+ raw_sol_enabled = parsed_payload.get("sol_enabled", False)
238
+ if not isinstance(raw_sol_enabled, bool):
239
+ raise TypeError("sol_enabled must be a boolean")
218
240
  return ModelTierRun(
219
241
  own_tier=parsed_payload["own_tier"],
220
242
  candidate_tiers=list(parsed_payload["candidate_tiers"]),
221
243
  attempts=list(parsed_payload["attempts"]),
222
244
  selected_tier=parsed_payload.get("selected_tier"),
223
245
  fallback_reason=parsed_payload.get("fallback_reason"),
246
+ is_sol_enabled=raw_sol_enabled,
224
247
  )
225
248
 
226
249