claude-dev-env 2.8.0 → 2.9.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 (53) hide show
  1. package/CLAUDE.md +7 -1
  2. package/agents/clean-coder.md +9 -19
  3. package/agents/test_agent_frontmatter.py +26 -0
  4. package/docs/CODE_RULES.md +4 -2
  5. package/docs/references/CLAUDE.md +2 -2
  6. package/docs/references/advisor-tool.md +44 -6
  7. package/docs/references/team-advisor-skill.md +14 -8
  8. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  9. package/output-styles/CLAUDE.md +17 -0
  10. package/output-styles/caveman-agent.md +37 -0
  11. package/package.json +2 -1
  12. package/rules/code-standards.md +33 -7
  13. package/rules/eli11-replies.md +1 -1
  14. package/scripts/CLAUDE.md +2 -2
  15. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
  16. package/scripts/dev_env_scripts_constants/grok_run_ledger_constants.py +50 -0
  17. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +104 -0
  18. package/scripts/grok_patch_artifacts.py +123 -0
  19. package/scripts/grok_run_ledger.py +318 -0
  20. package/scripts/spawn_grok_batch.py +553 -9
  21. package/scripts/test_grok_patch_artifacts.py +82 -0
  22. package/scripts/test_grok_run_ledger.py +116 -0
  23. package/scripts/test_spawn_grok_batch.py +295 -0
  24. package/skills/CLAUDE.md +4 -2
  25. package/skills/_shared/CLAUDE.md +37 -4
  26. package/skills/_shared/advisor/CLAUDE.md +9 -0
  27. package/skills/_shared/advisor/advisor-protocol.md +5 -0
  28. package/skills/_shared/advisor/scripts/README.md +9 -0
  29. package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
  30. package/skills/_shared/pr-loop/CLAUDE.md +18 -1
  31. package/skills/_shared/pr-loop/audit-contract.md +5 -0
  32. package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
  33. package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
  34. package/skills/_shared/pr-loop/fix-protocol.md +5 -0
  35. package/skills/_shared/pr-loop/gh-payloads.md +5 -0
  36. package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
  37. package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
  38. package/skills/_shared/pr-loop/scripts/CLAUDE.md +8 -1
  39. package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -0
  40. package/skills/_shared/pr-loop/state-schema.md +5 -0
  41. package/skills/_shared/pr-loop/worker-spawn.md +5 -0
  42. package/skills/e-code-review/SKILL.md +6 -1
  43. package/skills/e-code-review/reference/runner-selection.md +40 -0
  44. package/skills/e-code-review/scripts/e_code_review_scripts_constants/__init__.py +1 -0
  45. package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -0
  46. package/skills/e-code-review/scripts/grok_code_review.py +221 -0
  47. package/skills/e-code-review/scripts/test_grok_code_review.py +212 -0
  48. package/skills/grok-spawn/SKILL.md +5 -0
  49. package/skills/orchestrator/SKILL.md +5 -0
  50. package/skills/task-build/reference/tool-routing.md +3 -0
  51. package/skills/team-advisor/SKILL.md +23 -44
  52. package/system-prompts/software-engineer.xml +6 -3
  53. package/skills/test_markdown_link_integrity.py +0 -107
