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
@@ -3,26 +3,26 @@
3
3
  Detail behind the **Advisor block** section of [`advisor-protocol.md`](../advisor-protocol.md).
4
4
  Open this when assembling the block for an executor spawn prompt.
5
5
 
6
- Assembly order: one transport preamble picked by host profile, then the shared core, then — for an executor at Sonnet or below — the weak-executor add-on.
6
+ Assembly order: one transport preamble picked by host profile, then the shared core, then the weak-executor add-on for an executor at Sonnet or below.
7
7
  Paste the assembled block at the **top** of the spawn prompt, ahead of any other sentence that mentions the advisor.
8
- The assembled block is self-contained — the executor receives this text alone, so every path it names is absolute.
8
+ The assembled block is self-contained. The executor receives this text alone, so every path it names is absolute.
9
9
  The consult rules it restates are owned by [`consult-format.md`](consult-format.md); edit that file first and carry the change here.
10
10
 
11
- ## Transport preamble — Claude host
11
+ ## Transport preamble for a Claude host
12
12
 
13
13
  > A shared session advisor named `<name>` is reachable via SendMessage; send each consult to it directly by that name.
14
14
 
15
- ## Transport preamble — Codex host
15
+ ## Transport preamble for a Codex host
16
16
 
17
- > A shared session advisor named `<name>` is reachable as a native Codex Sol subagent; send each consult to it in-session by that name.
17
+ > A shared session advisor named `<name>` is reachable as a native Codex Astra subagent; send each consult to it in-session by that name.
18
18
 
19
- ## Transport preamble — third-party host
19
+ ## Transport preamble for a third-party host
20
20
 
21
21
  > The orchestrating session owns a standing advisor for this run.
22
- > The advisor chain: Claude Fable, then Sol through the Codex CLI when Fable is out of usage and the sol flag and its preflight open that rung. Both rungs use `ADVISOR_EFFORT` (default low). When neither binds, there is no advisor.
22
+ > The advisor chain: Claude Fable, then Astra through the Codex CLI when Fable is out of usage and the Astra flag and its preflight open that rung. Both rungs use `ADVISOR_EFFORT` (default low). When neither binds, there is no advisor.
23
23
  > The orchestrating session is your one path to it: send each consult as a report to the session that assigned you, and it relays the advisor's reply.
24
24
 
25
- ## Shared core — every host
25
+ ## Shared core for every host
26
26
 
27
27
  > Consult before locking in a nontrivial approach, once you believe your assignment is done, before any hard-to-reverse action, when the same failure repeats or progress has stalled, and when the chosen approach is being reconsidered.
28
28
  > Build the first consult with the complete format in `$HOME/.claude/_shared/advisor/reference/consult-format.md`: assignment, desired outcome, constraints and exclusions, actions taken in order, real output and current state, live decision or blocker, validation evidence, unresolved risks, and load-bearing paths or excerpts. Later consults carry only changed evidence.
