claude-dev-env 2.36.0 → 4.1.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 (84) hide show
  1. package/.agents/skills/_shared/advisor/scripts/README.md +1 -1
  2. package/.agents/skills/syncing-submodules/SKILL.md +101 -0
  3. package/.agents/skills/syncing-submodules/reference/run-tasks.md +9 -0
  4. package/.agents/skills/syncing-submodules/scripts/submodule_sync.py +383 -0
  5. package/.agents/skills/syncing-submodules/scripts/submodule_sync_constants/__init__.py +1 -0
  6. package/.agents/skills/syncing-submodules/scripts/submodule_sync_constants/config/__init__.py +1 -0
  7. package/.agents/skills/syncing-submodules/scripts/submodule_sync_constants/config/constants.py +66 -0
  8. package/.agents/skills/syncing-submodules/scripts/sync_parent_pointer.py +88 -0
  9. package/.agents/skills/syncing-submodules/scripts/test_submodule_sync.py +303 -0
  10. package/.agents/skills/syncing-submodules/scripts/test_sync_parent_pointer.py +83 -0
  11. package/.agents/skills/team-advisor/SKILL.md +11 -11
  12. package/_shared/advisor/advisor-protocol.md +51 -51
  13. package/_shared/advisor/reference/advisor-block.md +9 -9
  14. package/_shared/advisor/reference/astra-rung.md +39 -0
  15. package/_shared/advisor/reference/identity.md +4 -4
  16. package/_shared/advisor/reference/lifecycle.md +6 -6
  17. package/_shared/advisor/reference/spawn-walk-log.md +9 -9
  18. package/_shared/advisor/reference/third-party-bind.md +11 -11
  19. package/_shared/advisor/reference/warm-up.md +13 -13
  20. package/_shared/advisor/scripts/codex_astra_advisor.py +308 -0
  21. package/_shared/advisor/scripts/codex_astra_preflight.py +115 -0
  22. package/_shared/advisor/scripts/codex_astra_reply.py +159 -0
  23. package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +3 -3
  24. package/_shared/advisor/scripts/config/advisor_scripts_constants/astra_advisor_constants.py +38 -0
  25. package/_shared/advisor/scripts/model_tier_run_validator.py +17 -18
  26. package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +271 -0
  27. package/_shared/advisor/scripts/tests/test_codex_astra_preflight.py +24 -0
  28. package/_shared/advisor/scripts/tests/test_codex_astra_reply.py +42 -0
  29. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +55 -30
  30. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +14 -4
  31. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +14 -0
  32. package/_shared/pr-loop/scripts/terminology_sweep.py +138 -50
  33. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +13 -1
  34. package/bin/ever-shipped-skills.mjs +1 -0
  35. package/bin/ever-shipped-skills.test.mjs +30 -0
  36. package/bin/git_hooks_installer.mjs +0 -1
  37. package/bin/git_hooks_installer.test.mjs +16 -1
  38. package/bin/install.mjs +5 -2
  39. package/bin/install.prune.test.mjs +36 -24
  40. package/bin/install.test.mjs +7 -0
  41. package/docs/references/team-advisor-skill.md +3 -3
  42. package/hooks/validators/README.md +35 -1
  43. package/package.json +1 -1
  44. package/scripts/check.ps1 +18 -6
  45. package/scripts/policy_lint/adapter_detectors.py +42 -2
  46. package/scripts/policy_lint/adapters.py +24 -0
  47. package/scripts/policy_lint/config/constants.py +11 -0
  48. package/scripts/policy_lint/engine.py +1 -1
  49. package/scripts/policy_lint/model.py +1 -0
  50. package/scripts/policy_lint/registry.py +10 -1
  51. package/scripts/repository_checks/__init__.py +1 -0
  52. package/scripts/repository_checks/claude_md.py +108 -0
  53. package/scripts/repository_checks/config/__init__.py +1 -0
  54. package/scripts/repository_checks/config/constants.py +150 -0
  55. package/scripts/repository_checks/env_var_documentation.py +243 -0
  56. package/scripts/repository_checks/hook_modules.py +34 -0
  57. package/scripts/repository_checks/models.py +37 -0
  58. package/scripts/repository_checks/package_inventory.py +118 -0
  59. package/scripts/repository_checks/pytest_testpaths.py +84 -0
  60. package/scripts/repository_checks/reporting.py +39 -0
  61. package/scripts/repository_checks/runner.py +93 -0
  62. package/scripts/repository_checks/tracked_secrets.py +147 -0
  63. package/scripts/repository_checks/tracked_tree.py +28 -0
  64. package/scripts/repository_policy.py +120 -0
  65. package/scripts/tests/repository_policy_test_support.py +89 -0
  66. package/scripts/tests/test_cde_lint.py +25 -1
  67. package/scripts/tests/test_claude_md.py +68 -0
  68. package/scripts/tests/test_env_var_documentation.py +133 -0
  69. package/scripts/tests/test_hook_modules.py +17 -0
  70. package/scripts/tests/test_models.py +26 -0
  71. package/scripts/tests/test_package_inventory.py +96 -0
  72. package/scripts/tests/test_policy_lint_rules_registry.py +117 -0
  73. package/scripts/tests/test_pytest_testpaths.py +66 -0
  74. package/scripts/tests/test_reporting.py +54 -0
  75. package/scripts/tests/test_repository_policy.py +71 -0
  76. package/scripts/tests/test_runner.py +59 -0
  77. package/scripts/tests/test_tracked_secrets.py +175 -0
  78. package/scripts/tests/test_tracked_tree.py +28 -0
  79. package/_shared/advisor/reference/sol-rung.md +0 -39
  80. package/_shared/advisor/scripts/codex_sol_advisor.py +0 -581
  81. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +0 -38
  82. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +0 -740
  83. package/hooks/git-hooks/post_commit.py +0 -241
  84. package/hooks/git-hooks/test_post_commit.py +0 -298