@@ -0,0 +1,82 @@
1
+ """Behavioral tests for patch artifact extraction and manifests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
11
+ if str(_SCRIPTS_DIR) not in sys.path:
12
+ sys.path.insert(0, str(_SCRIPTS_DIR))
13
+
14
+ from grok_patch_artifacts import ( # noqa: E402
15
+ compute_sha256_hex,
16
+ extract_worktree_diff,
17
+ write_patch_manifest,
18
+ )
19
+
20
+
21
+ def _init_repo(path: Path) -> str:
22
+ subprocess.check_call(["git", "init"], cwd=path, stdout=subprocess.DEVNULL)
23
+ subprocess.check_call(
24
+ ["git", "config", "user.email", "test@example.com"], cwd=path, stdout=subprocess.DEVNULL
25
+ )
26
+ subprocess.check_call(
27
+ ["git", "config", "user.name", "test"], cwd=path, stdout=subprocess.DEVNULL
28
+ )
29
+ sample = path / "sample.txt"
30
+ sample.write_text("one\n", encoding="utf-8")
31
+ subprocess.check_call(["git", "add", "sample.txt"], cwd=path, stdout=subprocess.DEVNULL)
32
+ subprocess.check_call(
33
+ ["git", "commit", "-m", "init"], cwd=path, stdout=subprocess.DEVNULL
34
+ )
35
+ base = subprocess.check_output(
36
+ ["git", "rev-parse", "HEAD"], cwd=path, text=True
37
+ ).strip()
38
+ sample.write_text("one\ntwo\n", encoding="utf-8")
39
+ return base
40
+
41
+
42
+ def test_write_patch_manifest_binds_hash_paths_and_report(tmp_path: Path) -> None:
43
+ worktree = tmp_path / "wt"
44
+ worktree.mkdir()
45
+ base_sha = _init_repo(worktree)
46
+ run_dir = tmp_path / "run"
47
+ report_text = '{"status":"ok"}'
48
+ manifest = write_patch_manifest(
49
+ run_state_directory=run_dir,
50
+ task_id="O-04",
51
+ base_sha=base_sha,
52
+ worktree_path=worktree,
53
+ worker_report_text=report_text,
54
+ )
55
+ assert manifest["task_id"] == "O-04"
56
+ assert manifest["base_sha"] == base_sha
57
+ assert "sample.txt" in manifest["changed_paths"]
58
+ patch_path = Path(str(manifest["patch_path"]))
59
+ assert patch_path.is_file()
60
+ assert manifest["content_sha256"] == compute_sha256_hex(patch_path.read_bytes())
61
+ assert manifest["worker_report_sha256"] == compute_sha256_hex(
62
+ report_text.encode("utf-8")
63
+ )
64
+ on_disk = json.loads((run_dir / "patch-manifest.json").read_text(encoding="utf-8"))
65
+ assert on_disk["content_sha256"] == manifest["content_sha256"]
66
+
67
+
68
+ def test_compute_sha256_hex_is_stable() -> None:
69
+ digest = compute_sha256_hex(b"abc")
70
+ assert digest == compute_sha256_hex(b"abc")
71
+ assert digest != compute_sha256_hex(b"abd")
72
+
73
+
74
+ def test_extract_worktree_diff_returns_paths(tmp_path: Path) -> None:
75
+ worktree = tmp_path / "wt"
76
+ worktree.mkdir()
77
+ base_sha = _init_repo(worktree)
78
+ diff_text, all_changed_paths = extract_worktree_diff(
79
+ worktree_path=worktree, base_sha=base_sha
80
+ )
81
+ assert "sample.txt" in all_changed_paths
82
+ assert "two" in diff_text or "sample" in diff_text
@@ -0,0 +1,116 @@
1
+ """Behavioral tests for the host-neutral Grok run ledger."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
12
+ if str(_SCRIPTS_DIR) not in sys.path:
13
+ sys.path.insert(0, str(_SCRIPTS_DIR))
14
+
15
+ from dev_env_scripts_constants.grok_run_ledger_constants import ( # noqa: E402
16
+ TASK_STATUS_ADVISOR_BLOCKED,
17
+ TASK_STATUS_COMPLETED,
18
+ TASK_STATUS_PENDING,
19
+ TASK_STATUS_PENDING_REVIEW,
20
+ )
21
+ from grok_run_ledger import GrokRunLedger, is_legal_status # noqa: E402
22
+
23
+
24
+ def test_register_task_persists_atomically(tmp_path: Path) -> None:
25
+ ledger = GrokRunLedger(tmp_path)
26
+ ledger.register_task(task_id="O-04", all_dependencies=())
27
+ reloaded = GrokRunLedger(tmp_path)
28
+ record = reloaded.get_task("O-04")
29
+ assert record.status == TASK_STATUS_PENDING
30
+ assert is_legal_status(record.status)
31
+ payload = json.loads((tmp_path / "grok-run-ledger.json").read_text(encoding="utf-8"))
32
+ assert payload["tasks"][0]["task_id"] == "O-04"
33
+
34
+
35
+ def test_dependencies_block_dispatch(tmp_path: Path) -> None:
36
+ ledger = GrokRunLedger(tmp_path)
37
+ ledger.register_task(task_id="dep", all_dependencies=())
38
+ ledger.register_task(task_id="child", all_dependencies=("dep",))
39
+ assert ledger.can_dispatch("child") is False
40
+ with pytest.raises(ValueError, match="dependencies"):
41
+ ledger.mark_in_progress(
42
+ task_id="child",
43
+ owner_id="w1",
44
+ advisor_session_id="s1",
45
+ base_sha="aaa",
46
+ )
47
+ ledger.mark_in_progress(
48
+ task_id="dep", owner_id="w0", advisor_session_id="s0", base_sha="aaa"
49
+ )
50
+ ledger.mark_completed(
51
+ task_id="dep",
52
+ reviewed_head="bbb",
53
+ all_changed_paths=(),
54
+ advisor_verdict="ENDORSE",
55
+ all_acceptance_mapping={},
56
+ all_test_evidence=["ok"],
57
+ )
58
+ assert ledger.can_dispatch("child") is True
59
+
60
+
61
+ def test_one_live_owner_and_unique_advisor_session(tmp_path: Path) -> None:
62
+ ledger = GrokRunLedger(tmp_path)
63
+ ledger.register_task(task_id="a", all_dependencies=())
64
+ ledger.register_task(task_id="b", all_dependencies=())
65
+ ledger.mark_in_progress(
66
+ task_id="a", owner_id="owner", advisor_session_id="sess-a", base_sha="1"
67
+ )
68
+ with pytest.raises(ValueError, match="owner already live"):
69
+ ledger.mark_in_progress(
70
+ task_id="b", owner_id="owner", advisor_session_id="sess-b", base_sha="1"
71
+ )
72
+ with pytest.raises(ValueError, match="advisor session already bound"):
73
+ ledger.mark_in_progress(
74
+ task_id="b", owner_id="other", advisor_session_id="sess-a", base_sha="1"
75
+ )
76
+
77
+
78
+ def test_snapshot_drift_moves_to_pending_review(tmp_path: Path) -> None:
79
+ ledger = GrokRunLedger(tmp_path)
80
+ ledger.register_task(task_id="t", all_dependencies=())
81
+ ledger.mark_in_progress(
82
+ task_id="t", owner_id="w", advisor_session_id="s", base_sha="base"
83
+ )
84
+ record = ledger.invalidate_on_snapshot_drift(task_id="t", current_sha="drifted")
85
+ assert record.status == TASK_STATUS_PENDING_REVIEW
86
+ assert record.owner_id is None
87
+
88
+
89
+ def test_advisor_blocked_terminal(tmp_path: Path) -> None:
90
+ ledger = GrokRunLedger(tmp_path)
91
+ ledger.register_task(task_id="t", all_dependencies=())
92
+ ledger.mark_in_progress(
93
+ task_id="t", owner_id="w", advisor_session_id="s", base_sha="base"
94
+ )
95
+ record = ledger.mark_advisor_blocked(task_id="t", reason="bind failed")
96
+ assert record.status == TASK_STATUS_ADVISOR_BLOCKED
97
+ assert "bind failed" in record.test_evidence[0]
98
+
99
+
100
+ def test_completed_records_acceptance_and_head(tmp_path: Path) -> None:
101
+ ledger = GrokRunLedger(tmp_path)
102
+ ledger.register_task(task_id="t", all_dependencies=())
103
+ ledger.mark_in_progress(
104
+ task_id="t", owner_id="w", advisor_session_id="s", base_sha="base"
105
+ )
106
+ record = ledger.mark_completed(
107
+ task_id="t",
108
+ reviewed_head="head",
109
+ all_changed_paths=("a.py",),
110
+ advisor_verdict="ENDORSE",
111
+ all_acceptance_mapping={"criterion": "evidence"},
112
+ all_test_evidence=["pytest -q"],
113
+ )
114
+ assert record.status == TASK_STATUS_COMPLETED
115
+ assert record.reviewed_head == "head"
116
+ assert record.changed_paths == ("a.py",)
@@ -1319,3 +1319,298 @@ def test_load_batch_spec_rejects_empty_agent_name(tmp_path: Path) -> None:
1319
1319
 
