claude-dev-env 2.7.1 → 2.8.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.
- package/_shared/CLAUDE.md +1 -0
- package/_shared/advisor/advisor-protocol.md +19 -9
- package/_shared/pr-loop/audit-contract.md +4 -4
- package/_shared/pr-loop/precatch-rubric.md +2 -2
- package/_shared/process-tree/CLAUDE.md +41 -0
- package/_shared/process-tree/scripts/config/process_tree_scripts_constants/__init__.py +1 -0
- package/_shared/process-tree/scripts/config/process_tree_scripts_constants/process_tree_kill_constants.py +27 -0
- package/_shared/process-tree/scripts/process_tree_kill.py +141 -0
- package/_shared/process-tree/scripts/pyproject.toml +16 -0
- package/_shared/process-tree/scripts/test_process_tree_kill.py +278 -0
- package/agents/code-quality-agent.md +6 -5
- package/agents/deep-research.md +7 -24
- package/agents/docs-agent.md +1 -27
- package/agents/issue-tracker.md +1 -7
- package/agents/skill-writer-agent.md +1 -2
- package/agents/test_agent_frontmatter.py +309 -12
- package/hooks/blocking/CLAUDE.md +2 -0
- package/hooks/blocking/fable_spawn_gate.py +187 -0
- package/hooks/blocking/piped_pytest_blocker.py +1223 -0
- package/hooks/blocking/plain_language_blocker.py +287 -15
- package/hooks/blocking/test_fable_spawn_gate.py +374 -0
- package/hooks/blocking/test_piped_pytest_blocker.py +587 -0
- package/hooks/blocking/test_plain_language_blocker.py +277 -2
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +34 -2
- package/hooks/git-hooks/CLAUDE.md +2 -2
- package/hooks/git-hooks/git_hooks_constants/__init__.py +28 -0
- package/hooks/git-hooks/pre_push.py +343 -54
- package/hooks/git-hooks/test_pre_push.py +852 -6
- package/hooks/hooks.json +9 -19
- package/hooks/hooks_constants/CLAUDE.md +2 -0
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/fable_spawn_gate_constants.py +62 -0
- package/hooks/hooks_constants/piped_pytest_blocker_constants.py +360 -0
- package/hooks/hooks_constants/plain_language_blocker_constants.py +64 -1
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -0
- package/hooks/hooks_constants/shell_command_segments.py +1 -1
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +27 -0
- package/hooks/hooks_constants/test_prose_metrics_parity.py +124 -0
- package/package.json +1 -1
- package/rules/CLAUDE.md +1 -0
- package/rules/ask-user-question-required.md +26 -0
- package/rules/claims-as-quotes.md +65 -0
- package/scripts/CLAUDE.md +1 -1
- package/scripts/_code_review_test_support.py +6 -0
- package/scripts/check.ps1 +18 -5
- package/scripts/claude_chain_runner.py +203 -31
- package/scripts/codec_forwarding_test_support.py +2 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -4
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +38 -0
- package/scripts/dev_env_scripts_constants/code_review_constants.py +403 -2
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +28 -24
- package/scripts/grok_headless_runner.py +16 -83
- package/scripts/spawn_grok_batch.py +38 -1
- package/scripts/test_claude_chain_runner.py +358 -0
- package/scripts/test_grok_headless_runner.py +18 -63
- package/scripts/test_invoke_code_review.py +298 -0
- package/scripts/test_resolve_worker_spawn.py +6 -0
- package/scripts/test_spawn_grok_batch.py +101 -0
- package/scripts/tests/CLAUDE.md +1 -0
- package/scripts/tests/test_grok_worker_constants.py +59 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +46 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +105 -2
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +34 -1
- package/skills/autoconverge/workflow/converge.mjs +66 -33
- package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +0 -4
- package/skills/codex-review/scripts/codex_usage_probe.py +20 -33
- package/skills/codex-review/scripts/run_codex_review.py +16 -64
- package/skills/codex-review/scripts/test_codex_usage_probe.py +46 -41
- package/skills/codex-review/scripts/test_run_codex_review.py +1 -33
- package/skills/e-code-review/SKILL.md +9 -8
- package/skills/e-code-review/reference/fix.md +29 -7
- package/skills/e-code-review/reference/loop.md +230 -14
- package/skills/e-code-review/reference/low.md +33 -15
- package/skills/e-code-review/reference/medium.md +55 -21
- package/skills/e-code-review/reference/xhigh.md +30 -12
- package/skills/fresh-branch/CLAUDE.md +5 -5
- package/skills/fresh-branch/SKILL.md +14 -6
- package/skills/fresh-branch/scripts/create_fresh_branch.py +122 -39
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +10 -3
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +251 -0
- package/skills/grok-spawn/SKILL.md +5 -0
- package/skills/orchestrator/SKILL.md +4 -1
- package/skills/orchestrator-refresh/SKILL.md +5 -1
- package/skills/team-advisor/SKILL.md +4 -1
|
@@ -17,9 +17,12 @@ if str(SCRIPTS_DIRECTORY) not in sys.path:
|
|
|
17
17
|
sys.path.insert(0, str(SCRIPTS_DIRECTORY))
|
|
18
18
|
|
|
19
19
|
from fresh_branch_scripts_constants.fresh_branch_cli_constants import (
|
|
20
|
+
ALL_REPOSITORY_WORKTREE_ROOT_PARTS,
|
|
21
|
+
CLI_FLAG_WORKTREE_ROOT,
|
|
20
22
|
DEFAULT_AGENT_SLUG,
|
|
21
23
|
DEFAULT_BASE_REF,
|
|
22
24
|
ERROR_CLI_ARGUMENTS,
|
|
25
|
+
ERROR_WORKTREE_ROOT_NOT_ABSOLUTE,
|
|
23
26
|
EXIT_CODE_FAILURE,
|
|
24
27
|
EXIT_CODE_SUCCESS,
|
|
25
28
|
FRESH_BRANCH_AGENT_ENV_VAR,
|
|
@@ -244,6 +247,40 @@ class TestResolveAgentSlug:
|
|
|
244
247
|
module.resolve_agent_slug("../evil")
|
|
245
248
|
|
|
246
249
|
|
|
250
|
+
class TestResolveConfiguredWorktreeRoot:
|
|
251
|
+
def should_default_to_repo_claude_worktrees(
|
|
252
|
+
self,
|
|
253
|
+
tmp_path: Path,
|
|
254
|
+
) -> None:
|
|
255
|
+
module = load_create_fresh_branch_module()
|
|
256
|
+
configured_root = module.resolve_configured_worktree_root(tmp_path)
|
|
257
|
+
expected_root = tmp_path.joinpath(*ALL_REPOSITORY_WORKTREE_ROOT_PARTS)
|
|
258
|
+
assert configured_root == expected_root.resolve()
|
|
259
|
+
|
|
260
|
+
def should_accept_an_absolute_root_outside_the_repo(
|
|
261
|
+
self,
|
|
262
|
+
tmp_path: Path,
|
|
263
|
+
) -> None:
|
|
264
|
+
module = load_create_fresh_branch_module()
|
|
265
|
+
external_root = (tmp_path / "external-isolated").resolve()
|
|
266
|
+
configured_root = module.resolve_configured_worktree_root(
|
|
267
|
+
tmp_path / "repo",
|
|
268
|
+
maybe_worktree_root=str(external_root),
|
|
269
|
+
)
|
|
270
|
+
assert configured_root == external_root
|
|
271
|
+
|
|
272
|
+
def should_reject_a_relative_worktree_root(
|
|
273
|
+
self,
|
|
274
|
+
tmp_path: Path,
|
|
275
|
+
) -> None:
|
|
276
|
+
module = load_create_fresh_branch_module()
|
|
277
|
+
with pytest.raises(ValueError, match="absolute path"):
|
|
278
|
+
module.resolve_configured_worktree_root(
|
|
279
|
+
tmp_path,
|
|
280
|
+
maybe_worktree_root="relative/worktrees",
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
247
284
|
class TestResolveAgentWorktreeRoot:
|
|
248
285
|
def should_nest_under_the_repository_claude_worktrees_directory(
|
|
249
286
|
self,
|
|
@@ -263,6 +300,19 @@ class TestResolveAgentWorktreeRoot:
|
|
|
263
300
|
assert grok_root != claude_root
|
|
264
301
|
assert grok_root.parent == claude_root.parent
|
|
265
302
|
|
|
303
|
+
def should_nest_agent_under_an_explicit_configured_root(
|
|
304
|
+
self,
|
|
305
|
+
tmp_path: Path,
|
|
306
|
+
) -> None:
|
|
307
|
+
module = load_create_fresh_branch_module()
|
|
308
|
+
external_root = (tmp_path / "isolated-root").resolve()
|
|
309
|
+
agent_root = module.resolve_agent_worktree_root(
|
|
310
|
+
tmp_path / "repo",
|
|
311
|
+
"grok",
|
|
312
|
+
maybe_worktree_root=str(external_root),
|
|
313
|
+
)
|
|
314
|
+
assert agent_root == external_root / "grok"
|
|
315
|
+
|
|
266
316
|
|
|
267
317
|
class TestResolveUniqueWorktreePath:
|
|
268
318
|
def should_return_preferred_when_missing(self, tmp_path: Path) -> None:
|
|
@@ -358,6 +408,146 @@ class TestBranchNamePathSafety:
|
|
|
358
408
|
assert not (tmp_path / "unused-repo").exists()
|
|
359
409
|
|
|
360
410
|
|
|
411
|
+
class TestConfiguredWorktreeRootIntegration:
|
|
412
|
+
def should_create_worktree_under_explicit_absolute_root(
|
|
413
|
+
self,
|
|
414
|
+
tmp_path: Path,
|
|
415
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
416
|
+
) -> None:
|
|
417
|
+
module = load_create_fresh_branch_module()
|
|
418
|
+
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
419
|
+
configured_root = (tmp_path / "isolated-worktrees").resolve()
|
|
420
|
+
caller_head_before = read_head_branch(repository_path)
|
|
421
|
+
caller_commit_before = read_head_commit(repository_path)
|
|
422
|
+
success_payload = module.create_fresh_branch(
|
|
423
|
+
branch_name="fix/external-root",
|
|
424
|
+
repo_path=repository_path,
|
|
425
|
+
agent_slug="grok",
|
|
426
|
+
base_ref=DEFAULT_BASE_REF,
|
|
427
|
+
maybe_worktree_root=str(configured_root),
|
|
428
|
+
)
|
|
429
|
+
worktree_path = Path(success_payload[PAYLOAD_KEY_WORKTREE_PATH])
|
|
430
|
+
assert worktree_path == configured_root / "grok" / "fix" / "external-root"
|
|
431
|
+
assert worktree_path.is_dir()
|
|
432
|
+
assert worktree_path.resolve().is_relative_to(configured_root)
|
|
433
|
+
assert not (
|
|
434
|
+
repository_path.resolve()
|
|
435
|
+
/ ".claude"
|
|
436
|
+
/ "worktrees"
|
|
437
|
+
/ "grok"
|
|
438
|
+
/ "fix"
|
|
439
|
+
/ "external-root"
|
|
440
|
+
).exists()
|
|
441
|
+
assert read_head_branch(repository_path) == caller_head_before
|
|
442
|
+
assert read_head_commit(repository_path) == caller_commit_before
|
|
443
|
+
|
|
444
|
+
def should_suffix_when_path_exists_under_configured_root(
|
|
445
|
+
self,
|
|
446
|
+
tmp_path: Path,
|
|
447
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
448
|
+
) -> None:
|
|
449
|
+
module = load_create_fresh_branch_module()
|
|
450
|
+
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
451
|
+
configured_root = (tmp_path / "isolated-worktrees").resolve()
|
|
452
|
+
occupied_path = configured_root / "claude" / "fix" / "collision"
|
|
453
|
+
occupied_path.mkdir(parents=True)
|
|
454
|
+
success_payload = module.create_fresh_branch(
|
|
455
|
+
branch_name="fix/collision",
|
|
456
|
+
repo_path=repository_path,
|
|
457
|
+
agent_slug="claude",
|
|
458
|
+
base_ref=DEFAULT_BASE_REF,
|
|
459
|
+
maybe_worktree_root=str(configured_root),
|
|
460
|
+
)
|
|
461
|
+
worktree_path = Path(success_payload[PAYLOAD_KEY_WORKTREE_PATH])
|
|
462
|
+
assert worktree_path == configured_root / "claude" / "fix" / "collision-2"
|
|
463
|
+
assert worktree_path.is_dir()
|
|
464
|
+
|
|
465
|
+
def should_reject_relative_worktree_root_without_creating_worktree(
|
|
466
|
+
self,
|
|
467
|
+
tmp_path: Path,
|
|
468
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
469
|
+
) -> None:
|
|
470
|
+
module = load_create_fresh_branch_module()
|
|
471
|
+
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
472
|
+
with pytest.raises(ValueError, match="absolute path") as raised_error:
|
|
473
|
+
module.create_fresh_branch(
|
|
474
|
+
branch_name="fix/relative-root",
|
|
475
|
+
repo_path=repository_path,
|
|
476
|
+
agent_slug="claude",
|
|
477
|
+
base_ref=DEFAULT_BASE_REF,
|
|
478
|
+
maybe_worktree_root="relative/worktrees",
|
|
479
|
+
)
|
|
480
|
+
assert str(raised_error.value) == ERROR_WORKTREE_ROOT_NOT_ABSOLUTE
|
|
481
|
+
assert not (repository_path / ".claude" / "worktrees").exists()
|
|
482
|
+
|
|
483
|
+
def should_reject_relative_worktree_root_before_fetch(
|
|
484
|
+
self,
|
|
485
|
+
tmp_path: Path,
|
|
486
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
487
|
+
) -> None:
|
|
488
|
+
module = load_create_fresh_branch_module()
|
|
489
|
+
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
490
|
+
all_fetch_calls: list[str] = []
|
|
491
|
+
|
|
492
|
+
def record_fetch(repo_root: Path, base_ref: str) -> None:
|
|
493
|
+
all_fetch_calls.append(base_ref)
|
|
494
|
+
raise AssertionError("fetch must not run for a relative worktree root")
|
|
495
|
+
|
|
496
|
+
monkeypatch.setattr(module, "fetch_base_ref", record_fetch)
|
|
497
|
+
with pytest.raises(ValueError, match="absolute path") as raised_error:
|
|
498
|
+
module.create_fresh_branch(
|
|
499
|
+
branch_name="fix/relative-root-before-fetch",
|
|
500
|
+
repo_path=repository_path,
|
|
501
|
+
agent_slug="claude",
|
|
502
|
+
base_ref=DEFAULT_BASE_REF,
|
|
503
|
+
maybe_worktree_root="relative/worktrees",
|
|
504
|
+
)
|
|
505
|
+
assert str(raised_error.value) == ERROR_WORKTREE_ROOT_NOT_ABSOLUTE
|
|
506
|
+
assert all_fetch_calls == []
|
|
507
|
+
|
|
508
|
+
def should_fetch_base_before_creating_worktree(
|
|
509
|
+
self,
|
|
510
|
+
tmp_path: Path,
|
|
511
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
512
|
+
) -> None:
|
|
513
|
+
module = load_create_fresh_branch_module()
|
|
514
|
+
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
515
|
+
all_call_names: list[str] = []
|
|
516
|
+
real_fetch = module.fetch_base_ref
|
|
517
|
+
real_create = module.create_worktree_branch
|
|
518
|
+
|
|
519
|
+
def record_fetch(
|
|
520
|
+
repo_root: Path,
|
|
521
|
+
base_ref: str,
|
|
522
|
+
) -> None:
|
|
523
|
+
all_call_names.append("fetch")
|
|
524
|
+
real_fetch(repo_root, base_ref)
|
|
525
|
+
|
|
526
|
+
def record_create(
|
|
527
|
+
repo_root: Path,
|
|
528
|
+
branch_name: str,
|
|
529
|
+
worktree_path: Path,
|
|
530
|
+
base_ref: str,
|
|
531
|
+
) -> None:
|
|
532
|
+
all_call_names.append("create")
|
|
533
|
+
real_create(
|
|
534
|
+
repo_root,
|
|
535
|
+
branch_name=branch_name,
|
|
536
|
+
worktree_path=worktree_path,
|
|
537
|
+
base_ref=base_ref,
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
monkeypatch.setattr(module, "fetch_base_ref", record_fetch)
|
|
541
|
+
monkeypatch.setattr(module, "create_worktree_branch", record_create)
|
|
542
|
+
module.create_fresh_branch(
|
|
543
|
+
branch_name="fix/fetch-order",
|
|
544
|
+
repo_path=repository_path,
|
|
545
|
+
agent_slug="claude",
|
|
546
|
+
base_ref=DEFAULT_BASE_REF,
|
|
547
|
+
)
|
|
548
|
+
assert all_call_names == ["fetch", "create"]
|
|
549
|
+
|
|
550
|
+
|
|
361
551
|
class TestCreateFreshBranchIntegration:
|
|
362
552
|
def should_create_worktree_branch_without_moving_caller_head(
|
|
363
553
|
self,
|
|
@@ -670,3 +860,64 @@ class TestMainCli:
|
|
|
670
860
|
assert "relative path" in error_payload[PAYLOAD_KEY_ERROR]
|
|
671
861
|
assert not outside_target.exists()
|
|
672
862
|
assert not (tmp_path / "unused-repo").exists()
|
|
863
|
+
|
|
864
|
+
def should_print_error_json_for_relative_worktree_root(
|
|
865
|
+
self,
|
|
866
|
+
tmp_path: Path,
|
|
867
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
868
|
+
capsys: pytest.CaptureFixture[str],
|
|
869
|
+
) -> None:
|
|
870
|
+
module = load_create_fresh_branch_module()
|
|
871
|
+
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
872
|
+
monkeypatch.setattr(
|
|
873
|
+
sys,
|
|
874
|
+
"argv",
|
|
875
|
+
[
|
|
876
|
+
"create_fresh_branch.py",
|
|
877
|
+
"--branch-name",
|
|
878
|
+
"feat/relative-root",
|
|
879
|
+
"--repo",
|
|
880
|
+
str(repository_path),
|
|
881
|
+
"--agent",
|
|
882
|
+
"claude",
|
|
883
|
+
CLI_FLAG_WORKTREE_ROOT,
|
|
884
|
+
"relative/worktrees",
|
|
885
|
+
],
|
|
886
|
+
)
|
|
887
|
+
exit_code = module.main()
|
|
888
|
+
captured = capsys.readouterr()
|
|
889
|
+
assert exit_code == EXIT_CODE_FAILURE
|
|
890
|
+
error_payload = json.loads(captured.out)
|
|
891
|
+
assert error_payload[PAYLOAD_KEY_ERROR] == ERROR_WORKTREE_ROOT_NOT_ABSOLUTE
|
|
892
|
+
|
|
893
|
+
def should_print_success_json_for_absolute_worktree_root(
|
|
894
|
+
self,
|
|
895
|
+
tmp_path: Path,
|
|
896
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
897
|
+
capsys: pytest.CaptureFixture[str],
|
|
898
|
+
) -> None:
|
|
899
|
+
module = load_create_fresh_branch_module()
|
|
900
|
+
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
901
|
+
configured_root = (tmp_path / "cli-isolated").resolve()
|
|
902
|
+
monkeypatch.setattr(
|
|
903
|
+
sys,
|
|
904
|
+
"argv",
|
|
905
|
+
[
|
|
906
|
+
"create_fresh_branch.py",
|
|
907
|
+
"--branch-name",
|
|
908
|
+
"feat/cli-root",
|
|
909
|
+
"--repo",
|
|
910
|
+
str(repository_path),
|
|
911
|
+
"--agent",
|
|
912
|
+
"grok",
|
|
913
|
+
CLI_FLAG_WORKTREE_ROOT,
|
|
914
|
+
str(configured_root),
|
|
915
|
+
],
|
|
916
|
+
)
|
|
917
|
+
exit_code = module.main()
|
|
918
|
+
captured = capsys.readouterr()
|
|
919
|
+
assert exit_code == EXIT_CODE_SUCCESS
|
|
920
|
+
success_payload = json.loads(captured.out)
|
|
921
|
+
worktree_path = Path(success_payload[PAYLOAD_KEY_WORKTREE_PATH])
|
|
922
|
+
assert worktree_path == configured_root / "grok" / "feat" / "cli-root"
|
|
923
|
+
assert worktree_path.is_dir()
|
|
@@ -148,6 +148,11 @@ Workers run with no turn cap. The timeout is the only bound on a worker's
|
|
|
148
148
|
length, and a worker that hits it is killed with its whole process tree and
|
|
149
149
|
reported as `timeout`.
|
|
150
150
|
|
|
151
|
+
A worker entry accepts these fields and no others. Any other key fails the
|
|
152
|
+
load with an error naming the stray key and listing the accepted set, so a
|
|
153
|
+
misspelled field shows up at startup rather than passing for a setting that
|
|
154
|
+
took effect.
|
|
155
|
+
|
|
151
156
|
Put the spec file under the run state directory (or any path you pass to
|
|
152
157
|
`--spec`).
|
|
153
158
|
|
|
@@ -129,7 +129,10 @@ pending, or when the tool is `CronCreate`.
|
|
|
129
129
|
whole lifecycle (its Lifecycle ownership section); executors only ever
|
|
130
130
|
message the warm agent or report here, and an executor that finds the
|
|
131
131
|
advisor unreachable reports that upward — it never spawns a
|
|
132
|
-
replacement itself.
|
|
132
|
+
replacement itself. A **Fable**-tier attempt carries the exact token
|
|
133
|
+
`FABLE-SPAWN-AUTHORIZED` in its spawn prompt, as the protocol's
|
|
134
|
+
warm-up rule states; `hooks/blocking/fable_spawn_gate.py` denies a
|
|
135
|
+
fable spawn whose prompt lacks it.
|
|
133
136
|
3. **Write the run artifacts** (next section) before the first spawn.
|
|
134
137
|
4. **Activate status_gate** when the first open ledger task exists:
|
|
135
138
|
`python scripts/status_gate.py set --status active`.
|
|
@@ -89,7 +89,11 @@ once, and reports the unreachable advisor.
|
|
|
89
89
|
- **Claude host:** executors consult the warm `session-advisor` via
|
|
90
90
|
`SendMessage` (ENDORSE / CORRECTION / PLAN / STOP). This session
|
|
91
91
|
routes the same way; keep tool use to orchestration and light
|
|
92
|
-
verification reads.
|
|
92
|
+
verification reads. A drift re-spawn at the **Fable** tier carries
|
|
93
|
+
the exact token `FABLE-SPAWN-AUTHORIZED` in its fresh prompt, as
|
|
94
|
+
the protocol's warm-up rule states;
|
|
95
|
+
`hooks/blocking/fable_spawn_gate.py` denies a fable spawn whose
|
|
96
|
+
prompt lacks it.
|
|
93
97
|
- **Third-party host:** advisor is a max-tier Claude CLI bind owned
|
|
94
98
|
by this session (`claude_chain_runner.py`, Fable high then Opus
|
|
95
99
|
max). Do **not** spawn `session-advisor` via Agent. Executors
|
|
@@ -27,7 +27,10 @@ simply this session's own tier — no routing table to take a max against.
|
|
|
27
27
|
warm-up spawn and charter, the consult format and cadence, drift-respawn, and
|
|
28
28
|
the CLI fallback — using `team-advisor-agent` as the name and this session as
|
|
29
29
|
the only consumer (skip the "who you are and your assignment" opener in each
|
|
30
|
-
consult; a single-consumer session doesn't need it).
|
|
30
|
+
consult; a single-consumer session doesn't need it). A **Fable**-tier attempt
|
|
31
|
+
carries the exact token `FABLE-SPAWN-AUTHORIZED` in its spawn prompt, as the
|
|
32
|
+
protocol's warm-up rule states; `hooks/blocking/fable_spawn_gate.py` denies a
|
|
33
|
+
fable spawn whose prompt lacks it.
|
|
31
34
|
|
|
32
35
|
**Third-party host:** bind a max-tier Claude advisor through the shared CLI Claude-chain
|
|
33
36
|
in the protocol (Fable max, then Opus max; `claude_chain_runner.py` ranks
|