@@ -0,0 +1,303 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ SCRIPT_DIRECTORY = Path(__file__).resolve().parent
11
+ if str(SCRIPT_DIRECTORY) not in sys.path:
12
+ sys.path.insert(0, str(SCRIPT_DIRECTORY))
13
+
14
+ import submodule_sync
15
+ from submodule_sync_constants.config.constants import (
16
+ GH_COMMAND_TIMEOUT_SECONDS,
17
+ GIT_COMMAND_TIMEOUT_SECONDS,
18
+ )
19
+
20
+
21
+ def _build_git_environment() -> dict[str, str]:
22
+ return {
23
+ each_name: each_content
24
+ for each_name, each_content in os.environ.items()
25
+ if not each_name.upper().startswith("GIT_")
26
+ }
27
+
28
+
29
+ def _run_fixture_git(
30
+ repository_path: Path,
31
+ *all_arguments: str,
32
+ ) -> subprocess.CompletedProcess[str]:
33
+ return subprocess.run(
34
+ ["git", *all_arguments],
35
+ cwd=repository_path,
36
+ check=False,
37
+ capture_output=True,
38
+ text=True,
39
+ env=_build_git_environment(),
40
+ timeout=GIT_COMMAND_TIMEOUT_SECONDS,
41
+ )
42
+
43
+
44
+ def _require_fixture_git(repository_path: Path, *all_arguments: str) -> str:
45
+ completed_process = _run_fixture_git(repository_path, *all_arguments)
46
+ assert completed_process.returncode == 0, completed_process.stderr
47
+ return completed_process.stdout.strip()
48
+
49
+
50
+ def _configure_fixture_identity(repository_path: Path) -> None:
51
+ _require_fixture_git(repository_path, "config", "user.name", "Fixture User")
52
+ _require_fixture_git(
53
+ repository_path,
54
+ "config",
55
+ "user.email",
56
+ "fixture@example.invalid",
57
+ )
58
+ _require_fixture_git(repository_path, "config", "commit.gpgsign", "false")
59
+
60
+
61
+ def _initialize_fixture_repository(repository_path: Path) -> None:
62
+ repository_path.mkdir(parents=True)
63
+ _require_fixture_git(repository_path, "init", "-b", "main")
64
+ _configure_fixture_identity(repository_path)
65
+
66
+
67
+ def _create_submodule_fixture(tmp_path: Path) -> tuple[Path, Path]:
68
+ source_repository = tmp_path / "source-repository"
69
+ parent_repository = tmp_path / "parent-repository"
70
+ _initialize_fixture_repository(source_repository)
71
+ (source_repository / "README.md").write_text("source\n", encoding="utf-8")
72
+ _require_fixture_git(source_repository, "add", "README.md")
73
+ _require_fixture_git(source_repository, "commit", "-m", "Create source")
74
+ _initialize_fixture_repository(parent_repository)
75
+ (parent_repository / "README.md").write_text("parent\n", encoding="utf-8")
76
+ _require_fixture_git(parent_repository, "add", "README.md")
77
+ _require_fixture_git(parent_repository, "commit", "-m", "Create parent")
78
+ _require_fixture_git(
79
+ parent_repository,
80
+ "-c",
81
+ "protocol.file.allow=always",
82
+ "submodule",
83
+ "add",
84
+ str(source_repository),
85
+ "nested/sub[module]",
86
+ )
87
+ _require_fixture_git(parent_repository, "commit", "-am", "Add submodule")
88
+ submodule_repository = parent_repository / "nested" / "sub[module]"
89
+ _configure_fixture_identity(submodule_repository)
90
+ return parent_repository, submodule_repository
91
+
92
+
93
+ def _commit_child_change(submodule_repository: Path) -> str:
94
+ (submodule_repository / "change.txt").write_text("change\n", encoding="utf-8")
95
+ _require_fixture_git(submodule_repository, "add", "change.txt")
96
+ _require_fixture_git(submodule_repository, "commit", "-m", "Record child change")
97
+ return _require_fixture_git(submodule_repository, "rev-parse", "HEAD")
98
+
99
+
100
+ def _assert_parent_pointer_commit(
101
+ parent_repository: Path,
102
+ child_commit_hash: str,
103
+ ) -> None:
104
+ recorded_pointer = _require_fixture_git(
105
+ parent_repository,
106
+ "rev-parse",
107
+ "HEAD:nested/sub[module]",
108
+ )
109
+ changed_path = _require_fixture_git(
110
+ parent_repository,
111
+ "diff",
112
+ "HEAD^",
113
+ "HEAD",
114
+ "--name-only",
115
+ )
116
+ parent_commit_body = _require_fixture_git(
117
+ parent_repository,
118
+ "log",
119
+ "-1",
120
+ "--pretty=%B",
121
+ )
122
+ assert recorded_pointer == child_commit_hash
123
+ assert changed_path == "nested/sub[module]"
124
+ assert "Submodule commit: Record child change" in parent_commit_body
125
+ assert "Co-Authored-By" not in parent_commit_body
126
+
127
+
128
+ def test_sync_updates_bracketed_pointer_and_preserves_staged_path(
129
+ tmp_path: Path,
130
+ ) -> None:
131
+ parent_repository, submodule_repository = _create_submodule_fixture(tmp_path)
132
+ staged_note_path = parent_repository / "staged-note.txt"
133
+ staged_note_path.write_text("keep staged\n", encoding="utf-8")
134
+ _require_fixture_git(parent_repository, "add", "staged-note.txt")
135
+ child_commit_hash = _commit_child_change(submodule_repository)
136
+
137
+ sync_report = submodule_sync.sync_repository(submodule_repository)
138
+
139
+ assert sync_report.status is submodule_sync.SyncStatus.UPDATED
140
+ assert sync_report.commit == child_commit_hash
141
+ assert sync_report.submodule_path == "nested/sub[module]"
142
+ assert (
143
+ _require_fixture_git(parent_repository, "status", "--short")
144
+ == "A staged-note.txt"
145
+ )
146
+ _assert_parent_pointer_commit(parent_repository, child_commit_hash)
147
+
148
+
149
+ def test_second_sync_is_unchanged_without_new_commit(tmp_path: Path) -> None:
150
+ parent_repository, submodule_repository = _create_submodule_fixture(tmp_path)
151
+ _commit_child_change(submodule_repository)
152
+
153
+ first_report = submodule_sync.sync_repository(submodule_repository)
154
+ parent_commit = _require_fixture_git(parent_repository, "rev-parse", "HEAD")
155
+ second_report = submodule_sync.sync_repository(submodule_repository)
156
+
157
+ assert first_report.status is submodule_sync.SyncStatus.UPDATED
158
+ assert second_report.status is submodule_sync.SyncStatus.UNCHANGED
159
+ assert second_report.parent_commit == parent_commit
160
+ assert _require_fixture_git(parent_repository, "rev-parse", "HEAD") == parent_commit
161
+
162
+
163
+ def test_standalone_repository_is_a_noop(tmp_path: Path) -> None:
164
+ standalone_repository = tmp_path / "standalone"
165
+ _initialize_fixture_repository(standalone_repository)
166
+ (standalone_repository / "note.txt").write_text("unchanged\n", encoding="utf-8")
167
+ _require_fixture_git(standalone_repository, "add", "note.txt")
168
+ before_status = _require_fixture_git(standalone_repository, "status", "--short")
169
+
170
+ sync_report = submodule_sync.sync_repository(standalone_repository)
171
+
172
+ assert sync_report.status is submodule_sync.SyncStatus.NOT_SUBMODULE
173
+ assert sync_report.repository == standalone_repository.resolve().as_posix()
174
+ assert (
175
+ _require_fixture_git(standalone_repository, "status", "--short")
176
+ == before_status
177
+ )
178
+
179
+
180
+ @pytest.mark.parametrize(
181
+ ("failed_git_operation", "failure_exit_code"),
182
+ [("add", 1), ("diff", 2), ("commit", 1)],
183
+ )
184
+ def test_git_failures_return_error_status(
185
+ tmp_path: Path,
186
+ monkeypatch: pytest.MonkeyPatch,
187
+ failed_git_operation: str,
188
+ failure_exit_code: int,
189
+ ) -> None:
190
+ _, submodule_repository = _create_submodule_fixture(tmp_path)
191
+ _commit_child_change(submodule_repository)
192
+ original_execute_git = submodule_sync._execute_git
193
+
194
+ def fail_selected_operation(
195
+ all_arguments: tuple[str, ...],
196
+ cwd: Path,
197
+ ) -> subprocess.CompletedProcess[str]:
198
+ if all_arguments[0] == failed_git_operation:
199
+ return subprocess.CompletedProcess(
200
+ ["git", *all_arguments],
201
+ failure_exit_code,
202
+ "",
203
+ f"fatal: fixture {failed_git_operation} failure",
204
+ )
205
+ return original_execute_git(all_arguments, cwd)
206
+
207
+ monkeypatch.setattr(submodule_sync, "_execute_git", fail_selected_operation)
208
+ sync_report = submodule_sync.sync_repository(submodule_repository)
209
+
210
+ assert sync_report.status is submodule_sync.SyncStatus.ERROR
211
+ assert sync_report.diagnostic == f"fatal: fixture {failed_git_operation} failure"
212
+
213
+
214
+ def test_inherited_git_dir_does_not_redirect_commands(
215
+ tmp_path: Path,
216
+ monkeypatch: pytest.MonkeyPatch,
217
+ ) -> None:
218
+ unrelated_repository = tmp_path / "unrelated"
219
+ _initialize_fixture_repository(unrelated_repository)
220
+ parent_repository, submodule_repository = _create_submodule_fixture(tmp_path)
221
+ child_commit_hash = _commit_child_change(submodule_repository)
222
+ monkeypatch.setenv("GIT_DIR", str(unrelated_repository / ".git"))
223
+
224
+ sync_report = submodule_sync.sync_repository(submodule_repository)
225
+
226
+ assert sync_report.status is submodule_sync.SyncStatus.UPDATED
227
+ recorded_pointer = _require_fixture_git(
228
+ parent_repository,
229
+ "rev-parse",
230
+ "HEAD:nested/sub[module]",
231
+ )
232
+ assert recorded_pointer == child_commit_hash
233
+
234
+
235
+ def test_pull_request_lookup_returns_url_on_success(
236
+ tmp_path: Path,
237
+ monkeypatch: pytest.MonkeyPatch,
238
+ ) -> None:
239
+ completed_process = subprocess.CompletedProcess(
240
+ ["gh"],
241
+ 0,
242
+ "https://github.com/example/repo/pull/1\n",
243
+ "",
244
+ )
245
+
246
+ def return_completed_process(
247
+ *all_arguments: object,
248
+ **all_keyword_arguments: object,
249
+ ) -> subprocess.CompletedProcess[str]:
250
+ return completed_process
251
+
252
+ monkeypatch.setattr(submodule_sync.subprocess, "run", return_completed_process)
253
+
254
+ assert submodule_sync.lookup_pull_request_url(tmp_path) == (
255
+ "https://github.com/example/repo/pull/1"
256
+ )
257
+
258
+
259
+ @pytest.mark.parametrize(
260
+ "completed_process",
261
+ [
262
+ subprocess.CompletedProcess(["gh"], 1, "", "no pull request"),
263
+ subprocess.CompletedProcess(["gh"], 1, "", "auth failed"),
264
+ subprocess.CompletedProcess(["gh"], 0, "", ""),
265
+ ],
266
+ )
267
+ def test_pull_request_lookup_soft_failures_return_none(
268
+ tmp_path: Path,
269
+ monkeypatch: pytest.MonkeyPatch,
270
+ completed_process: subprocess.CompletedProcess[str],
271
+ ) -> None:
272
+ def return_completed_process(
273
+ *all_arguments: object,
274
+ **all_keyword_arguments: object,
275
+ ) -> subprocess.CompletedProcess[str]:
276
+ return completed_process
277
+
278
+ monkeypatch.setattr(submodule_sync.subprocess, "run", return_completed_process)
279
+
280
+ assert submodule_sync.lookup_pull_request_url(tmp_path) is None
281
+
282
+
283
+ @pytest.mark.parametrize(
284
+ "lookup_exception",
285
+ [
286
+ FileNotFoundError(),
287
+ subprocess.TimeoutExpired("gh", GH_COMMAND_TIMEOUT_SECONDS),
288
+ ],
289
+ )
290
+ def test_pull_request_lookup_unavailable_returns_none(
291
+ tmp_path: Path,
292
+ monkeypatch: pytest.MonkeyPatch,
293
+ lookup_exception: Exception,
294
+ ) -> None:
295
+ def raise_lookup_exception(
296
+ *all_arguments: object,
297
+ **all_keyword_arguments: object,
298
+ ) -> None:
299
+ raise lookup_exception
300
+
301
+ monkeypatch.setattr(submodule_sync.subprocess, "run", raise_lookup_exception)
302
+
303
+ assert submodule_sync.lookup_pull_request_url(tmp_path) is None
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ SCRIPT_DIRECTORY = Path(__file__).resolve().parent
10
+ if str(SCRIPT_DIRECTORY) not in sys.path:
11
+ sys.path.insert(0, str(SCRIPT_DIRECTORY))
12
+
13
+ import sync_parent_pointer
14
+ from submodule_sync import SyncReport, SyncStatus
15
+
16
+
17
+ def _read_single_json_line(
18
+ capsys: pytest.CaptureFixture[str],
19
+ ) -> tuple[dict[str, object], str]:
20
+ captured_streams = capsys.readouterr()
21
+ all_stdout_lines = captured_streams.out.splitlines()
22
+ assert len(all_stdout_lines) == 1
23
+ parsed_record = json.loads(all_stdout_lines[0])
24
+ assert isinstance(parsed_record, dict)
25
+ return parsed_record, captured_streams.err
26
+
27
+
28
+ def test_invalid_repository_emits_one_json_record_and_diagnostic(
29
+ tmp_path: Path,
30
+ capsys: pytest.CaptureFixture[str],
31
+ ) -> None:
32
+ exit_code = sync_parent_pointer.main(["--repository", str(tmp_path / "missing")])
33
+
34
+ sync_record, captured_error = _read_single_json_line(capsys)
35
+ assert exit_code == 1
36
+ assert sync_record["status"] == "error"
37
+ assert sync_record["diagnostic"]
38
+ assert "syncing-submodules:" in captured_error
39
+
40
+
41
+ def test_invalid_arguments_emit_one_json_record_and_exit_two(
42
+ capsys: pytest.CaptureFixture[str],
43
+ ) -> None:
44
+ exit_code = sync_parent_pointer.main(["--unknown"])
45
+
46
+ sync_record, captured_error = _read_single_json_line(capsys)
47
+ assert exit_code == 2
48
+ assert sync_record["status"] == "error"
49
+ assert "invalid command arguments" in str(sync_record["diagnostic"])
50
+ assert captured_error
51
+
52
+
53
+ def test_success_emits_one_json_record(
54
+ tmp_path: Path,
55
+ monkeypatch: pytest.MonkeyPatch,
56
+ capsys: pytest.CaptureFixture[str],
57
+ ) -> None:
58
+ expected_report = SyncReport(
59
+ status=SyncStatus.NOT_SUBMODULE,
60
+ repository=tmp_path.as_posix(),
61
+ )
62
+
63
+ def return_expected_report(repository: Path) -> SyncReport:
64
+ return expected_report
65
+
66
+ monkeypatch.setattr(
67
+ sync_parent_pointer,
68
+ "sync_repository",
69
+ return_expected_report,
70
+ )
71
+ exit_code = sync_parent_pointer.main(["--repository", str(tmp_path)])
72
+
73
+ sync_record, captured_error = _read_single_json_line(capsys)
74
+ assert exit_code == 0
75
+ assert sync_record["status"] == "not_submodule"
76
+ assert captured_error == ""
77
+
78
+
79
+ def test_native_post_commit_source_and_test_are_removed() -> None:
80
+ hook_directory = Path(__file__).resolve().parents[4] / "hooks" / "git-hooks"
81
+
82
+ assert not (hook_directory / "post_commit.py").exists()
83
+ assert not (hook_directory / "test_post_commit.py").exists()
@@ -16,23 +16,23 @@ One warm advisor at the strongest tier this session can reach. This session is t
16
16
 