1320
1320
  with pytest.raises(ValueError, match=WORKER_SPEC_AGENT_NAME_KEY):
1321
1321
  batch.load_batch_spec(specification_path)
1322
+
1323
+ # --- O-02 worker advisor contract ---
1324
+
1325
+
1326
+ def test_extract_advisor_signal_accepts_four_tokens_only() -> None:
1327
+ assert batch.extract_advisor_signal("ENDORSE\nok") == "ENDORSE"
1328
+ assert batch.extract_advisor_signal("CORRECTION fix path") == "CORRECTION"
1329
+ assert batch.extract_advisor_signal("PLAN later") == "PLAN"
1330
+ assert batch.extract_advisor_signal("STOP") == "STOP"
1331
+ assert batch.extract_advisor_signal("hello ENDORSE") is None
1332
+ assert batch.extract_advisor_signal("") is None
1333
+
1334
+
1335
+ def test_load_batch_spec_parses_advisor_block(tmp_path: Path) -> None:
1336
+ header_part, body_part = _write_prompt_parts(tmp_path)
1337
+ payload = _worker_payload(
1338
+ role_name="lens",
1339
+ all_prompt_parts=[str(header_part), str(body_part)],
1340
+ working_directory=tmp_path,
1341
+ tool_profile=TOOL_PROFILE_READONLY,
1342
+ )
1343
+ specification_path = tmp_path / "batch-spec.json"
1344
+ specification_path.write_text(
1345
+ json.dumps(
1346
+ {
1347
+ "role": DEFAULT_ROLE,
1348
+ "should_ping": False,
1349
+ "workers": [payload],
1350
+ "advisor": {
1351
+ "launcher": "fixture-advisor-launcher",
1352
+ "model": "opus",
1353
+ "effort": "high",
1354
+ },
1355
+ }
1356
+ ),
1357
+ encoding=UTF8_ENCODING,
1358
+ )
1359
+ loaded = batch.load_batch_spec(specification_path)
1360
+ assert loaded.advisor is not None
1361
+ assert loaded.advisor.launcher == "fixture-advisor-launcher"
1362
+ assert loaded.advisor.model == "opus"
1363
+ assert loaded.advisor.effort == "high"
1364
+
1365
+
1366
+ def test_unique_advisor_sessions_and_completion_verdict(
1367
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
1368
+ ) -> None:
1369
+ header_a, body_a = _write_prompt_parts(tmp_path, role_marker="alpha")
1370
+ header_b, body_b = _write_prompt_parts(tmp_path, role_marker="beta")
1371
+ workers = [
1372
+ _worker_payload(
1373
+ role_name="alpha",
1374
+ all_prompt_parts=[str(header_a), str(body_a)],
1375
+ working_directory=tmp_path,
1376
+ tool_profile=TOOL_PROFILE_BUILD,
1377
+ ),
1378
+ _worker_payload(
1379
+ role_name="beta",
1380
+ all_prompt_parts=[str(header_b), str(body_b)],
1381
+ working_directory=tmp_path,
1382
+ tool_profile=TOOL_PROFILE_BUILD,
1383
+ ),
1384
+ ]
1385
+ specification_path = tmp_path / "batch-spec.json"
1386
+ specification_path.write_text(
1387
+ json.dumps(
1388
+ {
1389
+ "role": DEFAULT_ROLE,
1390
+ "should_ping": False,
1391
+ "workers": workers,
1392
+ "advisor": {
1393
+ "launcher": "fixture-advisor-launcher",
1394
+ "model": "opus",
1395
+ "effort": "high",
1396
+ },
1397
+ }
1398
+ ),
1399
+ encoding=UTF8_ENCODING,
1400
+ )
1401
+ bind_count = {"n": 0}
1402
+ sessions_issued: list[str] = []
1403
+
1404
+ def fake_advisor(
1405
+ *,
1406
+ launcher: str,
1407
+ model: str,
1408
+ effort: str,
1409
+ prompt_text: str,
1410
+ session_id: str | None = None,
1411
+ ) -> tuple[str | None, str, int]:
1412
+ assert launcher == "fixture-advisor-launcher"
1413
+ assert model == "opus"
1414
+ assert effort == "high"
1415
+ if session_id is None:
1416
+ bind_count["n"] += 1
1417
+ session = f"session-{bind_count['n']}"
1418
+ sessions_issued.append(session)
1419
+ return session, "ENDORSE\npre-dispatch ok", 0
1420
+ return session_id, "ENDORSE\npost-report ok", 0
1421
+
1422
+ monkeypatch.setattr(batch, "batch_invoke_advisor", fake_advisor)
1423
+ monkeypatch.setattr(
1424
+ batch,
1425
+ "batch_preflight",
1426
+ lambda **kwargs: PreflightOutcome(is_usable=True, reason=None),
1427
+ )
1428
+ monkeypatch.setattr(batch, "batch_sleep", lambda seconds: None)
1429
+ recorder = _RunnerRecorder(
1430
+ {
1431
+ "alpha": _ok_outcome(),
1432
+ "beta": _ok_outcome(),
1433
+ }
1434
+ )
1435
+ monkeypatch.setattr(batch, "batch_headless_runner", recorder)
1436
+ loaded = batch.load_batch_spec(specification_path)
1437
+ summary = batch.run_grok_batch(
1438
+ batch_spec=loaded, run_state_directory=tmp_path / "run"
1439
+ )
1440
+ assert summary.is_preflight_usable
1441
+ assert len(summary.all_worker_reports) == 2
1442
+ all_session_ids = {
1443
+ each.advisor_session_id for each in summary.all_worker_reports
1444
+ }
1445
+ assert all_session_ids == {"session-1", "session-2"}
1446
+ assert all(
1447
+ each.advisor_completion_signal == "ENDORSE"
1448
+ for each in summary.all_worker_reports
1449
+ )
1450
+ assert all(
1451
+ each.classification != "advisor_blocked"
1452
+ for each in summary.all_worker_reports
1453
+ )
1454
+ for each_report in summary.all_worker_reports:
1455
+ prompt_text = Path(each_report.prompt_path).read_text(encoding=UTF8_ENCODING)
1456
+ assert each_report.advisor_session_id in prompt_text
1457
+
1458
+
1459
+ def test_advisor_failure_classifies_advisor_blocked(
1460
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
1461
+ ) -> None:
1462
+ header_part, body_part = _write_prompt_parts(tmp_path, role_marker="solo")
1463
+ payload = _worker_payload(
1464
+ role_name="solo",
1465
+ all_prompt_parts=[str(header_part), str(body_part)],
1466
+ working_directory=tmp_path,
1467
+ tool_profile=TOOL_PROFILE_BUILD,
1468
+ )
1469
+ specification_path = tmp_path / "batch-spec.json"
1470
+ specification_path.write_text(
1471
+ json.dumps(
1472
+ {
1473
+ "role": DEFAULT_ROLE,
1474
+ "should_ping": False,
1475
+ "workers": [payload],
1476
+ "advisor": {"launcher": "fixture-advisor-launcher"},
1477
+ }
1478
+ ),
1479
+ encoding=UTF8_ENCODING,
1480
+ )
1481
+
1482
+ def failing_advisor(**kwargs: object) -> tuple[str | None, str, int]:
1483
+ return None, "", 1
1484
+
1485
+ monkeypatch.setattr(batch, "batch_invoke_advisor", failing_advisor)
1486
+ monkeypatch.setattr(
1487
+ batch,
1488
+ "batch_preflight",
1489
+ lambda **kwargs: PreflightOutcome(is_usable=True, reason=None),
1490
+ )
1491
+ monkeypatch.setattr(batch, "batch_sleep", lambda seconds: None)
1492
+ monkeypatch.setattr(
1493
+ batch,
1494
+ "batch_headless_runner",
1495
+ _RunnerRecorder({"solo": _ok_outcome()}),
1496
+ )
1497
+ loaded = batch.load_batch_spec(specification_path)
1498
+ summary = batch.run_grok_batch(
1499
+ batch_spec=loaded, run_state_directory=tmp_path / "run"
1500
+ )
1501
+ assert len(summary.all_worker_reports) == 1
1502
+ report = summary.all_worker_reports[0]
1503
+ assert report.classification == "advisor_blocked"
1504
+ assert report.is_ok is False
1505
+
1506
+ def test_bind_unique_worker_advisor_rejects_placeholder(
1507
+ monkeypatch: pytest.MonkeyPatch,
1508
+ ) -> None:
1509
+ def boom(**kwargs: object) -> tuple[str | None, str, int]:
1510
+ raise AssertionError("should not call launcher for placeholder")
1511
+
1512
+ monkeypatch.setattr(batch, "batch_invoke_advisor", boom)
1513
+ with pytest.raises(ValueError, match="placeholder"):
1514
+ batch.bind_unique_worker_advisor(
1515
+ advisor_spec=batch.AdvisorSpec(launcher=batch.DEFAULT_ADVISOR_LAUNCHER_PLACEHOLDER),
1516
+ role_name="lens",
1517
+ all_used_session_ids=set(),
1518
+ )
1519
+
1520
+
1521
+ def test_bind_unique_worker_advisor_returns_session(
1522
+ monkeypatch: pytest.MonkeyPatch,
1523
+ ) -> None:
1524
+ def fake(**kwargs: object) -> tuple[str | None, str, int]:
1525
+ return "sess-unique-1", "ENDORSE\nok", 0
1526
+
1527
+ monkeypatch.setattr(batch, "batch_invoke_advisor", fake)
1528
+ session_id, signal = batch.bind_unique_worker_advisor(
1529
+ advisor_spec=batch.AdvisorSpec(launcher="fixture-advisor-launcher"),
1530
+ role_name="lens",
1531
+ all_used_session_ids=set(),
1532
+ )
1533
+ assert session_id == "sess-unique-1"
1534
+ assert signal == "ENDORSE"
1535
+
1536
+
1537
+ def test_obtain_advisor_completion_verdict_endorses(
1538
+ monkeypatch: pytest.MonkeyPatch,
1539
+ ) -> None:
1540
+ def fake(**kwargs: object) -> tuple[str | None, str, int]:
1541
+ return kwargs.get("session_id"), "ENDORSE\nok", 0
1542
+
1543
+ monkeypatch.setattr(batch, "batch_invoke_advisor", fake)
1544
+ signal = batch.obtain_advisor_completion_verdict(
1545
+ advisor_spec=batch.AdvisorSpec(launcher="fixture-advisor-launcher"),
1546
+ role_name="lens",
1547
+ session_id="sess-1",
1548
+ report_text="done",
1549
+ )
1550
+ assert signal == "ENDORSE"
1551
+
1552
+
1553
+ def test_invoke_advisor_launcher_builds_command(
1554
+ monkeypatch: pytest.MonkeyPatch,
1555
+ ) -> None:
1556
+ captured: dict[str, object] = {}
1557
+
1558
+ class _Completed:
1559
+ returncode = 0
1560
+ stdout = '{"session_id":"s1","result":"ENDORSE\\nok"}'
1561
+ stderr = ""
1562
+
1563
+ def fake_run(args, **kwargs): # type: ignore[no-untyped-def] # subprocess.run stub for argv capture
1564
+ captured["args"] = list(args)
1565
+ captured["input"] = kwargs.get("input")
1566
+ return _Completed()
1567
+
1568
+ monkeypatch.setattr(batch.subprocess, "run", fake_run)
1569
+ session_id, body, code = batch.invoke_advisor_launcher(
1570
+ launcher="fixture-advisor-launcher",
1571
+ model="opus",
1572
+ effort="high",
1573
+ prompt_text="hello",
1574
+ )
1575
+ assert code == 0
1576
+ assert session_id == "s1"
1577
+ assert "ENDORSE" in body
1578
+ assert captured["args"][0] == "fixture-advisor-launcher"
1579
+ assert "--model" in captured["args"]
1580
+
1581
+
1582
+ def test_invoke_advisor_launcher_missing_binary_raises_advisor_failure() -> None:
1583
+ try:
1584
+ batch.invoke_advisor_launcher(
1585
+ launcher="__no_such_advisor_launcher_xyz__",
1586
+ model="opus",
1587
+ effort="high",
1588
+ prompt_text="ping",
1589
+ )
1590
+ raise AssertionError("expected AdvisorFailureError")
1591
+ except batch.AdvisorFailureError as raised:
1592
+ assert "not found" in str(raised).lower() or "launcher" in str(raised).lower()
1593
+
1594
+
1595
+ def test_invoke_advisor_launcher_passes_timeout(
1596
+ monkeypatch: pytest.MonkeyPatch,
1597
+ ) -> None:
1598
+ captured: dict[str, object] = {}
1599
+
1600
+ class _Completed:
1601
+ returncode = 0
1602
+ stdout = '{"session_id":"s-timeout","result":"ENDORSE\\nok"}'
1603
+ stderr = ""
1604
+
1605
+ def fake_run(args, **kwargs): # type: ignore[no-untyped-def] # subprocess.run stub
1606
+ captured["timeout"] = kwargs.get("timeout")
1607
+ return _Completed()
1608
+
1609
+ monkeypatch.setattr(batch.subprocess, "run", fake_run)
1610
+ batch.invoke_advisor_launcher(
1611
+ launcher="fixture-advisor-launcher",
1612
+ model="opus",
1613
+ effort="high",
1614
+ prompt_text="hello",
1615
+ )
1616
+ assert captured["timeout"] == batch.MAXIMUM_ADVISOR_TIMEOUT_SECONDS
package/skills/CLAUDE.md CHANGED
@@ -17,7 +17,9 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
17
17
 
