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
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Grok medium-review discovery and verification orchestration.
|
|
3
|
+
|
|
4
|
+
Dispatches exactly eight named finder angles at one target head, binds each
|
|
5
|
+
finder to an isolated identity (worktree path, leader socket, advisor session),
|
|
6
|
+
deduplicates candidates, verifies them, and retains CONFIRMED plus unresolved
|
|
7
|
+
PLAUSIBLE findings with severity. Head drift rejects the whole batch.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
|
|
14
|
+
from e_code_review_scripts_constants.grok_code_review_constants import (
|
|
15
|
+
ALL_MEDIUM_FINDER_ANGLES,
|
|
16
|
+
ALL_SEVERITIES,
|
|
17
|
+
ALL_VERIFICATION_VERDICTS,
|
|
18
|
+
MEDIUM_REVIEW_FINDER_COUNT,
|
|
19
|
+
MEDIUM_REVIEW_SCHEMA_VERSION,
|
|
20
|
+
VERDICT_CONFIRMED,
|
|
21
|
+
VERDICT_PLAUSIBLE,
|
|
22
|
+
VERDICT_REFUTED,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class FinderCandidate:
|
|
28
|
+
"""One candidate finding from a discovery angle."""
|
|
29
|
+
|
|
30
|
+
angle: str
|
|
31
|
+
file_path: str
|
|
32
|
+
line_number: int
|
|
33
|
+
mechanism: str
|
|
34
|
+
scenario: str
|
|
35
|
+
worktree_path: str
|
|
36
|
+
leader_socket: str
|
|
37
|
+
advisor_session_id: str
|
|
38
|
+
reviewed_head: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class VerifiedFinding:
|
|
43
|
+
"""A retained finding after verification."""
|
|
44
|
+
|
|
45
|
+
file_path: str
|
|
46
|
+
line_number: int
|
|
47
|
+
mechanism: str
|
|
48
|
+
scenario: str
|
|
49
|
+
verdict: str
|
|
50
|
+
severity: str
|
|
51
|
+
reviewed_head: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class MediumReviewBatch:
|
|
56
|
+
"""Result of one medium-review run at a single head."""
|
|
57
|
+
|
|
58
|
+
schema_version: str = MEDIUM_REVIEW_SCHEMA_VERSION
|
|
59
|
+
target_head: str = ""
|
|
60
|
+
diff_base: str = ""
|
|
61
|
+
all_finder_angles: tuple[str, ...] = ALL_MEDIUM_FINDER_ANGLES
|
|
62
|
+
all_retained_findings: list[VerifiedFinding] = field(default_factory=list)
|
|
63
|
+
is_rejected: bool = False
|
|
64
|
+
rejection_reason: str | None = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def require_exact_finder_set(all_angles: tuple[str, ...] | list[str]) -> None:
|
|
68
|
+
"""Require exactly the eight named finder angles.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
all_angles: Angle names present in a run.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ValueError: When the set is not exactly the eight angles.
|
|
75
|
+
"""
|
|
76
|
+
angle_set = set(all_angles)
|
|
77
|
+
expected = set(ALL_MEDIUM_FINDER_ANGLES)
|
|
78
|
+
if angle_set != expected or len(all_angles) != MEDIUM_REVIEW_FINDER_COUNT:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"finder angles must be exactly {MEDIUM_REVIEW_FINDER_COUNT}: "
|
|
81
|
+
f"got {sorted(angle_set)}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def deduplicate_candidates(
|
|
86
|
+
all_candidates: list[FinderCandidate],
|
|
87
|
+
) -> list[FinderCandidate]:
|
|
88
|
+
"""Keep the most concrete scenario per file/line/mechanism.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
all_candidates: Raw finder outputs.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Deduplicated candidates preserving the longest scenario text.
|
|
95
|
+
"""
|
|
96
|
+
best_by_key: dict[tuple[str, int, str], FinderCandidate] = {}
|
|
97
|
+
for each_candidate in all_candidates:
|
|
98
|
+
key = (
|
|
99
|
+
each_candidate.file_path,
|
|
100
|
+
each_candidate.line_number,
|
|
101
|
+
each_candidate.mechanism,
|
|
102
|
+
)
|
|
103
|
+
existing = best_by_key.get(key)
|
|
104
|
+
if existing is None or len(each_candidate.scenario) > len(existing.scenario):
|
|
105
|
+
best_by_key[key] = each_candidate
|
|
106
|
+
return list(best_by_key.values())
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def retain_verified_findings(
|
|
110
|
+
*,
|
|
111
|
+
all_candidates: list[FinderCandidate],
|
|
112
|
+
verdict_by_key: dict[tuple[str, int, str], str],
|
|
113
|
+
severity_by_key: dict[tuple[str, int, str], str],
|
|
114
|
+
) -> list[VerifiedFinding]:
|
|
115
|
+
"""Retain CONFIRMED and unresolved PLAUSIBLE findings with severity.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
all_candidates: Deduplicated candidates.
|
|
119
|
+
verdict_by_key: Verification verdict per file/line/mechanism.
|
|
120
|
+
severity_by_key: Severity per file/line/mechanism.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Retained findings.
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
ValueError: When a retained finding lacks a legal severity or verdict.
|
|
127
|
+
"""
|
|
128
|
+
all_retained: list[VerifiedFinding] = []
|
|
129
|
+
for each_candidate in all_candidates:
|
|
130
|
+
key = (
|
|
131
|
+
each_candidate.file_path,
|
|
132
|
+
each_candidate.line_number,
|
|
133
|
+
each_candidate.mechanism,
|
|
134
|
+
)
|
|
135
|
+
verdict = verdict_by_key.get(key)
|
|
136
|
+
if verdict not in ALL_VERIFICATION_VERDICTS:
|
|
137
|
+
raise ValueError(f"missing or illegal verdict for {key}")
|
|
138
|
+
if verdict == VERDICT_REFUTED:
|
|
139
|
+
continue
|
|
140
|
+
if verdict not in {VERDICT_CONFIRMED, VERDICT_PLAUSIBLE}:
|
|
141
|
+
continue
|
|
142
|
+
severity = severity_by_key.get(key)
|
|
143
|
+
if severity not in ALL_SEVERITIES:
|
|
144
|
+
raise ValueError(f"missing or illegal severity for {key}")
|
|
145
|
+
all_retained.append(
|
|
146
|
+
VerifiedFinding(
|
|
147
|
+
file_path=each_candidate.file_path,
|
|
148
|
+
line_number=each_candidate.line_number,
|
|
149
|
+
mechanism=each_candidate.mechanism,
|
|
150
|
+
scenario=each_candidate.scenario,
|
|
151
|
+
verdict=verdict,
|
|
152
|
+
severity=severity,
|
|
153
|
+
reviewed_head=each_candidate.reviewed_head,
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
return all_retained
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def run_medium_review(
|
|
160
|
+
*,
|
|
161
|
+
target_head: str,
|
|
162
|
+
diff_base: str,
|
|
163
|
+
all_finder_candidates: list[FinderCandidate],
|
|
164
|
+
verdict_by_key: dict[tuple[str, int, str], str],
|
|
165
|
+
severity_by_key: dict[tuple[str, int, str], str],
|
|
166
|
+
live_head: str,
|
|
167
|
+
is_any_advisor_blocked: bool = False,
|
|
168
|
+
) -> MediumReviewBatch:
|
|
169
|
+
"""Run discovery and verification for one medium-review head.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
target_head: Reviewed head OID.
|
|
173
|
+
diff_base: Diff base OID.
|
|
174
|
+
all_finder_candidates: Outputs from the eight finder angles.
|
|
175
|
+
verdict_by_key: Verification results keyed by file/line/mechanism.
|
|
176
|
+
severity_by_key: Severities for retained findings.
|
|
177
|
+
live_head: Live head OID; must match target_head.
|
|
178
|
+
is_any_advisor_blocked: True when any finder ended advisor_blocked.
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ValueError: When finder angles are incomplete or duplicated.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
The medium-review batch (possibly rejected).
|
|
185
|
+
"""
|
|
186
|
+
batch = MediumReviewBatch(target_head=target_head, diff_base=diff_base)
|
|
187
|
+
if is_any_advisor_blocked:
|
|
188
|
+
batch.is_rejected = True
|
|
189
|
+
batch.rejection_reason = "advisor_blocked"
|
|
190
|
+
return batch
|
|
191
|
+
if live_head != target_head:
|
|
192
|
+
batch.is_rejected = True
|
|
193
|
+
batch.rejection_reason = "head_drift"
|
|
194
|
+
return batch
|
|
195
|
+
for each_candidate in all_finder_candidates:
|
|
196
|
+
if each_candidate.reviewed_head != target_head:
|
|
197
|
+
batch.is_rejected = True
|
|
198
|
+
batch.rejection_reason = "finder_head_mismatch"
|
|
199
|
+
return batch
|
|
200
|
+
all_angles = [each.angle for each in all_finder_candidates]
|
|
201
|
+
all_sockets = {each.leader_socket for each in all_finder_candidates}
|
|
202
|
+
all_sessions = {each.advisor_session_id for each in all_finder_candidates}
|
|
203
|
+
all_worktrees = {each.worktree_path for each in all_finder_candidates}
|
|
204
|
+
if (
|
|
205
|
+
len(all_sockets) != len(all_finder_candidates)
|
|
206
|
+
or len(all_sessions) != len(all_finder_candidates)
|
|
207
|
+
or len(all_worktrees) != len(all_finder_candidates)
|
|
208
|
+
):
|
|
209
|
+
batch.is_rejected = True
|
|
210
|
+
batch.rejection_reason = "non_unique_finder_identity"
|
|
211
|
+
return batch
|
|
212
|
+
require_exact_finder_set(tuple(sorted(set(all_angles))))
|
|
213
|
+
if len(set(all_angles)) != MEDIUM_REVIEW_FINDER_COUNT:
|
|
214
|
+
raise ValueError("each finder angle must run exactly once")
|
|
215
|
+
deduped = deduplicate_candidates(all_finder_candidates)
|
|
216
|
+
batch.all_retained_findings = retain_verified_findings(
|
|
217
|
+
all_candidates=deduped,
|
|
218
|
+
verdict_by_key=verdict_by_key,
|
|
219
|
+
severity_by_key=severity_by_key,
|
|
220
|
+
)
|
|
221
|
+
return batch
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Behavioral tests for Grok medium-review discovery and verification."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
_SCRIPTS = Path(__file__).resolve().parent
|
|
11
|
+
if str(_SCRIPTS) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(_SCRIPTS))
|
|
13
|
+
|
|
14
|
+
from e_code_review_scripts_constants.grok_code_review_constants import ( # noqa: E402
|
|
15
|
+
ALL_MEDIUM_FINDER_ANGLES,
|
|
16
|
+
MEDIUM_REVIEW_FINDER_COUNT,
|
|
17
|
+
VERDICT_CONFIRMED,
|
|
18
|
+
VERDICT_PLAUSIBLE,
|
|
19
|
+
VERDICT_REFUTED,
|
|
20
|
+
)
|
|
21
|
+
from grok_code_review import ( # noqa: E402
|
|
22
|
+
FinderCandidate,
|
|
23
|
+
deduplicate_candidates,
|
|
24
|
+
require_exact_finder_set,
|
|
25
|
+
retain_verified_findings,
|
|
26
|
+
run_medium_review,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _candidate(
|
|
31
|
+
angle: str,
|
|
32
|
+
*,
|
|
33
|
+
file_path: str = "a.py",
|
|
34
|
+
line_number: int = 1,
|
|
35
|
+
mechanism: str = "m",
|
|
36
|
+
scenario: str = "short",
|
|
37
|
+
suffix: str = "",
|
|
38
|
+
head: str = "head1",
|
|
39
|
+
) -> FinderCandidate:
|
|
40
|
+
token = suffix or angle
|
|
41
|
+
return FinderCandidate(
|
|
42
|
+
angle=angle,
|
|
43
|
+
file_path=file_path,
|
|
44
|
+
line_number=line_number,
|
|
45
|
+
mechanism=mechanism,
|
|
46
|
+
scenario=scenario,
|
|
47
|
+
worktree_path=f"/wt/{token}",
|
|
48
|
+
leader_socket=f"sock-{token}",
|
|
49
|
+
advisor_session_id=f"adv-{token}",
|
|
50
|
+
reviewed_head=head,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_require_exact_finder_set_accepts_all_angles() -> None:
|
|
55
|
+
require_exact_finder_set(ALL_MEDIUM_FINDER_ANGLES)
|
|
56
|
+
with pytest.raises(ValueError):
|
|
57
|
+
require_exact_finder_set(ALL_MEDIUM_FINDER_ANGLES[:-1])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_deduplicate_keeps_most_concrete_scenario() -> None:
|
|
61
|
+
short = _candidate("correctness", scenario="short")
|
|
62
|
+
long = _candidate("security", scenario="much more concrete failure path")
|
|
63
|
+
# same key different angles - force same file/line/mech
|
|
64
|
+
short = FinderCandidate(
|
|
65
|
+
angle="correctness",
|
|
66
|
+
file_path="a.py",
|
|
67
|
+
line_number=3,
|
|
68
|
+
mechanism="null",
|
|
69
|
+
scenario="x",
|
|
70
|
+
worktree_path="/wt/a",
|
|
71
|
+
leader_socket="s1",
|
|
72
|
+
advisor_session_id="a1",
|
|
73
|
+
reviewed_head="h",
|
|
74
|
+
)
|
|
75
|
+
long = FinderCandidate(
|
|
76
|
+
angle="security",
|
|
77
|
+
file_path="a.py",
|
|
78
|
+
line_number=3,
|
|
79
|
+
mechanism="null",
|
|
80
|
+
scenario="concrete long scenario",
|
|
81
|
+
worktree_path="/wt/b",
|
|
82
|
+
leader_socket="s2",
|
|
83
|
+
advisor_session_id="a2",
|
|
84
|
+
reviewed_head="h",
|
|
85
|
+
)
|
|
86
|
+
kept = deduplicate_candidates([short, long])
|
|
87
|
+
assert len(kept) == 1
|
|
88
|
+
assert kept[0].scenario == "concrete long scenario"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_run_medium_review_happy_path() -> None:
|
|
92
|
+
head = "abc123"
|
|
93
|
+
all_finders = [
|
|
94
|
+
_candidate(each_angle, head=head, suffix=each_angle)
|
|
95
|
+
for each_angle in ALL_MEDIUM_FINDER_ANGLES
|
|
96
|
+
]
|
|
97
|
+
# make unique keys per angle
|
|
98
|
+
all_finders = [
|
|
99
|
+
FinderCandidate(
|
|
100
|
+
angle=each_angle,
|
|
101
|
+
file_path=f"{each_angle}.py",
|
|
102
|
+
line_number=1,
|
|
103
|
+
mechanism="m",
|
|
104
|
+
scenario=f"scenario {each_angle}",
|
|
105
|
+
worktree_path=f"/wt/{each_angle}",
|
|
106
|
+
leader_socket=f"sock-{each_angle}",
|
|
107
|
+
advisor_session_id=f"adv-{each_angle}",
|
|
108
|
+
reviewed_head=head,
|
|
109
|
+
)
|
|
110
|
+
for each_angle in ALL_MEDIUM_FINDER_ANGLES
|
|
111
|
+
]
|
|
112
|
+
verdicts = {
|
|
113
|
+
(f"{each}.py", 1, "m"): VERDICT_CONFIRMED
|
|
114
|
+
for each in ALL_MEDIUM_FINDER_ANGLES
|
|
115
|
+
}
|
|
116
|
+
severities = {
|
|
117
|
+
(f"{each}.py", 1, "m"): "medium" for each in ALL_MEDIUM_FINDER_ANGLES
|
|
118
|
+
}
|
|
119
|
+
# refute one
|
|
120
|
+
first = ALL_MEDIUM_FINDER_ANGLES[0]
|
|
121
|
+
verdicts[(f"{first}.py", 1, "m")] = VERDICT_REFUTED
|
|
122
|
+
batch = run_medium_review(
|
|
123
|
+
target_head=head,
|
|
124
|
+
diff_base="base",
|
|
125
|
+
all_finder_candidates=all_finders,
|
|
126
|
+
verdict_by_key=verdicts,
|
|
127
|
+
severity_by_key=severities,
|
|
128
|
+
live_head=head,
|
|
129
|
+
)
|
|
130
|
+
assert batch.is_rejected is False
|
|
131
|
+
assert len(batch.all_finder_angles) == MEDIUM_REVIEW_FINDER_COUNT
|
|
132
|
+
assert all(each.reviewed_head == head for each in batch.all_retained_findings)
|
|
133
|
+
assert all(each.verdict in {VERDICT_CONFIRMED, VERDICT_PLAUSIBLE} for each in batch.all_retained_findings)
|
|
134
|
+
assert len(batch.all_retained_findings) == MEDIUM_REVIEW_FINDER_COUNT - 1
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_head_drift_rejects_batch() -> None:
|
|
138
|
+
head = "h1"
|
|
139
|
+
all_finders = [
|
|
140
|
+
FinderCandidate(
|
|
141
|
+
angle=each_angle,
|
|
142
|
+
file_path=f"{each_angle}.py",
|
|
143
|
+
line_number=1,
|
|
144
|
+
mechanism="m",
|
|
145
|
+
scenario="s",
|
|
146
|
+
worktree_path=f"/wt/{each_angle}",
|
|
147
|
+
leader_socket=f"sock-{each_angle}",
|
|
148
|
+
advisor_session_id=f"adv-{each_angle}",
|
|
149
|
+
reviewed_head=head,
|
|
150
|
+
)
|
|
151
|
+
for each_angle in ALL_MEDIUM_FINDER_ANGLES
|
|
152
|
+
]
|
|
153
|
+
batch = run_medium_review(
|
|
154
|
+
target_head=head,
|
|
155
|
+
diff_base="b",
|
|
156
|
+
all_finder_candidates=all_finders,
|
|
157
|
+
verdict_by_key={},
|
|
158
|
+
severity_by_key={},
|
|
159
|
+
live_head="other",
|
|
160
|
+
)
|
|
161
|
+
assert batch.is_rejected is True
|
|
162
|
+
assert batch.rejection_reason == "head_drift"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_advisor_blocked_rejects_batch() -> None:
|
|
166
|
+
head = "h1"
|
|
167
|
+
all_finders = [
|
|
168
|
+
FinderCandidate(
|
|
169
|
+
angle=each_angle,
|
|
170
|
+
file_path=f"{each_angle}.py",
|
|
171
|
+
line_number=1,
|
|
172
|
+
mechanism="m",
|
|
173
|
+
scenario="s",
|
|
174
|
+
worktree_path=f"/wt/{each_angle}",
|
|
175
|
+
leader_socket=f"sock-{each_angle}",
|
|
176
|
+
advisor_session_id=f"adv-{each_angle}",
|
|
177
|
+
reviewed_head=head,
|
|
178
|
+
)
|
|
179
|
+
for each_angle in ALL_MEDIUM_FINDER_ANGLES
|
|
180
|
+
]
|
|
181
|
+
batch = run_medium_review(
|
|
182
|
+
target_head=head,
|
|
183
|
+
diff_base="b",
|
|
184
|
+
all_finder_candidates=all_finders,
|
|
185
|
+
verdict_by_key={},
|
|
186
|
+
severity_by_key={},
|
|
187
|
+
live_head=head,
|
|
188
|
+
is_any_advisor_blocked=True,
|
|
189
|
+
)
|
|
190
|
+
assert batch.is_rejected is True
|
|
191
|
+
assert batch.rejection_reason == "advisor_blocked"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_retain_verified_findings_keeps_confirmed() -> None:
|
|
195
|
+
candidate = FinderCandidate(
|
|
196
|
+
angle="correctness",
|
|
197
|
+
file_path="a.py",
|
|
198
|
+
line_number=2,
|
|
199
|
+
mechanism="null",
|
|
200
|
+
scenario="boom",
|
|
201
|
+
worktree_path="/wt/x",
|
|
202
|
+
leader_socket="s",
|
|
203
|
+
advisor_session_id="a",
|
|
204
|
+
reviewed_head="h",
|
|
205
|
+
)
|
|
206
|
+
kept = retain_verified_findings(
|
|
207
|
+
all_candidates=[candidate],
|
|
208
|
+
verdict_by_key={("a.py", 2, "null"): VERDICT_CONFIRMED},
|
|
209
|
+
severity_by_key={("a.py", 2, "null"): "high"},
|
|
210
|
+
)
|
|
211
|
+
assert len(kept) == 1
|
|
212
|
+
assert kept[0].verdict == VERDICT_CONFIRMED
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# fresh-branch
|
|
2
2
|
|
|
3
|
-
Creates a new branch from fresh-fetched `origin/main` inside an isolated worktree under `<repo-root>/.claude/worktrees/<agent>/<branch-name>`. Does not push, open a PR, or run `checkout -b` in the caller tree.
|
|
3
|
+
Creates a new branch from fresh-fetched `origin/main` inside an isolated worktree under a configured root. Default: `<repo-root>/.claude/worktrees/<agent>/<branch-name>`. Optional absolute `--worktree-root` relocates the root (agent and branch still nest under it). Relative roots and path escape fail closed. Does not push, open a PR, or run `checkout -b` in the caller tree.
|
|
4
4
|
|
|
5
5
|
## Key files
|
|
6
6
|
|
|
7
7
|
| File | Purpose |
|
|
8
8
|
|---|---|
|
|
9
|
-
| `SKILL.md` | Phases, checklist, execute-vs-read for the CLI, gotchas |
|
|
10
|
-
| `scripts/create_fresh_branch.py` | Deterministic CLI: fetch base, `git worktree add -b`, JSON stdout |
|
|
9
|
+
| `SKILL.md` | Phases, checklist, configured-root contract, execute-vs-read for the CLI, gotchas |
|
|
10
|
+
| `scripts/create_fresh_branch.py` | Deterministic CLI: resolve configured root (fail closed before fetch), fetch base, `git worktree add -b`, JSON stdout |
|
|
11
11
|
| `scripts/fresh_branch_git_commands.py` | Git command helpers: fetch, ref checks, `git worktree add -b --no-track` |
|
|
12
|
-
| `scripts/test_create_fresh_branch.py` | Behavioral tests with temporary git repos |
|
|
12
|
+
| `scripts/test_create_fresh_branch.py` | Behavioral tests with temporary git repos (default root, explicit root, escape, collisions) |
|
|
13
13
|
| `scripts/test_fresh_branch_git_commands.py` | Behavioral tests for the git command helpers |
|
|
14
|
-
| `scripts/fresh_branch_scripts_constants/` | Constants package (`fresh_branch_cli_constants`) for
|
|
14
|
+
| `scripts/fresh_branch_scripts_constants/` | Constants package (`fresh_branch_cli_constants`) for CLI flags and error strings |
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fresh-branch
|
|
3
3
|
description: >-
|
|
4
|
-
Fresh git branch from origin/main in an isolated worktree under
|
|
4
|
+
Fresh git branch from origin/main in an isolated worktree under a configured root (default: <repo>/.claude/worktrees/<agent>/; never checkout -b in the caller tree).
|
|
5
5
|
Triggers: fresh branch, new branch from main, /fresh-branch, start fresh, clean branch off main,
|
|
6
6
|
worktree branch, branch in temp.
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# fresh-branch
|
|
10
10
|
|
|
11
|
-
Creates a new branch from a fresh-fetched `origin/main` inside an isolated git worktree under the repository's `.claude/worktrees
|
|
11
|
+
Creates a new branch from a fresh-fetched `origin/main` inside an isolated git worktree under a **configured isolated root**. Default root is the repository's `.claude/worktrees/`; pass `--worktree-root` for an absolute alternate root (including outside the repo). Every worktree path must resolve under that root. Shared primitive: other skills invoke `/fresh-branch` when they need a clean branch without touching the caller's dirty tree.
|
|
12
12
|
|
|
13
13
|
**Announce at start:** "Creating a fresh branch from origin/main."
|
|
14
14
|
|
|
@@ -62,15 +62,21 @@ Optional flags:
|
|
|
62
62
|
| Flag | Role |
|
|
63
63
|
|------|------|
|
|
64
64
|
| `--repo <path>` | Source repo (default: current directory) |
|
|
65
|
-
| `--agent <slug>` |
|
|
65
|
+
| `--agent <slug>` | Agent segment under the configured root: `claude`, `grok`, `cursor`, `codex`, … |
|
|
66
66
|
| `--base <ref>` | Base ref (default: `origin/main`) |
|
|
67
|
+
| `--worktree-root <abs-path>` | Absolute isolated root for all worktrees (default: `<repo-root>/.claude/worktrees`) |
|
|
67
68
|
|
|
68
69
|
Agent resolution inside the script: `--agent` → `FRESH_BRANCH_AGENT` env → host markers → `claude`.
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
Configured-root contract:
|
|
71
72
|
|
|
72
|
-
- `<repo-root>/.claude/worktrees
|
|
73
|
+
- Default configured root: `<repo-root>/.claude/worktrees`
|
|
74
|
+
- Explicit `--worktree-root` must be absolute; a relative value fails closed with a deterministic error before any git fetch
|
|
75
|
+
- Absolute roots outside the repository are legal; the agent and branch still nest under that root
|
|
76
|
+
- Worktree path: `<configured-root>/<agent>/<branch-name>`, on every platform
|
|
77
|
+
- Every allocated path must resolve under the configured root; traversal that escapes fails closed
|
|
73
78
|
- If the path exists, the script suffixes `-2`, `-3`, …
|
|
79
|
+
- Permission-rule cleanup (`stale_worktree_rule_sweep`) only walks `~/.claude/worktrees`; external `--worktree-root` trees are outside that sweep until a follow-up ships
|
|
74
80
|
|
|
75
81
|
On exit 0, stdout is one JSON object:
|
|
76
82
|
|
|
@@ -100,10 +106,12 @@ Further edits for the new branch belong in `worktree_path`, not in the caller's
|
|
|
100
106
|
|
|
101
107
|
## Gotchas
|
|
102
108
|
|
|
103
|
-
- **Dirty caller cwd blocks `checkout -b` and pollutes the tree.** Phase 3 always uses `git worktree add -b` into `<
|
|
109
|
+
- **Dirty caller cwd blocks `checkout -b` and pollutes the tree.** Phase 3 always uses `git worktree add -b` into `<configured-root>/<agent>/…`. If you reconstruct Phase 3 by hand with `checkout -b` in the session cwd, local modifications block the checkout and leave the user on a half-switched branch.
|
|
104
110
|
- **Caller HEAD must stay put.** After success, the original repo's checked-out branch and dirty files are unchanged; only the new worktree has the new branch.
|
|
105
111
|
- **Branch name collision.** If the branch already exists, the script exits non-zero with `{"error":...}`. Pick a new name; do not delete remote branches unless the user asks.
|
|
106
112
|
- **Path already occupied.** A leftover folder at the preferred worktree path gets a numeric suffix (`-2`, …); report the path from JSON, not the path you assumed.
|
|
113
|
+
- **Relative `--worktree-root` is refused before fetch.** Only an absolute path is accepted; the default root applies when the flag is omitted. Validation runs before `git fetch`, so a bad root never touches the network or remote-tracking refs.
|
|
114
|
+
- **External roots and stale permission rules.** `stale_worktree_rule_sweep` only walks `~/.claude/worktrees`. Edit rules granted under an external `--worktree-root` are not swept when that tree is deleted.
|
|
107
115
|
|
|
108
116
|
## File index
|
|
109
117
|
|