17
17
  | Doc | Holds |
18
18
  |---|---|
19
- | [`docs/references/advisor-tool.md`](../../docs/references/advisor-tool.md) | **Consult cadence and weight** — when to call, hard rule before first write, how to treat advice. Read this for every consult. |
20
- | [`~/.claude/_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md) | **Bind and lifecycle** — session identity, host detect, model floor, warm-up, CLI fallback; its read map routes each moment to a `reference/` detail file. |
21
- | [`agents/session-advisor.md`](../../agents/session-advisor.md) | **Reply contract** — ENDORSE / CORRECTION / PLAN / STOP; SendMessage only. |
22
- | [`reference/advisor-docs-review.md`](reference/advisor-docs-review.md) | Anthropic advisor-tool source facts: measured effects, Sonnet steering, cost levers, failure modes. Background — read it when tuning the bind, not on every consult. |
19
+ | [`docs/references/advisor-tool.md`](../../docs/references/advisor-tool.md) | **Consult cadence and weight.** When to call, the hard rule before first write, and how to treat advice. Read this for every consult. |
20
+ | [`~/.claude/_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md) | **Bind and lifecycle.** Session identity, host detect, model floor, warm-up, CLI fallback. Its read map routes each moment to a `reference/` detail file. |
21
+ | [`agents/session-advisor.md`](../../agents/session-advisor.md) | **Reply contract.** ENDORSE / CORRECTION / PLAN / STOP. SendMessage only. |
22
+ | [`reference/advisor-docs-review.md`](reference/advisor-docs-review.md) | Anthropic advisor-tool source facts: measured effects, Sonnet steering, cost levers, failure modes. Background. Read it when tuning the bind, not on every consult. |
23
23
 