18
18
  ## Shared support code
19
19
 
20
- `_shared/`support code used by more than one skill. It holds `pr-loop/`, which provides prompt templates and Python helper scripts shared across the PR-loop skills, and `advisor/`, which provides the shared warm-advisor protocol used by `team-advisor`, `orchestrator`, and `orchestrator-refresh`.
20
+ **`skills/_shared/`** — skill-local PR-loop helpers plus `@` stubs that name
21
+ canonical homes under **`@~/.claude/_shared/`** (advisor protocol, PR-loop
22
+ contracts, runtime scripts). Map: `skills/_shared/CLAUDE.md`. End-of-run gotchas: `skills/_shared/end-of-run-gotcha-recommendations.md`.
21
23
 
22
24
  ## Skill groups
23
25
 
@@ -25,7 +27,7 @@ Skills install to `~/.claude/skills/<skill-name>/` via `packages/claude-dev-env/
25
27
  - `anthropic-plan` — creates a source-grounded plan packet before any code changes
26
28
  - `orchestrator` — turns the session into the orchestrator: it spawns executor subagents to do the code edits and test runs; hard decisions go to a shared advisor (Claude warm `session-advisor` via SendMessage; a third-party host: max-tier Claude via CLI Claude-chain)
27
29
  - `orchestrator-refresh` — sub-skill fired by the `/orchestrator` loop to re-assert the host-matched shared-advisor discipline mid-run (Claude SendMessage; a third-party host's Claude CLI chain, no Agent-tool advisor spawn)
28
- - `team-advisor` — binds one advisor at the strongest reachable tier (Claude warm agent; a third-party host: max-tier Claude via CLI Claude-chain, fail closed when unreachable) and consults it for a second opinion before a big decision, at completion, when stuck, or when reconsidering the approach
30
+ - `team-advisor` — binds one advisor at the strongest reachable tier (Claude warm agent; a third-party host: max-tier Claude via CLI Claude-chain, fail closed when unreachable); consult cadence and weight live in `docs/references/advisor-tool.md`
29
31
  - `grokify` — builds a paste-ready Grok Build handoff with a Claude advisor charter
30
32
  - `grok-spawn` — orchestrator playbook for fleets of headless grok CLI workers (preflight, batch spec, `spawn_grok_batch.py`)
31
33
 
@@ -1,11 +1,44 @@
1
- # _shared
1
+ # skills/_shared
2
2
 
3
- Support code shared across multiple skills. Each subdirectory targets a specific cross-skill concern.
3
+ **Map** for skill-install shared assets. Open a stub, then load the `@` target.
4
+
5
+ ## Two homes
6
+
7
+ | Home | Path | Holds |
8
+ |---|---|---|
9
+ | **Skills shared** | `~/.claude/skills/_shared/` | Converge helpers, end-of-run gotcha ref, and `@` stubs |
10
+ | **Top-level shared** | `~/.claude/_shared/` | Advisor protocol, PR-loop contracts, runtime scripts |
11
+
12
+
13
+ ## Reference docs (this tree)
14
+
15
+ | File | Role |
16
+ |---|---|
17
+ | **`end-of-run-gotcha-recommendations.md`** | End-of-run pasteable gotcha protocol for every skill |
4
18
 
5
19
  ## Subdirectories
6
20
 
7
21
  | Directory | Role |
8
22
  |---|---|
9
- | `pr-loop/` | Prompt templates and Python helper scripts used by both `bugteam` and `pr-converge` for their audit-fix loop. |
23
+ | **`advisor/`** | Stubs `@~/.claude/_shared/advisor/` |
24
+ | **`pr-loop/`** | Local converge helpers + stubs → `@~/.claude/_shared/pr-loop/` |
25
+
26
+ ## Canonical-path stubs
27
+
28
+ | Stub here | Load |
29
+ |---|---|
30
+ | `advisor/advisor-protocol.md` | `@~/.claude/_shared/advisor/advisor-protocol.md` |
31
+ | `advisor/CLAUDE.md` | `@~/.claude/_shared/advisor/CLAUDE.md` |
32
+ | `advisor/scripts/README.md` | `@~/.claude/_shared/advisor/scripts/` |
33
+ | `pr-loop/audit-contract.md` | `@~/.claude/_shared/pr-loop/audit-contract.md` |
34
+ | `pr-loop/audit-reply-template.md` | `@~/.claude/_shared/pr-loop/audit-reply-template.md` |
35
+ | `pr-loop/code-rules-gate.md` | `@~/.claude/_shared/pr-loop/code-rules-gate.md` |
36
+ | `pr-loop/fix-protocol.md` | `@~/.claude/_shared/pr-loop/fix-protocol.md` |
37
+ | `pr-loop/gh-payloads.md` | `@~/.claude/_shared/pr-loop/gh-payloads.md` |
38
+ | `pr-loop/post-audit-thread-contract.md` | `@~/.claude/_shared/pr-loop/post-audit-thread-contract.md` |
39
+ | `pr-loop/precatch-rubric.md` | `@~/.claude/_shared/pr-loop/precatch-rubric.md` |
40
+ | `pr-loop/state-schema.md` | `@~/.claude/_shared/pr-loop/state-schema.md` |
41
+ | `pr-loop/worker-spawn.md` | `@~/.claude/_shared/pr-loop/worker-spawn.md` |
42
+ | `pr-loop/scripts/RUNTIME_SCRIPTS.md` | `@~/.claude/_shared/pr-loop/scripts/` |
10
43
 
11
- Files here are not skills themselves and have no `SKILL.md`. They install alongside each consuming skill via the install pipeline in `packages/claude-dev-env/bin/install.mjs`.
44
+ Install via `packages/claude-dev-env/bin/install.mjs`.
@@ -0,0 +1,9 @@
1
+ # Advisor
2
+
3
+ **Canonical home:**
4
+
5
+ @~/.claude/_shared/advisor/CLAUDE.md
6
+
7
+ @~/.claude/_shared/advisor/advisor-protocol.md
8
+
9
+ Scripts: `~/.claude/_shared/advisor/scripts/` (`model_tier_run_validator.py`, `tier_model_ids.py`, constants under `scripts/config/`).
@@ -0,0 +1,5 @@
1
+ # Advisor protocol
2
+
3
+ **Canonical home:**
4
+
5
+ @~/.claude/_shared/advisor/advisor-protocol.md
@@ -0,0 +1,9 @@
1
+ # Advisor scripts
2
+
3
+ **Canonical home:**
4
+
5
+ @~/.claude/_shared/advisor/scripts/model_tier_run_validator.py
6
+
7
+ @~/.claude/_shared/advisor/scripts/tier_model_ids.py
8
+
9
+ Full tree: `~/.claude/_shared/advisor/scripts/`.