@@ -31,7 +31,7 @@ The consult rules it restates are owned by [`consult-format.md`](consult-format.
31
31
  > Replies open with one of ENDORSE, CORRECTION, PLAN, or STOP — treat CORRECTION and PLAN as actions to take.
32
32
  > On STOP, or when the advisor is unreachable, stop and report that back to whoever assigned you; advisor binding and the four signals stay with the session that owns the advisor.
33
33
 
34
- ## Weak-executor add-on — Sonnet or below, either host
34
+ ## Weak-executor add-on for Sonnet or below, either host
35
35
 
36
36
  > Send your first consult right after orientation and before your first write.
37
37
  > Send a completion consult once your writes and test output exist — that consult asks the advisor to hunt for missing requirements, untested behavior, wrong assumptions, unhandled edge cases, evidence gaps, and early completion claims.
@@ -0,0 +1,39 @@
1
+ # Astra rung
2
+
3
+ Detail behind the **Host profiles → Astra rung** section of [`advisor-protocol.md`](../advisor-protocol.md).
4
+ Open this when Fable is out of usage on a Claude or ThirdParty host and an Astra fallback bind is starting.
5
+ On a Codex host, Astra is the in-session default. See [`identity.md`](identity.md).
6
+
7
+ ## Flag
8
+
9
+ `ADVISOR_ASTRA=1` (or `true` / `yes` / `on`) opens the Astra fallback after Fable is out of usage. Set the variable in the helper's process environment, or pass `--enable-astra` on the helper invocation. The CLI flag opens the rung for that run without touching the environment.
10
+ Flag off both ways: fail closed when Fable did not bind.
11
+ A Windows `setx` write only updates the persisted user environment; only a process started after that write inherits the new value, so an already-running session either sets the flag in its own invoking process environment or passes `--enable-astra`.
12
+
13
+ ## Effort
14
+
15
+ `ADVISOR_EFFORT` selects Codex `model_reasoning_effort` for Astra and `--effort` for Fable: `low`, `medium`, `high`, `xhigh`, or `max`. The default is `low`, which sends `model_reasoning_effort="low"`. Pass `--effort <level>` on the helper to set effort for that Astra run without changing the environment. An unset or unrecognized value uses `low`.
16
+
17
+ Every fallback reply carries a `fallback_kind` field. `declined` means policy closed the rung (flag off, usage meter at or below the gate). `broken` means the Astra path itself failed (missing executable, spawn error, timeout, malformed reply). A `broken` fallback is a defect to report.
18
+
19
+ ## Preflight
20
+
21
+ Flag on: run the Codex preflight first.
22
+
23
+ ```
24
+ python ~/.claude/_shared/pr-loop/scripts/codex_usage_probe.py
25
+ ```
26
+
27
+ **GOTCHA. Probe path.** That installed path is the only one. `codex_astra_advisor.resolve_usage_probe_path` builds `~/.claude/_shared/pr-loop/scripts/codex_usage_probe.py` and stops. Do not hunt `skills/codex-review/`, Codex worktrees under `C:\dev\.codex\worktrees\`, or other copies. Repo home: `packages/claude-dev-env/_shared/pr-loop/scripts/`.
28
+
29
+ The shared entry point is `~/.claude/_shared/advisor/scripts/codex_astra_advisor.py`. It calls the installed probe and owns Astra bind or resume parsing. Bind with `python ~/.claude/_shared/advisor/scripts/codex_astra_advisor.py --bind --enable-astra --cwd <repo-root>` and pipe the charter on stdin. Resume with `--resume <session_id>` and pipe the delta consult on stdin.
30
+
31
+ The gate passes only when the probe exits 0, `percent_left` is finite numeric data, and `percent_left` is strictly greater than `WEEKLY_USAGE_GATE_THRESHOLD_PERCENT` from the existing probe. The exact-threshold case fails closed when Fable did not bind.
32
+
33
+ ## Branches
34
+
35
+ **Preflight pass.** Bind one Codex CLI session at `gpt-6-astra` with `model_reasoning_effort` set from `ADVISOR_EFFORT` (default `low`), `--sandbox read-only`, and JSON output. The helper receives the standing-reviewer charter on stdin and returns only parsed ENDORSE / CORRECTION / PLAN / STOP guidance with a session ID.
36
+
37
+ **Preflight fail.** Probe failure, non-zero exit, timeout, missing or malformed usage, `null`, non-finite usage, or usage at or below the threshold fails closed when Fable did not bind.
38
+
39
+ The helper owns the Astra attempt and returns an explicit fallback result. The consuming advisor path owns the Fable bind. Apply the same gate to every Astra attempt, including resume.
@@ -9,7 +9,7 @@ Call `resolve_session_identity` from `$HOME/.claude/_shared/advisor/scripts/tier
9
9
 
10
10
  | Identity text | Host profile | Bind path |
11
11
  |---|---|---|
12
- | a `codex` token | Codex | In-session Sol spawn |
12
+ | a `codex` token | Codex | In-session Astra spawn |
13
13
  | a `claude` token | Claude | In-session Fable spawn of `session-advisor` |
14
14
  | any other identity | ThirdParty | Headless CLI chain |
15
15
 
@@ -17,11 +17,11 @@ When both `codex` and `claude` tokens appear, Codex wins. Empty text is ThirdPar
17
17
 
18
18
  ## Bind path
19
19
 
20
- **Claude.** Spawn `subagent_type: session-advisor` at Fable through the Agent tool. When Fable is out of usage, bind Sol through the Codex helper if `ADVISOR_SOL` is on. Fail closed when neither binds.
20
+ **Claude.** Spawn `subagent_type: session-advisor` at Fable through the Agent tool. When Fable is out of usage, bind Astra through the Codex helper if `ADVISOR_ASTRA` is on. Fail closed when neither binds.
21
21
 
22
- **Codex.** Spawn a native in-session Sol subagent at `resolve_codex_model_id("Sol")` (`gpt-5.6-sol`). Walk `candidate_tiers = ["Sol"]`. Record `{tier: "Sol", result: "spawned"}` on success. The `ADVISOR_SOL` flag is not required. Fail closed when Sol does not bind. Do not walk Fable on a Codex host.
22
+ **Codex.** Spawn a native in-session Astra subagent at `resolve_codex_model_id("Astra")` (`gpt-6-astra`). Walk `candidate_tiers = ["Astra"]`. Record `{tier: "Astra", result: "spawned"}` on success. The `ADVISOR_ASTRA` flag is not required. Fail closed when Astra does not bind. Do not walk Fable on a Codex host.
23
23
 
24
- **ThirdParty.** Bind Fable through the CLI Claude-chain. When Fable is out of usage, bind Sol through the Codex helper if `ADVISOR_SOL` is on. Fail closed when neither binds.
24
+ **ThirdParty.** Bind Fable through the CLI Claude-chain. When Fable is out of usage, bind Astra through the Codex helper if `ADVISOR_ASTRA` is on. Fail closed when neither binds.
25
25
 
26
26
  ## Mechanical override
27
27
 
@@ -5,7 +5,7 @@ Open this when the advisor drifts, dies, or the task pivots.
5
5
 
6
6
  ## Claude host
7
7
 
8
- The session that spawns the shared advisor owns its whole lifecycle — spawn, drift-respawn, and shutdown.
8
+ The session that spawns the shared advisor owns its whole lifecycle: spawn, drift-respawn, and shutdown.
9
9
  Every other consumer (executors, or any other consulting session) reaches it by message alone; spawn, respawn, and shutdown belong to the owning session.
10
10
  One shared advisor exists per orchestrated session, owned by the session that spawned it.
11
11
 
@@ -14,15 +14,15 @@ A **Fable**-tier re-spawn carries the exact token `FABLE-SPAWN-AUTHORIZED` in th
14
14
 
15
15
  ## Codex host
16
16
 
17
- The session that spawns the shared Sol subagent owns its whole lifecycle — spawn, drift-respawn, and shutdown.
17
+ The session that spawns the shared Astra subagent owns its whole lifecycle: spawn, drift-respawn, and shutdown.
18
18
  Every other consumer reaches it by message alone; spawn, respawn, and shutdown belong to the owning session.
19
19
 
20
- **Re-spawn on drift.** If a reply shows the agent working from a stale picture, or the session pivots to an unrelated task, the owning session ends that agent and spawns a fresh native Sol subagent with a new charter.
20
+ **Re-spawn on drift.** If a reply shows the agent working from a stale picture, or the session pivots to an unrelated task, the owning session ends that agent and spawns a fresh native Astra subagent with a new charter.
21
21
 
22
22
  ## Third-party host
23
23
 
24
- The orchestrating session owns the Claude CLI advisor bind for the whole run — first bind, re-bind on drift or lost `session_id`, and fail-closed report when the chain cannot serve.
24
+ The orchestrating session owns the CLI advisor bind for the whole run: first bind, re-bind on drift or lost `session_id`, and fail-closed report when the bound path cannot serve.
25
25
 
26
- **Re-bind on drift.** If a reply shows a stale picture, the task pivots, or `--resume` fails after a usage-limit failover (session stores are per binary/account), re-bind through `claude_chain_runner.py` with the charter plus a compact recap of consults so far.
27
- Capture the new `session_id`, and log a fresh Fable walk with `result: "cli"` on success, then Sol when that rung is open.
26
+ **Re-bind on drift.** If a reply shows a stale picture, the task pivots, or `--resume` fails after a usage-limit failover (session stores are per binary/account), re-bind Fable through `claude_chain_runner.py` with the charter plus a compact recap of consults so far.
27
+ Capture the new `session_id`, and log a fresh Fable walk with `result: "cli"` on success, then Astra when that rung is open. An Astra re-bind uses `codex_astra_advisor.py --bind --enable-astra` with the same charter and recap, and records `result: "codex"` on success.
28
28
  Executors keep reporting to the orchestrating session; advisor binding stays with that session alone.
@@ -6,16 +6,16 @@ Detail behind the **Model floor** section of [`advisor-protocol.md`](../advisor-
6
6
 
7
7
  Write the log as JSON with these field names:
8
8
 
9
- - `own_tier` — the consuming session's tier (recorded; it does not add Opus to the advisor walk).
10
- - `host_profile` — `Claude`, `Codex`, or `ThirdParty`. Omit to default to `Claude`.
11
- - `candidate_tiers` — on Claude and ThirdParty, Fable, plus Sol when `sol_enabled` is true. On Codex, Sol only.
12
- - `sol_enabled` — a boolean recorded before candidate selection; on Claude and ThirdParty, `true` adds Sol after Fable and `false` walks Fable alone. On Codex this flag does not change the walk.
13
- - `attempts` — one `{tier, result}` entry appended as each bind try happens; `result` is `codex` for a Sol helper bind, `spawned` for an in-session spawn (Claude Agent or Codex native Sol), `cli` for a CLI Claude-chain bind, or a failure reason such as `unavailable`.
14
- - `selected_tier` — the tier of the first successful bind (first `codex`, `spawned`, or `cli` entry), or `null` paired with a `fallback_reason` string when none bound.
9
+ - `own_tier`: the consuming session's tier (recorded; it does not add Opus to the advisor walk).
10
+ - `host_profile`: `Claude`, `Codex`, or `ThirdParty`. Omit to default to `Claude`.
11
+ - `candidate_tiers`: on Claude and ThirdParty, Fable, plus Astra when `astra_enabled` is true. On Codex, Astra only.
12
+ - `astra_enabled`: a boolean recorded before candidate selection. On Claude and ThirdParty, `true` adds Astra after Fable and `false` walks Fable alone. On Codex this flag does not change the walk.
13
+ - `attempts`: one `{tier, result}` entry appended as each bind try happens. `result` is `codex` for an Astra helper bind, `spawned` for an in-session spawn (Claude Agent or Codex native Astra), `cli` for a CLI Claude-chain bind, or a failure reason such as `unavailable`.
14
+ - `selected_tier`: the tier of the first successful bind (first `codex`, `spawned`, or `cli` entry), or `null` paired with a `fallback_reason` string when none bound.
15
15
 
16
16
  ## Log path
17
17
 
18
- Write to a path the session controls — typically `<job-temp-dir>/model-tier-run.json`, or the OS temp directory when no job directory exists.
18
+ Write to a path the session controls, typically `<job-temp-dir>/model-tier-run.json`, or the OS temp directory when no job directory exists.
19
19
 
20
20
  ## Validator
21
21
 
@@ -28,5 +28,5 @@ Exit code `1` means a ladder invariant failed.
28
28
  Exit code `2` means the path or JSON was unusable.
29
29
  The same checks are available in-process via `validate_model_tier_run(run)`.
30
30
 
31
- The validator checks ladder shape only: the candidate slice, the order of bind tries, and the success-token rules per tier and host. On Claude and ThirdParty, Sol is attempted after Fable when `sol_enabled` is true, and `selected_tier: "Sol"` requires `result: "codex"`. On Codex, the walk is Sol only, and `result: "spawned"` or `result: "codex"` counts as success.
32
- Host policy sits on top of it — see the Model floor section of the protocol.
31
+ The validator checks ladder shape only: the candidate slice, the order of bind tries, and the success-token rules per tier and host. On Claude and ThirdParty, Astra is attempted after Fable when `astra_enabled` is true, and `selected_tier: "Astra"` requires `result: "codex"`. On Codex, the walk is Astra only, and `result: "spawned"` or `result: "codex"` counts as success.
32
+ Host policy sits on top of it. See the Model floor section of the protocol.
@@ -3,23 +3,23 @@
3
3
  Detail behind the **Host profiles → Third-party host** section of [`advisor-protocol.md`](../advisor-protocol.md).
4
4
  Open this when binding or re-binding the advisor from a third-party (non-Claude, non-Codex) harness.
5
5
 
6
- ## GOTCHA — Cursor Sol first shot
6
+ ## GOTCHA: Cursor Astra first shot
7
7
 
8
- Cursor is a third-party host. When the walk reaches Sol (`ADVISOR_SOL=1` / `--enable-sol` after Fable is out of usage, or the user asks for Sol), bind Sol on the **first** tool call through the headless Codex helper. Do not use the Agent or Task tool, do not spawn Grok as a substitute, and do not search the filesystem for `codex_usage_probe.py`.
8
+ Cursor is a third-party host. When the walk reaches Astra (`ADVISOR_ASTRA=1` / `--enable-astra` after Fable is out of usage, or the user asks for Astra), bind Astra on the **first** tool call through the headless Codex helper. Do not use the Agent or Task tool, do not spawn Grok as a substitute, and do not search the filesystem for `codex_usage_probe.py`.
9
9
 
10
10
  ```
11
- python "$HOME/.claude/_shared/advisor/scripts/codex_sol_advisor.py" --bind --enable-sol --cwd <repo-root>
11
+ python "$HOME/.claude/_shared/advisor/scripts/codex_astra_advisor.py" --bind --enable-astra --cwd <repo-root>
12
12
  ```
13
13
 
14
- Pipe the standing-reviewer charter on stdin. Persist `session_id` from the JSON reply. Later consults: `--resume <session_id>` with the delta on stdin. Capture helper / `codex` stdout as UTF-8 on Windows (`encoding="utf-8"`, `errors="replace"`). Probe path and preflight: [`sol-rung.md`](sol-rung.md).
14
+ Pipe the standing-reviewer charter on stdin. Persist `session_id` from the JSON reply. Later consults: `--resume <session_id>` with the delta on stdin. Capture helper / `codex` stdout as UTF-8 on Windows (`encoding="utf-8"`, `errors="replace"`). Probe path and preflight: [`astra-rung.md`](astra-rung.md).
15
15
 
16
- When the Sol flag is off, follow the Claude-chain steps below.
16
+ When the Astra flag is off, follow the Claude-chain steps below.
17
17
 
18
18
  ## Bind steps
19
19
 
20
20
  1. Name the session identity first (protocol **Host profiles**). This path is for a ThirdParty profile.
21
- 2. Walk `candidate_tiers = ["Fable"]`. When Fable is out of usage, the sol rung ([`sol-rung.md`](sol-rung.md)) binds Sol after Fable (`candidate_tiers = ["Fable", "Sol"]`).
22
- 3. **CLI bind (primary path):** for each candidate top-down, pipe a charter file into:
21
+ 2. Walk `candidate_tiers = ["Fable"]`. When Fable is out of usage, the Astra rung ([`astra-rung.md`](astra-rung.md)) binds Astra after Fable (`candidate_tiers = ["Fable", "Astra"]`).
22
+ 3. **CLI bind (primary path):** for Fable, pipe a charter file into:
23
23
 
24
24
  ```
25
25
  python "$HOME/.claude/scripts/claude_chain_runner.py" --routing-mode ordered_account -- -p --model <alias> --effort <effort> --output-format json
@@ -27,15 +27,15 @@ When the Sol flag is off, follow the Claude-chain steps below.
27
27
 
28
28
  Use `--model fable --effort` with the value of `ADVISOR_EFFORT` (default `low`) on Fable.
29
29
  User-facing wording follows [`rules/asd-ste100-language.md`](../../../rules/asd-ste100-language.md).
30
- A root advisor bind uses `--routing-mode ordered_account` — walk order, failover, and the `advisor_blocked` terminal status are in [`cli-chain.md`](cli-chain.md).
30
+ A root advisor bind uses `--routing-mode ordered_account`. Walk order, failover, and the `advisor_blocked` terminal status are in [`cli-chain.md`](cli-chain.md).
31
31
  4. Stop at the first successful bind.
32
- Record `{tier, result: "cli"}` and set `selected_tier` to that tier.
32
+ Record `{tier, result: "cli"}` for Fable or `{tier: "Astra", result: "codex"}` for the Astra helper, and set `selected_tier` to that tier.
33
33
  Persist `session_id` from the JSON events (any event carries it; the runner also surfaces it on `ChainInvocationOutcome.session_id`; reply text is the `type == "result"` event's `.result` field).
34
- Run every bind and every later consult with cwd set to the repo root the work is for — Claude sessions are project-scoped by working directory.
34
+ Run every bind and every later consult with cwd set to the repo root the work is for. Claude sessions are project-scoped by working directory.
35
35
  5. **Fail closed:** when every candidate fails (chain exhausted, `advisor_blocked`, or model unavailable), set `selected_tier = null` and a `fallback_reason`, report that the advisor is unreachable, and **stop**. ENDORSE / CORRECTION / PLAN / STOP come only from a bound advisor.
36
36
  6. Assemble and paste each executor's Advisor block from [`advisor-block.md`](advisor-block.md). Executors report to the orchestrating session; that session consults the bound advisor and relays the four-signal reply.
37
37
 
38
38
  ## Session model field
39
39
 
40
40
  Resolve a third-party session's own model field with `resolve_cli_model_id("ThirdParty")` → `third-party` when a host model alias is required.
41
- The **advisor** bind uses the Fable alias. Sol binds through the Codex helper.
41
+ The **advisor** bind uses the Fable alias. Astra binds through the Codex helper.
@@ -3,39 +3,39 @@
3
3
  Detail behind the **Warm-up** section of [`advisor-protocol.md`](../advisor-protocol.md).
4
4
  Open this when binding the warm advisor on a Claude host or a Codex host, and for the charter text on every host.
5
5
 
6
- ## Spawn fields — Claude host
6
+ ## Spawn fields for a Claude host
7
7
 
8
- The consuming skill's session walks the candidate tiers top-down. For each try, spawn with:
8
+ The consuming skill's session tries Fable first. Spawn with:
9
9
 
10
10
  - `subagent_type: session-advisor` (see [`agents/session-advisor.md`](../../../agents/session-advisor.md) for the full signal contract).
11
- - `model`: the short alias for that try's candidate tier via `resolve_cli_model_id` (alias table: [`cli-chain.md`](cli-chain.md)) — for example `fable`. The walk tries Fable first, then Sol when that rung is open.
11
+ - `model`: the short alias via `resolve_cli_model_id("Fable")` (alias table: [`cli-chain.md`](cli-chain.md)): `fable`. When Fable is out of usage and the Astra rung is open, continue with the Codex helper in [`astra-rung.md`](astra-rung.md) as the Astra fallback.
12
12
  - `name`: a name the session and every consumer will use to reach it (e.g. `team-advisor-agent`).
13
13
  - `run_in_background: true`.
14
- - `prompt`: the charter below. A **Fable**-tier try carries the exact token `FABLE-SPAWN-AUTHORIZED` in that prompt — `hooks/blocking/fable_spawn_gate.py` denies every `Agent` or `Task` spawn at `model: fable` whose prompt lacks that token. A try at any other tier needs no token.
14
+ - `prompt`: the charter below. A **Fable**-tier try carries the exact token `FABLE-SPAWN-AUTHORIZED` in that prompt. `hooks/blocking/fable_spawn_gate.py` denies every `Agent` or `Task` spawn at `model: fable` whose prompt lacks that token. A try at any other tier needs no token.
15
15
 
16
- Stop at the first successful spawn. That try's tier is `selected_tier`; the warm agent lives at that tier for the rest of the session.
16
+ Stop at the first successful bind. That try's tier is `selected_tier`; the warm advisor lives at that tier for the rest of the session.
17
17
 
18
18
  ## Charter (the spawn prompt)
19
19
 
20
- The charter states: the agent's role — standing reviewer, answers only via SendMessage, with file edits and commands out of its scope — the repo path, and the session's current goal in two or three sentences.
20
+ The charter states the agent's role, the repo path, and the session's current goal in two or three sentences. The agent is a standing reviewer. It answers only via SendMessage. File edits and commands are out of its scope.
21
21
  On a Fable-tier try, include the exact token `FABLE-SPAWN-AUTHORIZED` as plain text in this prompt (substring match; the gate reads the token alone, wherever it came from).
22
22
  State plainly:
23
23
 
24
24
  - Every consult carries: who is asking (name and assignment), what changed since their last consult, the live decision or question, and any load-bearing paths or excerpts.
25
- - Reply via SendMessage to whoever sent the consult, by name — each reply goes back to its own sender, and many different consumers may reach this one agent.
26
- - Treat each consult on its own terms, keyed to the sender's stated assignment. Different consumers' consults will interleave in this one transcript — keep each consumer's context separate, and blend only when a consult explicitly asks for that.
25
+ - Reply via SendMessage to whoever sent the consult, by name. Each reply goes back to its own sender, and many different consumers may reach this one agent.
26
+ - Treat each consult on its own terms, keyed to the sender's stated assignment. Different consumers' consults will interleave in this one transcript. Keep each consumer's context separate, and blend only when a consult explicitly asks for that.
27
27
  - If a consult re-raises a question already answered, with nothing new attached, reply by restating the prior answer and naming it as a restatement.
28
- - Every reply, including this first bind turn, opens its first line with exactly one of the four uppercase signal words — `ENDORSE`, `CORRECTION`, `PLAN`, `STOP` — and nothing else on that line. Standing by after the bind is itself an `ENDORSE` of the charter, stated as that first line.
28
+ - Every reply, including this first bind turn, opens its first line with exactly one of the four uppercase signal words: `ENDORSE`, `CORRECTION`, `PLAN`, `STOP`. Nothing else on that line. Standing by after the bind is itself an `ENDORSE` of the charter, stated as that first line.
29
29
 
30
30
  The agent finishes its first turn standing by. `SendMessage` alone resumes it; between consults it waits quietly.
31
31
 
32
32
  ## Codex host
33
33
 
34
- Spawn a native in-session Sol subagent at `resolve_codex_model_id("Sol")` (`gpt-5.6-sol`) with the charter as its prompt.
35
- Record `{tier: "Sol", result: "spawned"}` on success. Fail closed when that spawn does not bind.
36
- The `ADVISOR_SOL` flag is not required. Do not walk Fable. Consults stay in-session with that Sol subagent.
34
+ Spawn a native in-session Astra subagent at `resolve_codex_model_id("Astra")` (`gpt-6-astra`) with the charter as its prompt.
35
+ Record `{tier: "Astra", result: "spawned"}` on success. Fail closed when that spawn does not bind.
36
+ The `ADVISOR_ASTRA` flag is not required. Do not walk Fable. Consults stay in-session with that Astra subagent.
37
37
  Identity routing: [`identity.md`](identity.md).
38
38
 
39
39
  ## Third-party host
40
40
 
41
- Bind per [`third-party-bind.md`](third-party-bind.md) and charter the CLI session with the same charter — the reply contract is the same, and consults travel through the CLI runner.
41
+ Bind per [`third-party-bind.md`](third-party-bind.md) and charter the CLI session with the same charter. The reply contract is the same, and consults travel through the CLI runner.
@@ -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:]))