24
24
  ## Bind
25
25
 
26
26
  1. Name the session identity first (protocol **Host profiles**), then walk the model floor.
27
- 2. Claude: Fable first in-session at `ADVISOR_EFFORT` (default low). When Fable is out of usage, bind Sol at the same effort. Codex: Sol in-session. Third-party: headless Fable then Sol. When the host's walk fails, fail closed.
28
- 3. Name: `team-advisor-agent` on Claude (Agent spawn of `session-advisor`); a native Sol subagent on Codex; one CLI `session_id` on a third-party host via the protocol Claude-chain.
27
+ 2. Claude: Fable first in-session at `ADVISOR_EFFORT` (default low). When Fable is out of usage, bind Astra at the same effort. Codex: Astra in-session. Third-party: headless Fable then Astra. When the host's walk fails, fail closed.
28
+ 3. Name: `team-advisor-agent` on Claude (Agent spawn of `session-advisor`); a native Astra subagent on Codex; one CLI `session_id` on a third-party host via the protocol Claude-chain or Astra helper.
29
29
  4. A Fable-tier spawn or re-spawn carries the exact token `FABLE-SPAWN-AUTHORIZED` in its prompt (protocol warm-up; `fable_spawn_gate` requires it).
30
- 5. Skip the multi-consumer "who you are" opener — sole consumer.
31
- 6. When the bind or reply path fails, fail closed and report to the user. On a third-party host, only the bound Claude advisor issues ENDORSE / CORRECTION / PLAN / STOP.
30
+ 5. Skip the multi-consumer "who you are" opener. This session is the sole consumer.
31
+ 6. When the bind or reply path fails, fail closed and report to the user. On a third-party host, only the bound advisor issues ENDORSE / CORRECTION / PLAN / STOP.
32
32
 
