claude-dev-env 2.2.1 → 2.4.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 +3 -2
- package/_shared/advisor/advisor-protocol.md +2 -2
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -284
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +3 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -0
- package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +5 -0
- package/_shared/pr-loop/scripts/stale_worktree_rule_sweep.py +107 -0
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +2 -0
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +55 -73
- package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +9 -15
- package/_shared/pr-loop/scripts/tests/test_grant_project_claude_permissions.py +91 -1
- package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +88 -1
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -0
- package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -0
- package/_shared/pr-loop/worker-spawn.md +1 -1
- package/agents/CLAUDE.md +1 -0
- package/agents/code-verifier.md +4 -4
- package/agents/skill-writer-agent.md +84 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +141 -41
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
- package/docs/CLAUDE.md +1 -0
- package/docs/references/CLAUDE.md +1 -0
- package/docs/references/code-review-enforcement.md +97 -0
- package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
- package/hooks/blocking/CLAUDE.md +8 -1
- package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
- package/hooks/blocking/code_review_gate_deny.py +74 -0
- package/hooks/blocking/code_review_pr_create_gate.py +194 -0
- package/hooks/blocking/code_review_push_gate.py +140 -0
- package/hooks/blocking/code_review_stamp_directory_write_blocker.py +340 -0
- package/hooks/blocking/code_review_stamp_store.py +233 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
- package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
- package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
- package/hooks/blocking/config/code_review_enforcement_constants.py +110 -0
- package/hooks/blocking/config/test_code_review_enforcement_constants.py +108 -0
- package/hooks/blocking/config/verified_commit_constants.py +24 -9
- package/hooks/blocking/conftest.py +2 -0
- package/hooks/blocking/convergence_gate_blocker.py +112 -23
- package/hooks/blocking/destructive_command_blocker.py +19 -6
- package/hooks/blocking/pr_description_proof_of_work.py +52 -34
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
- package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
- package/hooks/blocking/test_code_review_gate_deny.py +54 -0
- package/hooks/blocking/test_code_review_pr_create_gate.py +185 -0
- package/hooks/blocking/test_code_review_push_gate.py +189 -0
- package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +180 -0
- package/hooks/blocking/test_code_review_stamp_store.py +205 -0
- package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
- package/hooks/blocking/test_code_verifier_tools_contract.py +28 -0
- package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
- package/hooks/blocking/test_destructive_command_blocker.py +1 -1
- package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
- package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +8 -8
- package/hooks/blocking/test_verification_verdict_store.py +920 -810
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
- package/hooks/blocking/verification_verdict_store.py +118 -5
- package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
- package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
- package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
- package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
- package/hooks/git-hooks/pre_push.py +89 -2
- package/hooks/git-hooks/test_pre_push.py +103 -0
- package/hooks/hooks.json +16 -1
- package/hooks/hooks_constants/CLAUDE.md +1 -0
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
- package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
- package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
- package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
- package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
- package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
- package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
- package/hooks/validators/CLAUDE.md +1 -0
- package/hooks/validators/mypy_integration.py +63 -50
- package/hooks/validators/pyproject_config_discovery.py +101 -0
- package/hooks/validators/ruff_integration.py +211 -23
- package/hooks/validators/run_all_validators.py +21 -14
- package/hooks/validators/test_mypy_integration.py +32 -0
- package/hooks/validators/test_pyproject_config_discovery.py +94 -0
- package/hooks/validators/test_ruff_integration.py +68 -1
- package/hooks/validators/test_run_all_validators.py +25 -0
- package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
- package/package.json +1 -1
- package/rules/docstring-prose-matches-implementation.md +20 -43
- package/rules/durable-post-artifacts.md +7 -0
- package/scripts/CLAUDE.md +2 -1
- package/scripts/claude-chain.example.json +15 -3
- package/scripts/claude_chain_runner.py +130 -27
- package/scripts/claude_chain_usage.py +346 -0
- package/scripts/codec_forwarding_test_support.py +83 -0
- package/scripts/conftest.py +8 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -3
- package/scripts/dev_env_scripts_constants/claude_chain_constants.py +57 -2
- package/scripts/dev_env_scripts_constants/claude_chain_usage_constants.py +58 -0
- package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
- package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
- package/scripts/dev_env_scripts_constants/timing.py +1 -1
- package/scripts/invoke_code_review.py +550 -38
- package/scripts/resolve_worker_spawn.py +8 -1
- package/scripts/test_claude_chain_runner.py +412 -6
- package/scripts/test_claude_chain_usage.py +534 -0
- package/scripts/test_invoke_code_review.py +298 -4
- package/scripts/test_invoke_code_review_codec.py +77 -0
- package/scripts/test_resolve_worker_spawn_codec.py +101 -0
- package/skills/autoconverge/SKILL.md +9 -3
- package/skills/autoconverge/reference/convergence.md +2 -1
- package/skills/autoconverge/reference/multi-pr.md +6 -1
- package/skills/autoconverge/reference/stop-conditions.md +16 -10
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
- package/skills/autoconverge/workflow/converge.contract.test.mjs +19 -0
- package/skills/autoconverge/workflow/converge.mjs +24 -8
- package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
- package/skills/fresh-branch/CLAUDE.md +2 -0
- package/skills/fresh-branch/SKILL.md +2 -0
- package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
- package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
- package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
- package/skills/fresh-branch/scripts/pytest.ini +4 -0
- package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
- package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
- package/skills/orchestrator/SKILL.md +1 -20
- package/skills/orchestrator-refresh/SKILL.md +1 -1
- package/skills/pr-converge/SKILL.md +3 -3
- package/skills/pr-converge/reference/examples.md +3 -3
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +3 -3
- package/skills/pr-converge/reference/per-tick.md +5 -5
- package/skills/pr-converge/reference/progress-checklist.md +1 -1
- package/skills/pr-converge/test_step5_host_branch.py +1 -1
- package/skills/prototype/SKILL.md +86 -0
- package/skills/prototype/reference/honest-limitations.md +23 -0
- package/skills/prototype/reference/promotion-tasks.md +23 -0
- package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
- package/skills/prototype/scripts/conftest.py +15 -0
- package/skills/prototype/scripts/launch_sandbox.py +205 -0
- package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
- package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
- package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
- package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
- package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
- package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
- package/skills/prototype/workflows/promotion.md +27 -0
- package/skills/prototype/workflows/sandbox.md +35 -0
- package/skills/skill-builder/CLAUDE.md +3 -3
- package/skills/skill-builder/SKILL.md +5 -5
- package/skills/skill-builder/references/CLAUDE.md +1 -1
- package/skills/skill-builder/references/delegation-map.md +3 -3
- package/skills/skill-builder/references/description-field.md +1 -1
- package/skills/skill-builder/references/skill-modularity.md +2 -3
- package/skills/skill-builder/workflows/CLAUDE.md +1 -1
- package/skills/skill-builder/workflows/improve-skill.md +1 -1
- package/skills/skill-builder/workflows/new-skill.md +2 -2
- package/skills/team-advisor/SKILL.md +5 -4
|
@@ -146,6 +146,21 @@ def build_repo_with_origin(workspace_path: Path) -> Path:
|
|
|
146
146
|
return clone_path
|
|
147
147
|
|
|
148
148
|
|
|
149
|
+
def read_branch_config(
|
|
150
|
+
repository_path: Path,
|
|
151
|
+
branch_name: str,
|
|
152
|
+
config_leaf: str,
|
|
153
|
+
) -> str:
|
|
154
|
+
completed = subprocess.run(
|
|
155
|
+
["git", "config", "--get", f"branch.{branch_name}.{config_leaf}"],
|
|
156
|
+
cwd=str(repository_path),
|
|
157
|
+
check=False,
|
|
158
|
+
capture_output=True,
|
|
159
|
+
text=True,
|
|
160
|
+
)
|
|
161
|
+
return completed.stdout.strip()
|
|
162
|
+
|
|
163
|
+
|
|
149
164
|
def read_head_branch(repository_path: Path) -> str:
|
|
150
165
|
completed = run_git(["rev-parse", "--abbrev-ref", "HEAD"], repository_path)
|
|
151
166
|
return completed.stdout.strip()
|
|
@@ -156,6 +171,14 @@ def read_head_commit(repository_path: Path) -> str:
|
|
|
156
171
|
return completed.stdout.strip()
|
|
157
172
|
|
|
158
173
|
|
|
174
|
+
def read_remote_main_commit(repository_path: Path) -> str:
|
|
175
|
+
completed = run_git(
|
|
176
|
+
["ls-remote", REMOTE_NAME, f"refs/heads/{MAIN_BRANCH_NAME}"],
|
|
177
|
+
repository_path,
|
|
178
|
+
)
|
|
179
|
+
return completed.stdout.split()[0]
|
|
180
|
+
|
|
181
|
+
|
|
159
182
|
class TestResolveAgentSlug:
|
|
160
183
|
def should_prefer_flag_over_environment(
|
|
161
184
|
self,
|
|
@@ -512,6 +535,81 @@ class TestCreateFreshBranchIntegration:
|
|
|
512
535
|
assert read_head_commit(worktree_path) != local_main_commit
|
|
513
536
|
|
|
514
537
|
|
|
538
|
+
class TestWorktreeBranchTracking:
|
|
539
|
+
def should_leave_new_branch_without_an_upstream(
|
|
540
|
+
self,
|
|
541
|
+
tmp_path: Path,
|
|
542
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
543
|
+
) -> None:
|
|
544
|
+
module = load_create_fresh_branch_module()
|
|
545
|
+
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
546
|
+
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
547
|
+
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
548
|
+
monkeypatch.setattr(
|
|
549
|
+
module.tempfile,
|
|
550
|
+
"gettempdir",
|
|
551
|
+
lambda: str(agent_scratch_parent),
|
|
552
|
+
)
|
|
553
|
+
success_payload = module.create_fresh_branch(
|
|
554
|
+
branch_name="fix/no-upstream",
|
|
555
|
+
repo_path=repository_path,
|
|
556
|
+
agent_slug="claude",
|
|
557
|
+
base_ref=DEFAULT_BASE_REF,
|
|
558
|
+
)
|
|
559
|
+
worktree_path = Path(success_payload[PAYLOAD_KEY_WORKTREE_PATH])
|
|
560
|
+
assert read_head_branch(worktree_path) == "fix/no-upstream"
|
|
561
|
+
assert read_branch_config(repository_path, "fix/no-upstream", "merge") == ""
|
|
562
|
+
assert read_branch_config(repository_path, "fix/no-upstream", "remote") == ""
|
|
563
|
+
|
|
564
|
+
def should_not_aim_a_bare_push_at_main_under_push_default_upstream(
|
|
565
|
+
self,
|
|
566
|
+
tmp_path: Path,
|
|
567
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
568
|
+
) -> None:
|
|
569
|
+
module = load_create_fresh_branch_module()
|
|
570
|
+
repository_path = build_repo_with_origin(tmp_path / "repo")
|
|
571
|
+
empty_hooks_path = tmp_path / "repo" / "empty-hooks"
|
|
572
|
+
run_git(
|
|
573
|
+
["config", "push.default", "upstream"],
|
|
574
|
+
repository_path,
|
|
575
|
+
empty_hooks_path=empty_hooks_path,
|
|
576
|
+
)
|
|
577
|
+
agent_scratch_parent = tmp_path / "agent-scratch"
|
|
578
|
+
monkeypatch.setattr(module.sys, "platform", "linux")
|
|
579
|
+
monkeypatch.setattr(
|
|
580
|
+
module.tempfile,
|
|
581
|
+
"gettempdir",
|
|
582
|
+
lambda: str(agent_scratch_parent),
|
|
583
|
+
)
|
|
584
|
+
success_payload = module.create_fresh_branch(
|
|
585
|
+
branch_name="feature/silent-push",
|
|
586
|
+
repo_path=repository_path,
|
|
587
|
+
agent_slug="claude",
|
|
588
|
+
base_ref=DEFAULT_BASE_REF,
|
|
589
|
+
)
|
|
590
|
+
worktree_path = Path(success_payload[PAYLOAD_KEY_WORKTREE_PATH])
|
|
591
|
+
(worktree_path / SEED_FILE_NAME).write_text("drive-by\n", encoding="utf-8")
|
|
592
|
+
run_git(["add", SEED_FILE_NAME], worktree_path, empty_hooks_path=empty_hooks_path)
|
|
593
|
+
run_git(
|
|
594
|
+
["commit", "-m", "work on the feature branch"],
|
|
595
|
+
worktree_path,
|
|
596
|
+
empty_hooks_path=empty_hooks_path,
|
|
597
|
+
)
|
|
598
|
+
origin_main_before = read_remote_main_commit(repository_path)
|
|
599
|
+
push_attempt = subprocess.run(
|
|
600
|
+
["git", "-c", f"core.hooksPath={empty_hooks_path}", "push"],
|
|
601
|
+
cwd=str(worktree_path),
|
|
602
|
+
check=False,
|
|
603
|
+
capture_output=True,
|
|
604
|
+
text=True,
|
|
605
|
+
)
|
|
606
|
+
assert push_attempt.returncode != 0, (
|
|
607
|
+
"bare push should be refused, but it succeeded:\n"
|
|
608
|
+
f"{push_attempt.stdout}{push_attempt.stderr}"
|
|
609
|
+
)
|
|
610
|
+
assert read_remote_main_commit(repository_path) == origin_main_before
|
|
611
|
+
|
|
612
|
+
|
|
515
613
|
class TestMainCli:
|
|
516
614
|
def should_print_success_json_and_exit_zero(
|
|
517
615
|
self,
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""Behavioral tests for fresh_branch_git_commands using real temporary git repos."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
SCRIPTS_DIRECTORY = Path(__file__).resolve().parent
|
|
12
|
+
if str(SCRIPTS_DIRECTORY) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(SCRIPTS_DIRECTORY))
|
|
14
|
+
|
|
15
|
+
from fresh_branch_git_commands import (
|
|
16
|
+
assert_git_accepts_branch_name,
|
|
17
|
+
build_worktree_add_arguments,
|
|
18
|
+
create_worktree_branch,
|
|
19
|
+
fetch_base_ref,
|
|
20
|
+
is_ref_present,
|
|
21
|
+
read_failure_text,
|
|
22
|
+
resolve_base_commit,
|
|
23
|
+
resolve_repo_root,
|
|
24
|
+
run_git,
|
|
25
|
+
split_remote_ref,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
GIT_USER_NAME = "fresh-branch-test"
|
|
29
|
+
GIT_USER_EMAIL = "fresh-branch-test@example.com"
|
|
30
|
+
MAIN_BRANCH_NAME = "main"
|
|
31
|
+
SEED_FILE_NAME = "README.md"
|
|
32
|
+
SEED_FILE_CONTENTS = "seed\n"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_repo(workspace_path: Path) -> Path:
|
|
36
|
+
workspace_path.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
empty_hooks_path = workspace_path / "empty-hooks"
|
|
38
|
+
empty_hooks_path.mkdir()
|
|
39
|
+
hooks_argument = ["-c", f"core.hooksPath={empty_hooks_path}"]
|
|
40
|
+
subprocess.run(
|
|
41
|
+
["git", *hooks_argument, "init", "-b", MAIN_BRANCH_NAME, str(workspace_path)],
|
|
42
|
+
check=True,
|
|
43
|
+
capture_output=True,
|
|
44
|
+
text=True,
|
|
45
|
+
)
|
|
46
|
+
for each_pair in (
|
|
47
|
+
("user.name", GIT_USER_NAME),
|
|
48
|
+
("user.email", GIT_USER_EMAIL),
|
|
49
|
+
("commit.gpgsign", "false"),
|
|
50
|
+
):
|
|
51
|
+
subprocess.run(
|
|
52
|
+
["git", *hooks_argument, "config", *each_pair],
|
|
53
|
+
cwd=str(workspace_path),
|
|
54
|
+
check=True,
|
|
55
|
+
capture_output=True,
|
|
56
|
+
text=True,
|
|
57
|
+
)
|
|
58
|
+
(workspace_path / SEED_FILE_NAME).write_text(SEED_FILE_CONTENTS, encoding="utf-8")
|
|
59
|
+
subprocess.run(
|
|
60
|
+
["git", *hooks_argument, "add", SEED_FILE_NAME],
|
|
61
|
+
cwd=str(workspace_path),
|
|
62
|
+
check=True,
|
|
63
|
+
capture_output=True,
|
|
64
|
+
text=True,
|
|
65
|
+
)
|
|
66
|
+
subprocess.run(
|
|
67
|
+
["git", *hooks_argument, "commit", "-m", "initial commit"],
|
|
68
|
+
cwd=str(workspace_path),
|
|
69
|
+
check=True,
|
|
70
|
+
capture_output=True,
|
|
71
|
+
text=True,
|
|
72
|
+
)
|
|
73
|
+
return workspace_path
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_clone_with_origin(workspace_path: Path) -> tuple[Path, Path]:
|
|
77
|
+
seed_path = build_repo(workspace_path / "seed")
|
|
78
|
+
empty_hooks_path = seed_path / "empty-hooks"
|
|
79
|
+
hooks_argument = ["-c", f"core.hooksPath={empty_hooks_path}"]
|
|
80
|
+
bare_origin = workspace_path / "origin.git"
|
|
81
|
+
subprocess.run(
|
|
82
|
+
["git", *hooks_argument, "clone", "--bare", str(seed_path), str(bare_origin)],
|
|
83
|
+
check=True,
|
|
84
|
+
capture_output=True,
|
|
85
|
+
text=True,
|
|
86
|
+
)
|
|
87
|
+
clone_path = workspace_path / "clone"
|
|
88
|
+
subprocess.run(
|
|
89
|
+
["git", *hooks_argument, "clone", str(bare_origin), str(clone_path)],
|
|
90
|
+
check=True,
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
)
|
|
94
|
+
for each_pair in (
|
|
95
|
+
("user.name", GIT_USER_NAME),
|
|
96
|
+
("user.email", GIT_USER_EMAIL),
|
|
97
|
+
("commit.gpgsign", "false"),
|
|
98
|
+
):
|
|
99
|
+
subprocess.run(
|
|
100
|
+
["git", *hooks_argument, "config", *each_pair],
|
|
101
|
+
cwd=str(clone_path),
|
|
102
|
+
check=True,
|
|
103
|
+
capture_output=True,
|
|
104
|
+
text=True,
|
|
105
|
+
)
|
|
106
|
+
return clone_path, bare_origin
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def advance_origin_main(workspace_path: Path, bare_origin: Path) -> str:
|
|
110
|
+
pusher_path = workspace_path / "pusher"
|
|
111
|
+
empty_hooks_path = workspace_path / "seed" / "empty-hooks"
|
|
112
|
+
hooks_argument = ["-c", f"core.hooksPath={empty_hooks_path}"]
|
|
113
|
+
subprocess.run(
|
|
114
|
+
["git", *hooks_argument, "clone", str(bare_origin), str(pusher_path)],
|
|
115
|
+
check=True,
|
|
116
|
+
capture_output=True,
|
|
117
|
+
text=True,
|
|
118
|
+
)
|
|
119
|
+
for each_pair in (
|
|
120
|
+
("user.name", GIT_USER_NAME),
|
|
121
|
+
("user.email", GIT_USER_EMAIL),
|
|
122
|
+
("commit.gpgsign", "false"),
|
|
123
|
+
):
|
|
124
|
+
subprocess.run(
|
|
125
|
+
["git", *hooks_argument, "config", *each_pair],
|
|
126
|
+
cwd=str(pusher_path),
|
|
127
|
+
check=True,
|
|
128
|
+
capture_output=True,
|
|
129
|
+
text=True,
|
|
130
|
+
)
|
|
131
|
+
(pusher_path / SEED_FILE_NAME).write_text("advanced\n", encoding="utf-8")
|
|
132
|
+
subprocess.run(
|
|
133
|
+
["git", *hooks_argument, "commit", "-am", "advance main"],
|
|
134
|
+
cwd=str(pusher_path),
|
|
135
|
+
check=True,
|
|
136
|
+
capture_output=True,
|
|
137
|
+
text=True,
|
|
138
|
+
)
|
|
139
|
+
subprocess.run(
|
|
140
|
+
["git", *hooks_argument, "push", "origin", MAIN_BRANCH_NAME],
|
|
141
|
+
cwd=str(pusher_path),
|
|
142
|
+
check=True,
|
|
143
|
+
capture_output=True,
|
|
144
|
+
text=True,
|
|
145
|
+
)
|
|
146
|
+
completed = subprocess.run(
|
|
147
|
+
["git", "rev-parse", "HEAD"],
|
|
148
|
+
cwd=str(pusher_path),
|
|
149
|
+
check=True,
|
|
150
|
+
capture_output=True,
|
|
151
|
+
text=True,
|
|
152
|
+
)
|
|
153
|
+
return completed.stdout.strip()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def read_branch_config(
|
|
157
|
+
repository_path: Path,
|
|
158
|
+
branch_name: str,
|
|
159
|
+
config_leaf: str,
|
|
160
|
+
) -> str:
|
|
161
|
+
completed = subprocess.run(
|
|
162
|
+
["git", "config", "--get", f"branch.{branch_name}.{config_leaf}"],
|
|
163
|
+
cwd=str(repository_path),
|
|
164
|
+
check=False,
|
|
165
|
+
capture_output=True,
|
|
166
|
+
text=True,
|
|
167
|
+
)
|
|
168
|
+
return completed.stdout.strip()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class TestBuildWorktreeAddArguments:
|
|
172
|
+
def test_pass_no_track_so_the_new_branch_gets_no_upstream(self) -> None:
|
|
173
|
+
all_arguments = build_worktree_add_arguments(
|
|
174
|
+
"fix/x",
|
|
175
|
+
Path("/tmp/agent/fix/x"),
|
|
176
|
+
"origin/main",
|
|
177
|
+
)
|
|
178
|
+
assert "--no-track" in all_arguments
|
|
179
|
+
|
|
180
|
+
def test_order_arguments_the_way_git_worktree_add_expects(self) -> None:
|
|
181
|
+
all_arguments = build_worktree_add_arguments(
|
|
182
|
+
"fix/x",
|
|
183
|
+
Path("/tmp/agent/fix/x"),
|
|
184
|
+
"origin/main",
|
|
185
|
+
)
|
|
186
|
+
assert all_arguments[:4] == ["worktree", "add", "-b", "fix/x"]
|
|
187
|
+
assert all_arguments[-1] == "origin/main"
|
|
188
|
+
assert all_arguments[-2] == str(Path("/tmp/agent/fix/x"))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class TestSplitRemoteRef:
|
|
192
|
+
def test_split_an_origin_ref(self) -> None:
|
|
193
|
+
assert split_remote_ref("origin/main") == ("origin", "main")
|
|
194
|
+
|
|
195
|
+
def test_split_a_non_origin_remote_ref(self) -> None:
|
|
196
|
+
assert split_remote_ref("upstream/dev") == ("upstream", "dev")
|
|
197
|
+
|
|
198
|
+
def test_default_a_bare_name_to_origin(self) -> None:
|
|
199
|
+
assert split_remote_ref("main") == ("origin", "main")
|
|
200
|
+
|
|
201
|
+
def test_keep_slashes_inside_the_branch_name(self) -> None:
|
|
202
|
+
assert split_remote_ref("origin/fix/nested") == ("origin", "fix/nested")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class TestAssertGitAcceptsBranchName:
|
|
206
|
+
def test_accept_a_conventional_branch_name(self) -> None:
|
|
207
|
+
assert assert_git_accepts_branch_name("fix/example-one") is None
|
|
208
|
+
|
|
209
|
+
def test_reject_a_name_git_refuses(self) -> None:
|
|
210
|
+
with pytest.raises(ValueError, match="relative path"):
|
|
211
|
+
assert_git_accepts_branch_name("fix..example")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class TestResolveRepoRoot:
|
|
215
|
+
def test_return_the_toplevel_for_a_nested_path(self, tmp_path: Path) -> None:
|
|
216
|
+
repository_path = build_repo(tmp_path / "repo")
|
|
217
|
+
nested_path = repository_path / "nested"
|
|
218
|
+
nested_path.mkdir()
|
|
219
|
+
assert resolve_repo_root(nested_path).resolve() == repository_path.resolve()
|
|
220
|
+
|
|
221
|
+
def test_raise_outside_a_repository(self, tmp_path: Path) -> None:
|
|
222
|
+
plain_directory = tmp_path / "plain"
|
|
223
|
+
plain_directory.mkdir()
|
|
224
|
+
with pytest.raises(RuntimeError, match="git repository"):
|
|
225
|
+
resolve_repo_root(plain_directory)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TestIsRefPresent:
|
|
229
|
+
def test_find_a_local_branch(self, tmp_path: Path) -> None:
|
|
230
|
+
repository_path = build_repo(tmp_path / "repo")
|
|
231
|
+
assert is_ref_present(repository_path, MAIN_BRANCH_NAME) is True
|
|
232
|
+
|
|
233
|
+
def test_not_find_an_absent_ref(self, tmp_path: Path) -> None:
|
|
234
|
+
repository_path = build_repo(tmp_path / "repo")
|
|
235
|
+
assert is_ref_present(repository_path, "origin/nowhere") is False
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class TestReadFailureText:
|
|
239
|
+
def test_prefer_stderr(self, tmp_path: Path) -> None:
|
|
240
|
+
repository_path = build_repo(tmp_path / "repo")
|
|
241
|
+
completed = run_git(["rev-parse", "does-not-exist"], repository_path)
|
|
242
|
+
assert completed.returncode != 0
|
|
243
|
+
assert "does-not-exist" in read_failure_text(completed)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class TestFetchBaseRef:
|
|
247
|
+
def test_bring_the_remote_tip_into_the_clone(self, tmp_path: Path) -> None:
|
|
248
|
+
clone_path, bare_origin = build_clone_with_origin(tmp_path / "workspace")
|
|
249
|
+
advanced_commit = advance_origin_main(tmp_path / "workspace", bare_origin)
|
|
250
|
+
fetch_base_ref(clone_path, "origin/main")
|
|
251
|
+
assert is_ref_present(clone_path, "origin/main") is True
|
|
252
|
+
assert resolve_base_commit(clone_path, "origin/main") == advanced_commit
|
|
253
|
+
|
|
254
|
+
def test_raise_for_a_remote_branch_that_does_not_exist(
|
|
255
|
+
self,
|
|
256
|
+
tmp_path: Path,
|
|
257
|
+
) -> None:
|
|
258
|
+
clone_path, _bare_origin = build_clone_with_origin(tmp_path / "workspace")
|
|
259
|
+
with pytest.raises(RuntimeError, match="git fetch failed"):
|
|
260
|
+
fetch_base_ref(clone_path, "origin/nowhere")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class TestResolveBaseCommit:
|
|
264
|
+
def test_return_the_sha_at_the_ref(self, tmp_path: Path) -> None:
|
|
265
|
+
repository_path = build_repo(tmp_path / "repo")
|
|
266
|
+
head_commit = run_git(["rev-parse", "HEAD"], repository_path).stdout.strip()
|
|
267
|
+
assert resolve_base_commit(repository_path, MAIN_BRANCH_NAME) == head_commit
|
|
268
|
+
|
|
269
|
+
def test_raise_for_an_unresolvable_ref(self, tmp_path: Path) -> None:
|
|
270
|
+
repository_path = build_repo(tmp_path / "repo")
|
|
271
|
+
with pytest.raises(RuntimeError, match="could not resolve base commit"):
|
|
272
|
+
resolve_base_commit(repository_path, "refs/heads/nowhere")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class TestCreateWorktreeBranch:
|
|
276
|
+
def test_create_the_branch_at_the_base_ref_with_no_upstream(
|
|
277
|
+
self,
|
|
278
|
+
tmp_path: Path,
|
|
279
|
+
) -> None:
|
|
280
|
+
clone_path, _bare_origin = build_clone_with_origin(tmp_path / "workspace")
|
|
281
|
+
origin_tip = resolve_base_commit(clone_path, "origin/main")
|
|
282
|
+
worktree_path = tmp_path / "worktrees" / "fix" / "created"
|
|
283
|
+
create_worktree_branch(
|
|
284
|
+
clone_path,
|
|
285
|
+
branch_name="fix/created",
|
|
286
|
+
worktree_path=worktree_path,
|
|
287
|
+
base_ref="origin/main",
|
|
288
|
+
)
|
|
289
|
+
assert worktree_path.is_dir()
|
|
290
|
+
assert resolve_base_commit(worktree_path, "HEAD") == origin_tip
|
|
291
|
+
assert read_branch_config(clone_path, "fix/created", "merge") == ""
|
|
292
|
+
assert read_branch_config(clone_path, "fix/created", "remote") == ""
|
|
293
|
+
|
|
294
|
+
def test_raise_when_the_branch_already_exists(self, tmp_path: Path) -> None:
|
|
295
|
+
clone_path, _bare_origin = build_clone_with_origin(tmp_path / "workspace")
|
|
296
|
+
first_path = tmp_path / "worktrees" / "dup-one"
|
|
297
|
+
second_path = tmp_path / "worktrees" / "dup-two"
|
|
298
|
+
create_worktree_branch(
|
|
299
|
+
clone_path,
|
|
300
|
+
branch_name="fix/dup",
|
|
301
|
+
worktree_path=first_path,
|
|
302
|
+
base_ref="origin/main",
|
|
303
|
+
)
|
|
304
|
+
with pytest.raises(RuntimeError, match="worktree add failed"):
|
|
305
|
+
create_worktree_branch(
|
|
306
|
+
clone_path,
|
|
307
|
+
branch_name="fix/dup",
|
|
308
|
+
worktree_path=second_path,
|
|
309
|
+
base_ref="origin/main",
|
|
310
|
+
)
|
|
@@ -30,32 +30,13 @@ The moment it edits a file or runs a test itself, the pairing breaks —
|
|
|
30
30
|
its own tool use stays orchestration, run-artifact writes, and light
|
|
31
31
|
verification reads.
|
|
32
32
|
|
|
33
|
-
## Design rule — thin prompts, thick artifacts, thin skill
|
|
34
|
-
|
|
35
|
-
Three faces of one rule (reading pinned by the audit spec on
|
|
36
|
-
jl-cmd/claude-dev-env#174: thin means high-signal, not short; thick
|
|
37
|
-
context lives outside the window, reachable by pointers):
|
|
38
|
-
|
|
39
|
-
- **Thin prompts.** A spawn prompt is a dispatch ticket: one task,
|
|
40
|
-
pointers to the artifacts that hold the context, one done-check, a thin
|
|
41
|
-
return contract. The prompt can be thin because the context is thick
|
|
42
|
-
elsewhere.
|
|
43
|
-
- **Thick artifacts.** Durable run state lives in files — the run
|
|
44
|
-
charter, one assignment file per task, result records — written once,
|
|
45
|
-
read by every agent that needs them, and still there when any one
|
|
46
|
-
agent's context is gone.
|
|
47
|
-
- **Thin skill.** This file holds only what changes decisions at
|
|
48
|
-
orchestration time. Standing policy lives where it loads once:
|
|
49
|
-
[`_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md)
|
|
50
|
-
for everything advisor, the agent definitions for executor discipline.
|
|
51
|
-
|
|
52
33
|
## Process
|
|
53
34
|
|
|
54
35
|
1. **Invocation guard.** One `/orchestrator` per session. When the
|
|
55
36
|
refresh loop is already running, do not schedule a second one; reuse
|
|
56
37
|
the live advisor bind and go to step 4.
|
|
57
38
|
2. **Register the discipline reminder.** Schedule it with
|
|
58
|
-
`ScheduleWakeup` at `delaySeconds:
|
|
39
|
+
`ScheduleWakeup` at `delaySeconds: 2700`, prompt
|
|
59
40
|
`/orchestrator-refresh`, where each refresh re-schedules the next one.
|
|
60
41
|
3. **Bind the shared advisor before any executor.** Follow
|
|
61
42
|
[`_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orchestrator-refresh
|
|
3
3
|
description: >-
|
|
4
|
-
Fired by the /orchestrator loop reminder about every
|
|
4
|
+
Fired by the /orchestrator loop reminder about every 45 minutes to
|
|
5
5
|
re-assert the advisor discipline mid-run: orchestrate, route hard decisions
|
|
6
6
|
to the shared advisor (ENDORSE / CORRECTION / PLAN / STOP — SendMessage on
|
|
7
7
|
Claude, Claude CLI chain on a third-party host), reuse warm agents. Triggers:
|
|
@@ -195,7 +195,7 @@ the `persistent_agents` map
|
|
|
195
195
|
record it, and continue the tick. Never abort a tick on a stale id;
|
|
196
196
|
never retry the same dead id.
|
|
197
197
|
- **Fresh every round (never persisted):** the Step 5 host-aware
|
|
198
|
-
`invoke_code_review.py` / `/code-review
|
|
198
|
+
`invoke_code_review.py` / `/code-review xhigh --fix` pass and the Step 6
|
|
199
199
|
bugteam audit (unbiased eyes each round; the enforcer needs the formal
|
|
200
200
|
Skill call), and every `code-verifier` — a named code-verifier never fires
|
|
201
201
|
`SubagentStop`, so no verdict mints (see the named-`code-verifier` entry
|
|
@@ -253,7 +253,7 @@ post a fresh PR in a fresh branch based on origin main to the user.
|
|
|
253
253
|
Windows is Git Bash which cannot execute PowerShell cmdlets. Route all
|
|
254
254
|
PowerShell work through the PowerShell tool or `pwsh -NoProfile -File`.
|
|
255
255
|
- **Cross-repo PR: route cwd into the PR worktree before Step 5 review** —
|
|
256
|
-
`invoke_code_review.py` and `/code-review
|
|
256
|
+
`invoke_code_review.py` and `/code-review xhigh --fix` audit the repo of the
|
|
257
257
|
cwd (the helper's `--cwd`). When the session is rooted in a different repo
|
|
258
258
|
than the PR, `EnterWorktree` cannot re-root (it is scoped to the session's
|
|
259
259
|
repo); resolve the PR worktree and `cd` into it per
|
|
@@ -351,7 +351,7 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
351
351
|
- [ ] **Static sweep fails** → apply shared fix protocol → push → reset markers
|
|
352
352
|
→ stay CODE_REVIEW → Step 5
|
|
353
353
|
- [ ] **`mode == in_session`** (Claude host, session model opus) → run
|
|
354
|
-
`/code-review
|
|
354
|
+
`/code-review xhigh --fix` in-session (no path args)
|
|
355
355
|
- [ ] **`mode == chain`** (any other host or non-opus session) → helper
|
|
356
356
|
already ran the headless review; read `returncode`,
|
|
357
357
|
`served_command`, and `dirty_tree` from JSON
|
|
@@ -18,7 +18,7 @@ resets push-invalidated markers per [ground-rules.md](ground-rules.md) /
|
|
|
18
18
|
at 270s, re-runs the sweep next tick]
|
|
19
19
|
</example>
|
|
20
20
|
|
|
21
|
-
<example> CODE_REVIEW tick, static sweep clean, `/code-review
|
|
21
|
+
<example> CODE_REVIEW tick, static sweep clean, `/code-review xhigh --fix`
|
|
22
22
|
applies fixes to the working tree. Claude: [commits the applied fixes in one
|
|
23
23
|
commit, pushes, resets push-invalidated markers per
|
|
24
24
|
[ground-rules.md](ground-rules.md) / [state-schema.md](state-schema.md) (all
|
|
@@ -26,7 +26,7 @@ commit, pushes, resets push-invalidated markers per
|
|
|
26
26
|
stays `phase = CODE_REVIEW`, Step 4 at 270s, returns]
|
|
27
27
|
</example>
|
|
28
28
|
|
|
29
|
-
<example> CODE_REVIEW tick, static sweep clean and `/code-review
|
|
29
|
+
<example> CODE_REVIEW tick, static sweep clean and `/code-review xhigh --fix`
|
|
30
30
|
clean (no changes applied). Claude: [sets `code_review_clean_at = HEAD`,
|
|
31
31
|
`phase = BUGTEAM`, runs `Skill({skill: "bugteam", ...})` in same tick]
|
|
32
32
|
</example>
|
|
@@ -94,7 +94,7 @@ applies **Convergence** from `workflows/schedule-wakeup-loop.md`]
|
|
|
94
94
|
|
|
95
95
|
<example> CODE_REVIEW tick, review body says "found 3 potential issues"
|
|
96
96
|
against HEAD (a stale prior finding) but the diff is clean. Claude: [the static
|
|
97
|
-
sweep and `/code-review
|
|
97
|
+
sweep and `/code-review xhigh --fix` both pass, sets `code_review_clean_at =
|
|
98
98
|
HEAD`, `phase = BUGTEAM`]
|
|
99
99
|
</example>
|
|
100
100
|
|
|
@@ -32,6 +32,6 @@ files during fix phase in multi-PR mode.
|
|
|
32
32
|
regardless of phase. A new commit **resets the full convergence cycle**: a
|
|
33
33
|
code-review clean, a bugteam clean, and a Bugbot clean on an older SHA do
|
|
34
34
|
**not** count toward convergence on the new `HEAD`. Re-run the static sweep and
|
|
35
|
-
`/code-review
|
|
35
|
+
`/code-review xhigh --fix` on `current_head`, then bugteam, then the terminal
|
|
36
36
|
Bugbot gate, all on the same `HEAD` with no intervening push. Re-entering in the
|
|
37
37
|
same tick saves a wakeup cycle.
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
- **All `*_clean_at`, `merge_state_status`, and `bugbot_down` / `codex_down` reset on every push.**
|
|
11
11
|
- **`bugbot run` comment is load-bearing.** Literal phrase exactly —
|
|
12
12
|
empirically the only re-trigger Cursor Bugbot recognizes.
|
|
13
|
-
- **Production edits go through `clean-coder`, except `/code-review
|
|
13
|
+
- **Production edits go through `clean-coder`, except `/code-review xhigh --fix`.**
|
|
14
14
|
The lead never hand-edits production files. Every bugbot, bugteam,
|
|
15
15
|
Copilot, or Claude finding spawns `Agent(subagent_type="clean-coder")` to
|
|
16
16
|
apply the fix. The CODE_REVIEW phase is the one exception: `/code-review
|
|
17
|
-
|
|
17
|
+
xhigh --fix` applies its own findings to the working tree, which the next
|
|
18
18
|
CODE_REVIEW/BUGTEAM cycle re-reviews after the loop resets.
|
|
19
19
|
- **Adapt when reality contradicts on-disk state.** If `state.json`,
|
|
20
20
|
`git`, or `gh` disagree with live PR, escalate as hard blocker per
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- **Cross-repo cwd routing is routine, not a fork.** When the PR under
|
|
23
23
|
convergence lives in a different repo than the session is rooted in, route
|
|
24
24
|
the working directory into a checkout of the PR's repo automatically —
|
|
25
|
-
`/code-review
|
|
25
|
+
`/code-review xhigh --fix`, `git`, and every `clean-coder` fix spawn act on the
|
|
26
26
|
repo of the current working directory. The resolution is fixed
|
|
27
27
|
([per-tick.md § Step 1.5](per-tick.md)): resolve the PR worktree, `cd` into
|
|
28
28
|
it, run local work there. Do not pause, ask, or raise it as a material fork.
|
|
@@ -71,7 +71,7 @@ Capture `number`, `head.sha` (= `current_head`), owner/repo, branch.
|
|
|
71
71
|
|
|
72
72
|
The **PR worktree** is the local working tree of the PR's repo on its head
|
|
73
73
|
branch. Every local operation this tick runs there: the CODE_REVIEW static sweep
|
|
74
|
-
and `/code-review
|
|
74
|
+
and `/code-review xhigh --fix`, every `clean-coder` fix spawn, and every commit and
|
|
75
75
|
push. `/code-review` and `git` both act on the repo of the current working
|
|
76
76
|
directory, so the working directory must be the PR worktree before any local
|
|
77
77
|
work begins. Re-resolve it every tick — a rebase or a fresh HEAD can move the
|
|
@@ -180,7 +180,7 @@ CODE_REVIEW.
|
|
|
180
180
|
### `phase == CODE_REVIEW`
|
|
181
181
|
|
|
182
182
|
The entry phase of every convergence tick, re-entered after any fix push. It runs
|
|
183
|
-
a deterministic static sweep, then the built-in `/code-review
|
|
183
|
+
a deterministic static sweep, then the built-in `/code-review xhigh --fix` on the
|
|
184
184
|
full `origin/main...HEAD` diff at effort high on model opus. `/code-review`
|
|
185
185
|
produces no GitHub review artifact, so there are no code-review threads to
|
|
186
186
|
resolve.
|
|
@@ -196,7 +196,7 @@ a. **Static sweep — runs first, before `/code-review`.** Run the deterministic
|
|
|
196
196
|
`phase = CODE_REVIEW`, and re-run the sweep. When the sweep is clean, run
|
|
197
197
|
the host-aware review below.
|
|
198
198
|
|
|
199
|
-
b. Run the built-in `/code-review
|
|
199
|
+
b. Run the built-in `/code-review xhigh --fix` with OPUS on the FULL `origin/main...HEAD`
|
|
200
200
|
diff — every file the PR touches — via the
|
|
201
201
|
[local diff review](https://code.claude.com/docs/en/code-review#review-a-diff-locally).
|
|
202
202
|
The review always runs at effort high on model opus. It reviews the diff and
|
|
@@ -231,14 +231,14 @@ b. Run the built-in `/code-review high --fix` on the FULL `origin/main...HEAD`
|
|
|
231
231
|
Match the first mode whose predicate holds:
|
|
232
232
|
|
|
233
233
|
- **`mode == "in_session"`** (Claude host and session model is opus): run
|
|
234
|
-
`/code-review
|
|
234
|
+
`/code-review xhigh --fix` with OPUS in this session with no path arguments so it
|
|
235
235
|
audits the whole branch diff against `origin/main`. After it returns, a
|
|
236
236
|
non-empty `git status --porcelain` means fixes applied (`dirty_tree`
|
|
237
237
|
equivalent). Treat a failed in-session slash command the same as a failed
|
|
238
238
|
review: do not set `code_review_clean_at`.
|
|
239
239
|
- **`mode == "chain"`** (any other host, or a Claude session on any model
|
|
240
240
|
other than opus): the helper already ran the headless review
|
|
241
|
-
(`claude -p "/code-review
|
|
241
|
+
(`claude -p "/code-review xhigh --fix" --model opus` through the chain
|
|
242
242
|
runner) with cwd set to the PR worktree. Read `returncode`,
|
|
243
243
|
`served_command`, and `dirty_tree` from the JSON. A successful serve is
|
|
244
244
|
`returncode == 0` with a non-null `served_command`. `dirty_tree` true
|
|
@@ -91,7 +91,7 @@ round as converged. This rule holds every tick, every loop, every PR.
|
|
|
91
91
|
- [ ] **Static sweep fails** → apply shared fix protocol → push → reset markers
|
|
92
92
|
→ stay CODE_REVIEW → Step 5
|
|
93
93
|
- [ ] **`mode == in_session`** (Claude host, session model opus) → run
|
|
94
|
-
`/code-review
|
|
94
|
+
`/code-review xhigh --fix` in-session (no path args)
|
|
95
95
|
- [ ] **`mode == chain`** (any other host or non-opus session) → helper
|
|
96
96
|
already ran the headless review; read `returncode`,
|
|
97
97
|
`served_command`, and `dirty_tree` from JSON
|
|
@@ -32,7 +32,7 @@ NEVER_PUSHES_PHRASE = "never pushes"
|
|
|
32
32
|
EMPTY_STDIN_PHRASE = "empty"
|
|
33
33
|
CWD_FLAG = "--cwd"
|
|
34
34
|
OPUS_MODEL = "opus"
|
|
35
|
-
HIGH_EFFORT_SLASH = "/code-review
|
|
35
|
+
HIGH_EFFORT_SLASH = "/code-review xhigh --fix"
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def _read_markdown(markdown_path: Path) -> str:
|