claude-dev-env 2.7.1 → 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/_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/clean-coder.md +9 -19
- 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 +335 -12
- 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/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/code_rules_path_utils_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/output-styles/CLAUDE.md +17 -0
- package/output-styles/caveman-agent.md +37 -0
- package/package.json +2 -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/rules/code-standards.md +33 -7
- package/rules/eli11-replies.md +1 -1
- package/scripts/CLAUDE.md +3 -3
- 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_run_ledger_constants.py +50 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +132 -24
- package/scripts/grok_headless_runner.py +16 -83
- package/scripts/grok_patch_artifacts.py +123 -0
- package/scripts/grok_run_ledger.py +318 -0
- package/scripts/spawn_grok_batch.py +591 -10
- package/scripts/test_claude_chain_runner.py +358 -0
- package/scripts/test_grok_headless_runner.py +18 -63
- package/scripts/test_grok_patch_artifacts.py +82 -0
- package/scripts/test_grok_run_ledger.py +116 -0
- 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 +396 -0
- package/scripts/tests/CLAUDE.md +1 -0
- package/scripts/tests/test_grok_worker_constants.py +59 -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/scripts/test_build_audit_prompt.py +46 -0
- package/skills/_shared/pr-loop/state-schema.md +5 -0
- package/skills/_shared/pr-loop/worker-spawn.md +5 -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 +15 -9
- 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/runner-selection.md +40 -0
- package/skills/e-code-review/reference/xhigh.md +30 -12
- 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/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 +10 -0
- package/skills/orchestrator/SKILL.md +9 -1
- package/skills/orchestrator-refresh/SKILL.md +5 -1
- package/skills/task-build/reference/tool-routing.md +3 -0
- package/skills/team-advisor/SKILL.md +23 -41
- package/system-prompts/software-engineer.xml +6 -3
- package/skills/test_markdown_link_integrity.py +0 -107
|
@@ -874,6 +874,107 @@ def test_load_batch_spec_missing_worker_keys_raise_value_error(
|
|
|
874
874
|
).lower() or "must be" in str(raised_error.value).lower()
|
|
875
875
|
|
|
876
876
|
|
|
877
|
+
def test_load_batch_spec_rejects_an_unknown_worker_key(
|
|
878
|
+
tmp_path: Path,
|
|
879
|
+
) -> None:
|
|
880
|
+
header_part, body_part = _write_prompt_parts(tmp_path)
|
|
881
|
+
working_directory = tmp_path / "project"
|
|
882
|
+
working_directory.mkdir()
|
|
883
|
+
worker_payload = _worker_payload(
|
|
884
|
+
role_name="stray-key-worker",
|
|
885
|
+
all_prompt_parts=[str(header_part), str(body_part)],
|
|
886
|
+
working_directory=working_directory,
|
|
887
|
+
tool_profile=TOOL_PROFILE_BUILD,
|
|
888
|
+
)
|
|
889
|
+
worker_payload["timeout_second"] = 30
|
|
890
|
+
specification_path = _write_batch_spec(
|
|
891
|
+
tmp_path, all_worker_payloads=[worker_payload]
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
with pytest.raises(ValueError) as raised_error:
|
|
895
|
+
batch.load_batch_spec(specification_path)
|
|
896
|
+
|
|
897
|
+
error_text = str(raised_error.value)
|
|
898
|
+
assert "timeout_second" in error_text
|
|
899
|
+
assert WORKER_SPEC_TIMEOUT_KEY in error_text
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
def test_load_batch_spec_names_every_unknown_worker_key(
|
|
903
|
+
tmp_path: Path,
|
|
904
|
+
) -> None:
|
|
905
|
+
header_part, body_part = _write_prompt_parts(tmp_path)
|
|
906
|
+
working_directory = tmp_path / "project"
|
|
907
|
+
working_directory.mkdir()
|
|
908
|
+
worker_payload = _worker_payload(
|
|
909
|
+
role_name="two-stray-keys",
|
|
910
|
+
all_prompt_parts=[str(header_part), str(body_part)],
|
|
911
|
+
working_directory=working_directory,
|
|
912
|
+
tool_profile=TOOL_PROFILE_BUILD,
|
|
913
|
+
)
|
|
914
|
+
worker_payload["stray_cap"] = 5
|
|
915
|
+
worker_payload["notes"] = "operator scratch"
|
|
916
|
+
specification_path = _write_batch_spec(
|
|
917
|
+
tmp_path, all_worker_payloads=[worker_payload]
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
with pytest.raises(ValueError) as raised_error:
|
|
921
|
+
batch.load_batch_spec(specification_path)
|
|
922
|
+
|
|
923
|
+
error_text = str(raised_error.value)
|
|
924
|
+
assert "stray_cap" in error_text
|
|
925
|
+
assert "notes" in error_text
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def test_load_batch_spec_rejects_the_retired_turn_cap_key(
|
|
929
|
+
tmp_path: Path,
|
|
930
|
+
) -> None:
|
|
931
|
+
header_part, body_part = _write_prompt_parts(tmp_path)
|
|
932
|
+
working_directory = tmp_path / "project"
|
|
933
|
+
working_directory.mkdir()
|
|
934
|
+
worker_payload = _worker_payload(
|
|
935
|
+
role_name="retired-cap-worker",
|
|
936
|
+
all_prompt_parts=[str(header_part), str(body_part)],
|
|
937
|
+
working_directory=working_directory,
|
|
938
|
+
tool_profile=TOOL_PROFILE_BUILD,
|
|
939
|
+
)
|
|
940
|
+
worker_payload[RETIRED_MAX_TURNS_KEYWORD] = 5
|
|
941
|
+
specification_path = _write_batch_spec(
|
|
942
|
+
tmp_path, all_worker_payloads=[worker_payload]
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
with pytest.raises(ValueError) as raised_error:
|
|
946
|
+
batch.load_batch_spec(specification_path)
|
|
947
|
+
|
|
948
|
+
error_text = str(raised_error.value)
|
|
949
|
+
assert RETIRED_MAX_TURNS_KEYWORD in error_text
|
|
950
|
+
assert WORKER_SPEC_TIMEOUT_KEY in error_text
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
def test_load_batch_spec_accepts_every_documented_worker_key(
|
|
954
|
+
tmp_path: Path,
|
|
955
|
+
) -> None:
|
|
956
|
+
header_part, body_part = _write_prompt_parts(tmp_path)
|
|
957
|
+
working_directory = tmp_path / "project"
|
|
958
|
+
working_directory.mkdir()
|
|
959
|
+
worker_payload: dict[str, object] = {
|
|
960
|
+
"role_name": "every-key-worker",
|
|
961
|
+
"prompt_parts": [str(header_part), str(body_part)],
|
|
962
|
+
"cwd": str(working_directory),
|
|
963
|
+
"tool_profile": TOOL_PROFILE_READONLY,
|
|
964
|
+
"timeout_seconds": 30,
|
|
965
|
+
"is_repo_only": True,
|
|
966
|
+
"agent_name": None,
|
|
967
|
+
}
|
|
968
|
+
specification_path = _write_batch_spec(
|
|
969
|
+
tmp_path, all_worker_payloads=[worker_payload]
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
batch_spec = batch.load_batch_spec(specification_path)
|
|
973
|
+
|
|
974
|
+
assert batch_spec.all_workers[0].role_name == "every-key-worker"
|
|
975
|
+
assert batch_spec.all_workers[0].is_repo_only is True
|
|
976
|
+
|
|
977
|
+
|
|
877
978
|
def test_load_batch_spec_rejects_non_positive_timeout(
|
|
878
979
|
tmp_path: Path,
|
|
879
980
|
) -> None:
|
|
@@ -1218,3 +1319,298 @@ def test_load_batch_spec_rejects_empty_agent_name(tmp_path: Path) -> None:
|
|
|
1218
1319
|
|
|
1219
1320
|
with pytest.raises(ValueError, match=WORKER_SPEC_AGENT_NAME_KEY):
|
|
1220
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/scripts/tests/CLAUDE.md
CHANGED
|
@@ -10,6 +10,7 @@ pytest suite for the Python scripts and Pester suite for the PowerShell scripts
|
|
|
10
10
|
| `test_setup_project_paths_config.py` | Configuration constants used by `setup_project_paths.py` |
|
|
11
11
|
| `test_sweep_empty_dirs.py` | `sweep_empty_dirs.py` — age check, one-shot mode, and continuous-watch behavior |
|
|
12
12
|
| `test_sync_to_cursor.py` | `sync_to_cursor/` package — mapping, hashing, manifest, and path resolution |
|
|
13
|
+
| `test_grok_worker_constants.py` | `grok_worker_constants.py` — the accepted batch worker-key set stays in step with the worker key constants, and the unknown-key message names both its placeholders |
|
|
13
14
|
|
|
14
15
|
## PowerShell test files
|
|
15
16
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Specifications for the accepted-key set on a batch worker entry.
|
|
2
|
+
|
|
3
|
+
The batch launcher rejects a worker entry carrying any key outside
|
|
4
|
+
``ALL_KNOWN_WORKER_SPEC_KEYS``. That makes the set a gate on the published
|
|
5
|
+
JSON contract: a worker key the module defines but the set omits is rejected
|
|
6
|
+
from every spec that uses it, and a member the module declares nowhere keeps
|
|
7
|
+
accepting a key the launcher drops. Both drifts are silent, so they are
|
|
8
|
+
pinned here.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dev_env_scripts_constants import grok_worker_constants
|
|
14
|
+
|
|
15
|
+
WORKER_SPEC_KEY_NAME_PREFIX: str = "WORKER_SPEC_"
|
|
16
|
+
WORKER_SPEC_KEY_NAME_SUFFIX: str = "_KEY"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _all_declared_worker_key_values() -> set[str]:
|
|
20
|
+
"""Read every ``WORKER_SPEC_*_KEY`` value the constants module declares."""
|
|
21
|
+
return {
|
|
22
|
+
getattr(grok_worker_constants, each_name)
|
|
23
|
+
for each_name in dir(grok_worker_constants)
|
|
24
|
+
if each_name.startswith(WORKER_SPEC_KEY_NAME_PREFIX)
|
|
25
|
+
and each_name.endswith(WORKER_SPEC_KEY_NAME_SUFFIX)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_should_accept_every_declared_worker_spec_key() -> None:
|
|
30
|
+
all_declared_key_values = _all_declared_worker_key_values()
|
|
31
|
+
all_missing_key_values = (
|
|
32
|
+
all_declared_key_values - grok_worker_constants.ALL_KNOWN_WORKER_SPEC_KEYS
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
assert not all_missing_key_values, (
|
|
36
|
+
"worker key constants missing from the accepted set: "
|
|
37
|
+
f"{sorted(all_missing_key_values)}"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_should_accept_only_keys_the_module_declares() -> None:
|
|
42
|
+
all_declared_key_values = _all_declared_worker_key_values()
|
|
43
|
+
all_stale_key_values = (
|
|
44
|
+
grok_worker_constants.ALL_KNOWN_WORKER_SPEC_KEYS - all_declared_key_values
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
assert not all_stale_key_values, (
|
|
48
|
+
f"accepted keys with no worker key constant: {sorted(all_stale_key_values)}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_should_name_both_placeholders_in_the_unknown_key_message() -> None:
|
|
53
|
+
formatted_message = grok_worker_constants.UNKNOWN_WORKER_KEY_ERROR_TEMPLATE.format(
|
|
54
|
+
unknown_keys="timeout_second",
|
|
55
|
+
accepted_keys="timeout_seconds",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
assert "timeout_second;" in formatted_message
|
|
59
|
+
assert formatted_message.endswith("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/`).
|