33
- **GOTCHA (Cursor / ThirdParty + Sol):** when the walk reaches Sol or the user asks for Sol, first tool call is `python ~/.claude/_shared/advisor/scripts/codex_sol_advisor.py --bind --enable-sol --cwd <repo-root>` with the charter on stdin — not Agent/Task, not a probe-path search. Details: [`third-party-bind.md`](../../_shared/advisor/reference/third-party-bind.md) GOTCHA and [`sol-rung.md`](../../_shared/advisor/reference/sol-rung.md).
33
+ **GOTCHA (Cursor / ThirdParty + Astra):** when the walk reaches Astra or the user asks for Astra, first tool call is `python ~/.claude/_shared/advisor/scripts/codex_astra_advisor.py --bind --enable-astra --cwd <repo-root>` with the charter on stdin. Do not use Agent or Task. Do not search for a probe path. Details: [`third-party-bind.md`](../../_shared/advisor/reference/third-party-bind.md) GOTCHA and [`astra-rung.md`](../../_shared/advisor/reference/astra-rung.md).
34
34
 
35
- Full walk, charter, consult packet, Sol routing, and drift re-bind live in the protocol read map and its authoritative `reference/` leaves.
35
+ Full walk, charter, consult packet, Astra routing, and drift re-bind live in the protocol read map and its `reference/` files.
36
36
 
37
37
  ## Consult
38
38
 
@@ -44,6 +44,6 @@ Aim for two consults on a normal task: one after orientation and one after write
44
44
 
45
45
  ## Constraints
46
46
 
47
- - One bind per session; this session owns spawn, in-session Sol spawn, or CLI bind, drift re-bind, and shutdown.
47
+ - One bind per session; this session owns spawn, in-session Astra spawn, or CLI bind, drift re-bind, and shutdown.
48
48
  - Bind at or above the protocol floor for this host.
49
49
  - The advisor only answers (messaging); the session runs tools and posts.
@@ -13,20 +13,20 @@ On Claude, continue with **Model floor** and the rest of this document.
13
13
 
14
14
  ## Read map
15
15
 
16
- The sections below hold the standing rules; open a reference file at the moment its row names.
16
+ The sections below hold the standing rules. Open a reference file at the moment its row names.
17
17
 
18
18
  | Moment | Open |
19
19
  |---|---|
