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.
- package/CLAUDE.md +7 -1
- package/agents/clean-coder.md +9 -19
- package/agents/test_agent_frontmatter.py +26 -0
- package/docs/CODE_RULES.md +4 -2
- package/docs/references/CLAUDE.md +2 -2
- package/docs/references/advisor-tool.md +44 -6
- package/docs/references/team-advisor-skill.md +14 -8
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/output-styles/CLAUDE.md +17 -0
- package/output-styles/caveman-agent.md +37 -0
- package/package.json +2 -1
- package/rules/code-standards.md +33 -7
- package/rules/eli11-replies.md +1 -1
- package/scripts/CLAUDE.md +2 -2
- package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
- package/scripts/dev_env_scripts_constants/grok_run_ledger_constants.py +50 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +104 -0
- package/scripts/grok_patch_artifacts.py +123 -0
- package/scripts/grok_run_ledger.py +318 -0
- package/scripts/spawn_grok_batch.py +553 -9
- package/scripts/test_grok_patch_artifacts.py +82 -0
- package/scripts/test_grok_run_ledger.py +116 -0
- package/scripts/test_spawn_grok_batch.py +295 -0
- package/skills/CLAUDE.md +4 -2
- package/skills/_shared/CLAUDE.md +37 -4
- package/skills/_shared/advisor/CLAUDE.md +9 -0
- package/skills/_shared/advisor/advisor-protocol.md +5 -0
- package/skills/_shared/advisor/scripts/README.md +9 -0
- package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
- package/skills/_shared/pr-loop/CLAUDE.md +18 -1
- package/skills/_shared/pr-loop/audit-contract.md +5 -0
- package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
- package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
- package/skills/_shared/pr-loop/fix-protocol.md +5 -0
- package/skills/_shared/pr-loop/gh-payloads.md +5 -0
- package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
- package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
- package/skills/_shared/pr-loop/scripts/CLAUDE.md +8 -1
- package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -0
- package/skills/_shared/pr-loop/state-schema.md +5 -0
- package/skills/_shared/pr-loop/worker-spawn.md +5 -0
- package/skills/e-code-review/SKILL.md +6 -1
- package/skills/e-code-review/reference/runner-selection.md +40 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/__init__.py +1 -0
- package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -0
- package/skills/e-code-review/scripts/grok_code_review.py +221 -0
- package/skills/e-code-review/scripts/test_grok_code_review.py +212 -0
- package/skills/grok-spawn/SKILL.md +5 -0
- package/skills/orchestrator/SKILL.md +5 -0
- package/skills/task-build/reference/tool-routing.md +3 -0
- package/skills/team-advisor/SKILL.md +23 -44
- package/system-prompts/software-engineer.xml +6 -3
- 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
|
-
|
|
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)
|
|
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
|
|
package/skills/_shared/CLAUDE.md
CHANGED
|
@@ -1,11 +1,44 @@
|
|
|
1
|
-
# _shared
|
|
1
|
+
# skills/_shared
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
|
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
|
-
|
|
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/`).
|