20
- | Naming the session identity | [`reference/identity.md`](reference/identity.md) — Claude, Codex, or neither |
21
- | Binding on a Claude host | [`reference/warm-up.md`](reference/warm-up.md) — spawn fields, Fable token, charter |
22
- | Binding on a Codex host | [`reference/identity.md`](reference/identity.md) — in-session Sol spawn |
23
- | Binding from a third-party host | [`reference/third-party-bind.md`](reference/third-party-bind.md) — CLI bind steps, fail-closed rule |
24
- | Fable is out of usage | [`reference/sol-rung.md`](reference/sol-rung.md) — Sol fallback at shared effort |
25
- | Composing a consult | [`reference/consult-format.md`](reference/consult-format.md) — packet, new-evidence and report-back rules |
26
- | Assembling an executor spawn prompt | [`reference/advisor-block.md`](reference/advisor-block.md) — the paste parts |
27
- | Advisor drifts, dies, or the task pivots | [`reference/lifecycle.md`](reference/lifecycle.md) — re-spawn and re-bind steps |
28
- | Logging or checking a bind walk | [`reference/spawn-walk-log.md`](reference/spawn-walk-log.md) — record shape, validator |
29
- | Any CLI call to a Claude advisor | [`reference/cli-chain.md`](reference/cli-chain.md) — runner modes, alias table, resume |
20
+ | Naming the session identity | [`reference/identity.md`](reference/identity.md): Claude, Codex, or neither |
21
+ | Binding on a Claude host | [`reference/warm-up.md`](reference/warm-up.md): spawn fields, Fable token, charter |
22
+ | Binding on a Codex host | [`reference/identity.md`](reference/identity.md): in-session Astra spawn |
23
+ | Binding from a third-party host | [`reference/third-party-bind.md`](reference/third-party-bind.md): CLI bind steps, fail-closed rule |
24
+ | Fable is out of usage | [`reference/astra-rung.md`](reference/astra-rung.md): Astra fallback at shared effort |
25
+ | Composing a consult | [`reference/consult-format.md`](reference/consult-format.md): packet, new-evidence and report-back rules |
26
+ | Assembling an executor spawn prompt | [`reference/advisor-block.md`](reference/advisor-block.md): the paste parts |
27
+ | Advisor drifts, dies, or the task pivots | [`reference/lifecycle.md`](reference/lifecycle.md): re-spawn and re-bind steps |
28
+ | Logging or checking a bind walk | [`reference/spawn-walk-log.md`](reference/spawn-walk-log.md): record shape, validator |
29
+ | Any CLI call to a Claude advisor | [`reference/cli-chain.md`](reference/cli-chain.md): runner modes, alias table, resume |
30
30
 
31
31
  ## Host profiles
32
32
 
@@ -42,95 +42,95 @@ Session identity:
42
42
  Mechanical override for scripts:
43
43
 
44
44
  1. `ADVISOR_HOST_PROFILE=ThirdParty` or `=Claude` or `=Codex` (explicit override; any letter case).
45
- 2. `THIRD_PARTY=1` (or `true` / `yes` / `on`) — a third-party (non-Claude) harness.
45
+ 2. `THIRD_PARTY=1` (or `true` / `yes` / `on`) selects a third-party (non-Claude) harness.
46
46
  3. Default: Claude.
47
47
 
48
- ### Shared effort — any host
48
+ ### Shared effort, any host
49
49
 
50
- Fable and Sol both read `ADVISOR_EFFORT` (`low`, `medium`, `high`, `xhigh`, `max`). The default is `low`.
51
- Pass `--effort <level>` on the Sol helper to set effort for that Sol run.
50
+ Fable and Astra both read `ADVISOR_EFFORT` (`low`, `medium`, `high`, `xhigh`, `max`). The default is `low`.
51
+ Pass `--effort <level>` on the Astra helper to set effort for that Astra run.
52
52
  Pass `--effort <level>` on the Claude CLI bind for Fable. An unset or unrecognized value uses `low`.
53
53
 
54
- ### Sol rung
54
+ ### Astra rung
55
55
 
56
- On Claude and ThirdParty: when Fable is out of usage, bind Sol through the Codex helper.
57
- Open that attempt with `ADVISOR_SOL=1` (or `true` / `yes` / `on`) in the environment, or pass `--enable-sol` on the helper invocation.
56
+ On Claude and ThirdParty: when Fable is out of usage, bind Astra through the Codex helper.
57
+ Open that attempt with `ADVISOR_ASTRA=1` (or `true` / `yes` / `on`) in the environment, or pass `--enable-astra` on the helper invocation.
58
58
  Flag off both ways: fail closed when Fable did not bind.
59
- Flag on: run the Codex preflight and bind per [`reference/sol-rung.md`](reference/sol-rung.md); a failed preflight fails closed when Fable did not bind.
59
+ Flag on: run the Codex preflight and bind per [`reference/astra-rung.md`](reference/astra-rung.md); a failed preflight fails closed when Fable did not bind.
60
60
 
61
- On Codex: Sol is the in-session default. The `ADVISOR_SOL` flag is not required. Fail closed when that spawn does not bind.
61
+ On Codex: Astra is the in-session default. The `ADVISOR_ASTRA` flag is not required. Fail closed when that spawn does not bind.
62
62
 
63
63
  ### Claude host
64
64
 
65
- Use the **Model floor** ladder below (Fable first, then Sol when Fable is out of usage).
66
- Warm-up spawns `subagent_type: session-advisor` via the Agent tool; consults go through `SendMessage` to that warm agent.
65
+ Use the **Model floor** ladder below (Fable first, then Astra when Fable is out of usage).
66
+ Warm-up spawns `subagent_type: session-advisor` via the Agent tool for Fable; consults go through `SendMessage` to that warm agent. Astra uses the Codex helper.
67
67
  Assemble and paste each executor's Advisor block per the **Advisor block** section.
68
68
 
69
69
  ### Codex host
70
70
 
71
- Spawn a native in-session Sol subagent at `resolve_codex_model_id("Sol")`.
72
- Walk `candidate_tiers = ["Sol"]`. Record `{tier: "Sol", result: "spawned"}` on success.
73
- **Fail closed:** when Sol does not bind, set `selected_tier = null` and a `fallback_reason`, report that the advisor is unreachable, and **stop**.
74
- Do not walk Fable on a Codex host. Consults stay in-session with that Sol subagent.
71
+ Spawn a native in-session Astra subagent at `resolve_codex_model_id("Astra")`.
72
+ Walk `candidate_tiers = ["Astra"]`. Record `{tier: "Astra", result: "spawned"}` on success.
73
+ **Fail closed:** when Astra does not bind, set `selected_tier = null` and a `fallback_reason`, report that the advisor is unreachable, and **stop**.
74
+ Do not walk Fable on a Codex host. Consults stay in-session with that Astra subagent.
75
75
  Assemble and paste each executor's Advisor block per the **Advisor block** section.
76
76
 
77
77
  ### Third-party host
78
78
 
79
79
  On a third-party (non-Claude, non-Codex) harness, the shared CLI Claude-chain is the one path to a Claude advisor: bind Fable through it, per [`reference/third-party-bind.md`](reference/third-party-bind.md).
80
80
  The bound Claude session is the advisor; this third-party session stays the executor.
81
- Walk `candidate_tiers = ["Fable"]`. When Fable is out of usage, the sol rung binds after Fable (`candidate_tiers = ["Fable", "Sol"]`).
82
- **Cursor Sol first shot:** when the walk reaches Sol (or the user asks for Sol), bind through `codex_sol_advisor.py --bind --enable-sol` on the first tool call (see the GOTCHA in [`reference/third-party-bind.md`](reference/third-party-bind.md)) — not the Agent tool, and not a probe-path hunt.
83
- **Fail closed:** when every candidate fails, 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.
81
+ Walk `candidate_tiers = ["Fable"]`. When Fable is out of usage, the Astra rung binds after Fable (`candidate_tiers = ["Fable", "Astra"]`).
82
+ **Cursor Astra first shot:** when the walk reaches Astra (or the user asks for Astra), bind through `codex_astra_advisor.py --bind --enable-astra` on the first tool call (see the GOTCHA in [`reference/third-party-bind.md`](reference/third-party-bind.md)). Do not use the Agent tool, and do not hunt a probe path.
83
+ **Fail closed:** when every candidate fails, 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.
84
84
  Executors report to the orchestrating session; that session consults the bound advisor and relays the four-signal reply.
85
85
 
86
86
  ## Model floor
87
87
 
88
- On Claude and ThirdParty the advisor ladder is `Fable` first, then sol (flag-gated, Codex CLI) when Fable is out of usage.
89
- On Codex the walk is Sol only, in-session.
88
+ On Claude and ThirdParty the advisor ladder is `Fable` first, then Astra (flag-gated, Codex CLI) when Fable is out of usage.
89
+ On Codex the walk is Astra only, in-session.
90
90
  Opus is not an advisor candidate. `Sonnet` and `Haiku` are executor tiers only.
91
91
  Consumer `own_tier` is recorded on the spawn-walk log; it does not add Opus to the advisor walk.
92
92
  Tier names are canonical Title Case; the validator accepts any letter case and normalizes to Title Case.
93
- Try binds top-down. Each try resolves its candidate tier to the short model alias via the tier-to-alias map in [`reference/cli-chain.md`](reference/cli-chain.md).
94
- The advisor is created at `selected_tier` — the first ladder tier that binds.
95
- When Fable fails on a Claude host, try Sol if the flag is on, else fail closed. The CLI chain is the Fable bind path on a third-party host and the Claude-host fallback for Fable; it does not bind Opus as advisor.
96
- On a Codex host a failed Sol spawn fails closed per **Host profiles → Codex host**.
97
- On a third-party host the CLI chain is already the primary path, so a failed Fable (and Sol, when enabled) walk fails closed per **Host profiles → Third-party host**.
93
+ Try binds top-down. Fable resolves to the short model alias in [`reference/cli-chain.md`](reference/cli-chain.md); Astra resolves through `resolve_codex_model_id("Astra")` to `gpt-6-astra`.
94
+ The advisor is created at `selected_tier`, the first ladder tier that binds.
95
+ When Fable fails on a Claude host, try Astra if the flag is on, else fail closed. The CLI chain is the Fable bind path on a third-party host and the Claude-host fallback for Fable; it does not bind Opus as advisor.
96
+ On a Codex host a failed Astra spawn fails closed per **Host profiles → Codex host**.
97
+ On a third-party host the CLI chain is already the primary path, so a failed Fable (and Astra, when enabled) walk fails closed per **Host profiles → Third-party host**.
98
98
 
99
99
  Emit a structured spawn-walk log so the walk can be checked mechanically: [`reference/spawn-walk-log.md`](reference/spawn-walk-log.md).
100
100
  The validator checks ladder shape only; host policy sits on top.
101
101
 
102
- **Equal-tier pairings.** Bind Fable for an independent second pass on irreversible or security-sensitive work. The advisor is Fable or Sol.
102
+ **Equal-tier pairings.** Bind Fable for an independent second pass on irreversible or security-sensitive work. The advisor is Fable or Astra.
103
103
 
104
104
  ## Warm-up (once per session)
105
105
 
106
- On a **Claude host**, walk the candidate tiers top-down, spawning `session-advisor` in the background at each candidate's alias with the charter as its prompt, stopping at the first successful spawn.
107
- A **Fable**-tier try carries the exact token `FABLE-SPAWN-AUTHORIZED` in its prompt — `hooks/blocking/fable_spawn_gate.py` denies a Fable-tier spawn without it.
106
+ On a **Claude host**, spawn `session-advisor` at the Fable alias with the charter as its prompt. When Fable is out of usage and the Astra rung is open, bind Astra through the Codex helper instead.
107
+ A **Fable**-tier try carries the exact token `FABLE-SPAWN-AUTHORIZED` in its prompt. `hooks/blocking/fable_spawn_gate.py` denies a Fable-tier spawn without it.
108
108
  Full spawn fields and the charter template: [`reference/warm-up.md`](reference/warm-up.md).
109
109
 
110
- On a **Codex host**, spawn a native in-session Sol subagent with the same charter. Bind fields: [`reference/identity.md`](reference/identity.md) and [`reference/warm-up.md`](reference/warm-up.md).
110
+ On a **Codex host**, spawn a native in-session Astra subagent with the same charter. Bind fields: [`reference/identity.md`](reference/identity.md) and [`reference/warm-up.md`](reference/warm-up.md).
111
111
 
112
- On a **third-party host**, bind per [`reference/third-party-bind.md`](reference/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.
112
+ On a **third-party host**, bind per [`reference/third-party-bind.md`](reference/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.
113
113
 
114
114
  ## Consulting the warm agent
115
115
 
116
- Send a consult at the trigger points `docs/references/advisor-tool.md` **When to call** defines — plan lock-in, believed completion, hard-to-reverse actions, repeated failure or stalled progress, and reconsidered approach.
116
+ Send a consult at the trigger points `docs/references/advisor-tool.md` **When to call** defines: plan lock-in, believed completion, hard-to-reverse actions, repeated failure or stalled progress, and reconsidered approach.
117
117
  The paste parts in [`reference/advisor-block.md`](reference/advisor-block.md) restate them for executors.
118
118
 
119
- Each consult carries the sender's identity and assignment, the delta since the last consult, the live decision or blocker, and the paths or excerpts needed to answer well — full packet shape plus the new-evidence and report-back rules: [`reference/consult-format.md`](reference/consult-format.md).
119
+ Each consult carries the sender's identity and assignment, the delta since the last consult, the live decision or blocker, and the paths or excerpts needed to answer well. Full packet shape plus the new-evidence and report-back rules: [`reference/consult-format.md`](reference/consult-format.md).
120
120
  Consult briefs embed the `docs/references/advisor-tool.md` **Brevity cue** line, sized per that section.
121
121
 
122
- Treat the reply as a serious second opinion: a CORRECTION — whether it names a wrong step or a risk worth closing — is something to address before treating the plan or the work as done.
122
+ A CORRECTION names a wrong step or a risk worth closing. Address it before treating the plan or the work as done.
123
123
  Route a STOP, or an unreachable advisor, upward per [`reference/consult-format.md`](reference/consult-format.md).
124
124
 
125
- ## Advisor block — assemble and paste into every executor spawn prompt
125
+ ## Advisor block
126
126
 
127
- Assemble each executor's block from the parts in [`reference/advisor-block.md`](reference/advisor-block.md), in 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.
127
+ Assemble each executor's block from the parts in [`reference/advisor-block.md`](reference/advisor-block.md), in 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.
128
128
  Paste the assembled block at the **top** of the spawn prompt, ahead of any other sentence that mentions the advisor.
129
- The assembled block is self-contained — the executor receives this text alone.
129
+ The assembled block is self-contained. The executor receives this text alone.
130
130
 
131
131
  ## Lifecycle ownership
132
132
 
133
- The session that binds the advisor owns its whole lifecycle — first bind, drift re-spawn or re-bind, and shutdown; every other consumer reaches the advisor by message alone.
133
+ The session that binds the advisor owns its whole lifecycle: first bind, drift re-spawn or re-bind, and shutdown. Every other consumer reaches the advisor by message alone.
134
134
  Drift signals and the per-host re-spawn / re-bind steps: [`reference/lifecycle.md`](reference/lifecycle.md).
135
135
 
136
136
  ## CLI chain
@@ -140,14 +140,14 @@ Modes and failover, the tier-to-alias table, brief piping, and `--resume` sessio
140
140
 
141
141
  **Third-party host:** the primary bind and consult path; the walk order and fail-closed rule live in [`reference/third-party-bind.md`](reference/third-party-bind.md).
142
142
 
143
- **Codex host:** do not use this runner as the primary path. Sol binds in-session.
143
+ **Codex host:** do not use this runner as the primary path. Astra binds in-session.
144
144
 
145
145
  **Claude host:** fall back to this runner exactly when one of these holds:
146
- - The Agent-tool spawn errors at every candidate tier down to the floor — the tool itself is unavailable.
146
+ - The Agent-tool spawn errors at every candidate tier down to the floor. The tool itself is unavailable.
147
147
  - `SendMessage` to the shared advisor errors, or draws no reply within the bound in `ADVISOR_SENDMESSAGE_REPLY_WAIT_SECONDS` (120) in `$HOME/.claude/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py`, and a re-spawn also fails.
148
148
  - The running session is itself a subagent barred from spawning further agents.
149
149
 
150
150
  ## State the mechanism
151
151
 
152
- One warm agent, addressed by name, whose transcript accumulates across consults — each consult sends only the delta since the last one.
152
+ One warm agent, addressed by name, whose transcript accumulates across consults. Each consult sends only the delta since the last one.
153
153
  A consuming skill's own text states this mechanism; a token or cost saving becomes a claim only after a measured comparison against cold spawns.