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,116 @@
|
|
|
1
|
+
"""Behavioral tests for the host-neutral Grok run ledger."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
12
|
+
if str(_SCRIPTS_DIR) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
14
|
+
|
|
15
|
+
from dev_env_scripts_constants.grok_run_ledger_constants import ( # noqa: E402
|
|
16
|
+
TASK_STATUS_ADVISOR_BLOCKED,
|
|
17
|
+
TASK_STATUS_COMPLETED,
|
|
18
|
+
TASK_STATUS_PENDING,
|
|
19
|
+
TASK_STATUS_PENDING_REVIEW,
|
|
20
|
+
)
|
|
21
|
+
from grok_run_ledger import GrokRunLedger, is_legal_status # noqa: E402
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_register_task_persists_atomically(tmp_path: Path) -> None:
|
|
25
|
+
ledger = GrokRunLedger(tmp_path)
|
|
26
|
+
ledger.register_task(task_id="O-04", all_dependencies=())
|
|
27
|
+
reloaded = GrokRunLedger(tmp_path)
|
|
28
|
+
record = reloaded.get_task("O-04")
|
|
29
|
+
assert record.status == TASK_STATUS_PENDING
|
|
30
|
+
assert is_legal_status(record.status)
|
|
31
|
+
payload = json.loads((tmp_path / "grok-run-ledger.json").read_text(encoding="utf-8"))
|
|
32
|
+
assert payload["tasks"][0]["task_id"] == "O-04"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_dependencies_block_dispatch(tmp_path: Path) -> None:
|
|
36
|
+
ledger = GrokRunLedger(tmp_path)
|
|
37
|
+
ledger.register_task(task_id="dep", all_dependencies=())
|
|
38
|
+
ledger.register_task(task_id="child", all_dependencies=("dep",))
|
|
39
|
+
assert ledger.can_dispatch("child") is False
|
|
40
|
+
with pytest.raises(ValueError, match="dependencies"):
|
|
41
|
+
ledger.mark_in_progress(
|
|
42
|
+
task_id="child",
|
|
43
|
+
owner_id="w1",
|
|
44
|
+
advisor_session_id="s1",
|
|
45
|
+
base_sha="aaa",
|
|
46
|
+
)
|
|
47
|
+
ledger.mark_in_progress(
|
|
48
|
+
task_id="dep", owner_id="w0", advisor_session_id="s0", base_sha="aaa"
|
|
49
|
+
)
|
|
50
|
+
ledger.mark_completed(
|
|
51
|
+
task_id="dep",
|
|
52
|
+
reviewed_head="bbb",
|
|
53
|
+
all_changed_paths=(),
|
|
54
|
+
advisor_verdict="ENDORSE",
|
|
55
|
+
all_acceptance_mapping={},
|
|
56
|
+
all_test_evidence=["ok"],
|
|
57
|
+
)
|
|
58
|
+
assert ledger.can_dispatch("child") is True
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_one_live_owner_and_unique_advisor_session(tmp_path: Path) -> None:
|
|
62
|
+
ledger = GrokRunLedger(tmp_path)
|
|
63
|
+
ledger.register_task(task_id="a", all_dependencies=())
|
|
64
|
+
ledger.register_task(task_id="b", all_dependencies=())
|
|
65
|
+
ledger.mark_in_progress(
|
|
66
|
+
task_id="a", owner_id="owner", advisor_session_id="sess-a", base_sha="1"
|
|
67
|
+
)
|
|
68
|
+
with pytest.raises(ValueError, match="owner already live"):
|
|
69
|
+
ledger.mark_in_progress(
|
|
70
|
+
task_id="b", owner_id="owner", advisor_session_id="sess-b", base_sha="1"
|
|
71
|
+
)
|
|
72
|
+
with pytest.raises(ValueError, match="advisor session already bound"):
|
|
73
|
+
ledger.mark_in_progress(
|
|
74
|
+
task_id="b", owner_id="other", advisor_session_id="sess-a", base_sha="1"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_snapshot_drift_moves_to_pending_review(tmp_path: Path) -> None:
|
|
79
|
+
ledger = GrokRunLedger(tmp_path)
|
|
80
|
+
ledger.register_task(task_id="t", all_dependencies=())
|
|
81
|
+
ledger.mark_in_progress(
|
|
82
|
+
task_id="t", owner_id="w", advisor_session_id="s", base_sha="base"
|
|
83
|
+
)
|
|
84
|
+
record = ledger.invalidate_on_snapshot_drift(task_id="t", current_sha="drifted")
|
|
85
|
+
assert record.status == TASK_STATUS_PENDING_REVIEW
|
|
86
|
+
assert record.owner_id is None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_advisor_blocked_terminal(tmp_path: Path) -> None:
|
|
90
|
+
ledger = GrokRunLedger(tmp_path)
|
|
91
|
+
ledger.register_task(task_id="t", all_dependencies=())
|
|
92
|
+
ledger.mark_in_progress(
|
|
93
|
+
task_id="t", owner_id="w", advisor_session_id="s", base_sha="base"
|
|
94
|
+
)
|
|
95
|
+
record = ledger.mark_advisor_blocked(task_id="t", reason="bind failed")
|
|
96
|
+
assert record.status == TASK_STATUS_ADVISOR_BLOCKED
|
|
97
|
+
assert "bind failed" in record.test_evidence[0]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_completed_records_acceptance_and_head(tmp_path: Path) -> None:
|
|
101
|
+
ledger = GrokRunLedger(tmp_path)
|
|
102
|
+
ledger.register_task(task_id="t", all_dependencies=())
|
|
103
|
+
ledger.mark_in_progress(
|
|
104
|
+
task_id="t", owner_id="w", advisor_session_id="s", base_sha="base"
|
|
105
|
+
)
|
|
106
|
+
record = ledger.mark_completed(
|
|
107
|
+
task_id="t",
|
|
108
|
+
reviewed_head="head",
|
|
109
|
+
all_changed_paths=("a.py",),
|
|
110
|
+
advisor_verdict="ENDORSE",
|
|
111
|
+
all_acceptance_mapping={"criterion": "evidence"},
|
|
112
|
+
all_test_evidence=["pytest -q"],
|
|
113
|
+
)
|
|
114
|
+
assert record.status == TASK_STATUS_COMPLETED
|
|
115
|
+
assert record.reviewed_head == "head"
|
|
116
|
+
assert record.changed_paths == ("a.py",)
|
|
@@ -15,11 +15,37 @@ import pytest
|
|
|
15
15
|
import invoke_code_review as invoker
|
|
16
16
|
from claude_chain_runner import ChainConfigurationError, ChainInvocationOutcome
|
|
17
17
|
from _code_review_test_support import FIXTURE_SESSION_OPUS
|
|
18
|
+
from dev_env_scripts_constants.claude_chain_constants import TERMINAL_STATUS_SERVED
|
|
18
19
|
from dev_env_scripts_constants.code_review_constants import (
|
|
20
|
+
ALL_FINDING_SEVERITIES,
|
|
21
|
+
ALL_LOOP_TERMINALS,
|
|
22
|
+
ALL_RETAINED_VERIFICATION_VERDICTS,
|
|
19
23
|
DEFAULT_CODE_REVIEW_EFFORT,
|
|
24
|
+
FINDING_FIELD_SEVERITY,
|
|
25
|
+
FINDING_FIELD_VERDICT,
|
|
20
26
|
PERMISSION_MODE_ACCEPT_EDITS,
|
|
21
27
|
PERMISSION_MODE_BYPASS,
|
|
28
|
+
RESULT_KEY_DRAFT_PRESERVED,
|
|
29
|
+
RESULT_KEY_REVIEWED_HEAD_COUNT,
|
|
30
|
+
RESULT_KEY_SURVIVING_FINDINGS,
|
|
31
|
+
RESULT_KEY_TERMINAL,
|
|
22
32
|
REVIEW_PERMISSION_MODE,
|
|
33
|
+
SEVERITY_BLOCKER,
|
|
34
|
+
SEVERITY_HIGH,
|
|
35
|
+
SEVERITY_LOW,
|
|
36
|
+
SEVERITY_MEDIUM,
|
|
37
|
+
SEVERITY_NIT,
|
|
38
|
+
TERMINAL_ADVISOR_BLOCKED,
|
|
39
|
+
TERMINAL_CLEAN,
|
|
40
|
+
TERMINAL_NITS_FIXED,
|
|
41
|
+
VERDICT_CONFIRMED,
|
|
42
|
+
VERDICT_PLAUSIBLE,
|
|
43
|
+
VERDICT_REFUTED,
|
|
44
|
+
all_findings_carry_severity_and_verdict,
|
|
45
|
+
encode_review_loop_terminal_result,
|
|
46
|
+
is_nits_only_findings,
|
|
47
|
+
record_reviewed_head,
|
|
48
|
+
resolve_review_loop_terminal,
|
|
23
49
|
)
|
|
24
50
|
|
|
25
51
|
|
|
@@ -66,6 +92,7 @@ def _serve_a_refusing_binary(
|
|
|
66
92
|
stdout=EMPTY_REVIEW_STDOUT,
|
|
67
93
|
stderr=REVIEW_BINARY_REFUSAL_TEXT,
|
|
68
94
|
attempts=(),
|
|
95
|
+
terminal_status=TERMINAL_STATUS_SERVED,
|
|
69
96
|
)
|
|
70
97
|
|
|
71
98
|
|
|
@@ -141,3 +168,274 @@ def test_host_profile_failure_reports_its_reason_on_stderr(
|
|
|
141
168
|
captured_streams = capsys.readouterr()
|
|
142
169
|
assert outcome.is_stamp_minted is False
|
|
143
170
|
assert HOST_PROFILE_FAILURE_TEXT in captured_streams.err
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
HEAD_SHA_ONE: str = "aaa111"
|
|
174
|
+
HEAD_SHA_TWO: str = "bbb222"
|
|
175
|
+
HEAD_SHA_THREE: str = "ccc333"
|
|
176
|
+
HEAD_SHA_FOUR: str = "ddd444"
|
|
177
|
+
FINDING_FILE_PATH: str = "pkg/module.py"
|
|
178
|
+
FINDING_LINE_NUMBER: int = 12
|
|
179
|
+
FINDING_SUMMARY_TEXT: str = "example finding"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _retained_finding(
|
|
183
|
+
*,
|
|
184
|
+
severity: str,
|
|
185
|
+
verdict: str = VERDICT_CONFIRMED,
|
|
186
|
+
) -> dict[str, object]:
|
|
187
|
+
return {
|
|
188
|
+
"file": FINDING_FILE_PATH,
|
|
189
|
+
"line": FINDING_LINE_NUMBER,
|
|
190
|
+
"summary": FINDING_SUMMARY_TEXT,
|
|
191
|
+
FINDING_FIELD_SEVERITY: severity,
|
|
192
|
+
FINDING_FIELD_VERDICT: verdict,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_severity_vocabulary_is_the_frozen_five_token_set() -> None:
|
|
197
|
+
assert ALL_FINDING_SEVERITIES == (
|
|
198
|
+
SEVERITY_BLOCKER,
|
|
199
|
+
SEVERITY_HIGH,
|
|
200
|
+
SEVERITY_MEDIUM,
|
|
201
|
+
SEVERITY_LOW,
|
|
202
|
+
SEVERITY_NIT,
|
|
203
|
+
)
|
|
204
|
+
assert ALL_FINDING_SEVERITIES == (
|
|
205
|
+
"blocker",
|
|
206
|
+
"high",
|
|
207
|
+
"medium",
|
|
208
|
+
"low",
|
|
209
|
+
"nit",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_loop_terminals_are_the_frozen_three_token_set() -> None:
|
|
214
|
+
assert ALL_LOOP_TERMINALS == (
|
|
215
|
+
TERMINAL_CLEAN,
|
|
216
|
+
TERMINAL_NITS_FIXED,
|
|
217
|
+
TERMINAL_ADVISOR_BLOCKED,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_retained_finding_requires_severity_and_verification_verdict() -> None:
|
|
222
|
+
complete_finding = _retained_finding(severity=SEVERITY_HIGH)
|
|
223
|
+
missing_severity = {
|
|
224
|
+
"file": FINDING_FILE_PATH,
|
|
225
|
+
"line": FINDING_LINE_NUMBER,
|
|
226
|
+
FINDING_FIELD_VERDICT: VERDICT_CONFIRMED,
|
|
227
|
+
}
|
|
228
|
+
missing_verdict = {
|
|
229
|
+
"file": FINDING_FILE_PATH,
|
|
230
|
+
"line": FINDING_LINE_NUMBER,
|
|
231
|
+
FINDING_FIELD_SEVERITY: SEVERITY_HIGH,
|
|
232
|
+
}
|
|
233
|
+
refuted_finding = _retained_finding(
|
|
234
|
+
severity=SEVERITY_LOW,
|
|
235
|
+
verdict=VERDICT_REFUTED,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
assert all_findings_carry_severity_and_verdict([complete_finding]) is True
|
|
239
|
+
assert all_findings_carry_severity_and_verdict([missing_severity]) is False
|
|
240
|
+
assert all_findings_carry_severity_and_verdict([missing_verdict]) is False
|
|
241
|
+
assert all_findings_carry_severity_and_verdict([refuted_finding]) is False
|
|
242
|
+
assert VERDICT_CONFIRMED in ALL_RETAINED_VERIFICATION_VERDICTS
|
|
243
|
+
assert VERDICT_PLAUSIBLE in ALL_RETAINED_VERIFICATION_VERDICTS
|
|
244
|
+
assert VERDICT_REFUTED not in ALL_RETAINED_VERIFICATION_VERDICTS
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_review_of_new_head_increments_reviewed_head_count_once() -> None:
|
|
248
|
+
after_first = record_reviewed_head((), HEAD_SHA_ONE)
|
|
249
|
+
after_same_head = record_reviewed_head(after_first, HEAD_SHA_ONE)
|
|
250
|
+
after_second = record_reviewed_head(after_same_head, HEAD_SHA_TWO)
|
|
251
|
+
|
|
252
|
+
assert after_first == (HEAD_SHA_ONE,)
|
|
253
|
+
assert after_same_head == (HEAD_SHA_ONE,)
|
|
254
|
+
assert after_second == (HEAD_SHA_ONE, HEAD_SHA_TWO)
|
|
255
|
+
assert len(after_second) == 2
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_empty_findings_return_clean() -> None:
|
|
259
|
+
terminal_status = resolve_review_loop_terminal(
|
|
260
|
+
all_findings=(),
|
|
261
|
+
reviewed_head_count=1,
|
|
262
|
+
is_gates_passed=True,
|
|
263
|
+
is_nits_applied=False,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
assert terminal_status == TERMINAL_CLEAN
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def test_empty_findings_continue_when_gates_have_not_passed() -> None:
|
|
270
|
+
terminal_status = resolve_review_loop_terminal(
|
|
271
|
+
all_findings=(),
|
|
272
|
+
reviewed_head_count=1,
|
|
273
|
+
is_gates_passed=False,
|
|
274
|
+
is_nits_applied=False,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
assert terminal_status is None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def test_nits_only_round_returns_nits_fixed_after_gates() -> None:
|
|
281
|
+
all_nits = (
|
|
282
|
+
_retained_finding(severity=SEVERITY_NIT),
|
|
283
|
+
_retained_finding(severity=SEVERITY_NIT, verdict=VERDICT_PLAUSIBLE),
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
assert is_nits_only_findings(all_nits) is True
|
|
287
|
+
terminal_status = resolve_review_loop_terminal(
|
|
288
|
+
all_findings=all_nits,
|
|
289
|
+
reviewed_head_count=1,
|
|
290
|
+
is_gates_passed=True,
|
|
291
|
+
is_nits_applied=True,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
assert terminal_status == TERMINAL_NITS_FIXED
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_nits_without_retained_verdict_do_not_return_nits_fixed() -> None:
|
|
298
|
+
severity_only_nit = {
|
|
299
|
+
"file": FINDING_FILE_PATH,
|
|
300
|
+
"line": FINDING_LINE_NUMBER,
|
|
301
|
+
"summary": FINDING_SUMMARY_TEXT,
|
|
302
|
+
FINDING_FIELD_SEVERITY: SEVERITY_NIT,
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
assert is_nits_only_findings((severity_only_nit,)) is True
|
|
306
|
+
assert all_findings_carry_severity_and_verdict((severity_only_nit,)) is False
|
|
307
|
+
terminal_status = resolve_review_loop_terminal(
|
|
308
|
+
all_findings=(severity_only_nit,),
|
|
309
|
+
reviewed_head_count=1,
|
|
310
|
+
is_gates_passed=True,
|
|
311
|
+
is_nits_applied=True,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
assert terminal_status is None
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def test_open_non_nit_findings_continue_at_any_head_count() -> None:
|
|
318
|
+
all_findings = (
|
|
319
|
+
_retained_finding(severity=SEVERITY_HIGH),
|
|
320
|
+
_retained_finding(severity=SEVERITY_NIT),
|
|
321
|
+
)
|
|
322
|
+
all_heads = record_reviewed_head((), HEAD_SHA_ONE)
|
|
323
|
+
all_heads = record_reviewed_head(all_heads, HEAD_SHA_TWO)
|
|
324
|
+
all_heads = record_reviewed_head(all_heads, HEAD_SHA_THREE)
|
|
325
|
+
all_heads = record_reviewed_head(all_heads, HEAD_SHA_FOUR)
|
|
326
|
+
|
|
327
|
+
assert len(all_heads) == 4
|
|
328
|
+
terminal_status = resolve_review_loop_terminal(
|
|
329
|
+
all_findings=all_findings,
|
|
330
|
+
reviewed_head_count=len(all_heads),
|
|
331
|
+
is_gates_passed=True,
|
|
332
|
+
is_nits_applied=False,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
assert terminal_status is None
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def test_unclassified_findings_continue_when_advisor_is_reachable() -> None:
|
|
339
|
+
unclassified_finding = {
|
|
340
|
+
"file": FINDING_FILE_PATH,
|
|
341
|
+
"line": FINDING_LINE_NUMBER,
|
|
342
|
+
"summary": FINDING_SUMMARY_TEXT,
|
|
343
|
+
FINDING_FIELD_VERDICT: VERDICT_CONFIRMED,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
terminal_status = resolve_review_loop_terminal(
|
|
347
|
+
all_findings=(unclassified_finding,),
|
|
348
|
+
reviewed_head_count=5,
|
|
349
|
+
is_gates_passed=True,
|
|
350
|
+
is_nits_applied=False,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
assert terminal_status is None
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def test_nits_only_returns_nits_fixed_at_any_head_count() -> None:
|
|
357
|
+
all_nits = (_retained_finding(severity=SEVERITY_NIT),)
|
|
358
|
+
|
|
359
|
+
terminal_status = resolve_review_loop_terminal(
|
|
360
|
+
all_findings=all_nits,
|
|
361
|
+
reviewed_head_count=5,
|
|
362
|
+
is_gates_passed=True,
|
|
363
|
+
is_nits_applied=True,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
assert terminal_status == TERMINAL_NITS_FIXED
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def test_advisor_unreachable_with_unclassified_returns_advisor_blocked() -> None:
|
|
370
|
+
unclassified_finding = {
|
|
371
|
+
"file": FINDING_FILE_PATH,
|
|
372
|
+
"line": FINDING_LINE_NUMBER,
|
|
373
|
+
FINDING_FIELD_VERDICT: VERDICT_CONFIRMED,
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
terminal_status = resolve_review_loop_terminal(
|
|
377
|
+
all_findings=(unclassified_finding,),
|
|
378
|
+
reviewed_head_count=1,
|
|
379
|
+
is_gates_passed=False,
|
|
380
|
+
is_nits_applied=False,
|
|
381
|
+
is_advisor_unreachable=True,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
assert terminal_status == TERMINAL_ADVISOR_BLOCKED
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def test_terminal_serialization_preserves_draft_and_findings() -> None:
|
|
388
|
+
surviving_finding = _retained_finding(severity=SEVERITY_MEDIUM)
|
|
389
|
+
encoded_payload = encode_review_loop_terminal_result(
|
|
390
|
+
terminal=TERMINAL_ADVISOR_BLOCKED,
|
|
391
|
+
all_surviving_findings=(surviving_finding,),
|
|
392
|
+
reviewed_head_count=4,
|
|
393
|
+
is_draft_preserved=True,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
assert encoded_payload[RESULT_KEY_TERMINAL] == TERMINAL_ADVISOR_BLOCKED
|
|
397
|
+
assert encoded_payload[RESULT_KEY_DRAFT_PRESERVED] is True
|
|
398
|
+
assert encoded_payload[RESULT_KEY_REVIEWED_HEAD_COUNT] == 4
|
|
399
|
+
assert encoded_payload[RESULT_KEY_SURVIVING_FINDINGS] == [surviving_finding]
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def test_advisor_blocked_forces_draft_preserved_when_caller_passes_false() -> None:
|
|
403
|
+
surviving_finding = _retained_finding(severity=SEVERITY_HIGH)
|
|
404
|
+
advisor_blocked_payload = encode_review_loop_terminal_result(
|
|
405
|
+
terminal=TERMINAL_ADVISOR_BLOCKED,
|
|
406
|
+
all_surviving_findings=(surviving_finding,),
|
|
407
|
+
reviewed_head_count=1,
|
|
408
|
+
is_draft_preserved=False,
|
|
409
|
+
)
|
|
410
|
+
clean_payload = encode_review_loop_terminal_result(
|
|
411
|
+
terminal=TERMINAL_CLEAN,
|
|
412
|
+
all_surviving_findings=(),
|
|
413
|
+
reviewed_head_count=1,
|
|
414
|
+
is_draft_preserved=False,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
assert advisor_blocked_payload[RESULT_KEY_DRAFT_PRESERVED] is True
|
|
418
|
+
assert clean_payload[RESULT_KEY_DRAFT_PRESERVED] is False
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def test_fourth_and_later_heads_are_recorded() -> None:
|
|
422
|
+
all_heads = (HEAD_SHA_ONE, HEAD_SHA_TWO, HEAD_SHA_THREE)
|
|
423
|
+
after_fourth = record_reviewed_head(all_heads, HEAD_SHA_FOUR)
|
|
424
|
+
|
|
425
|
+
assert after_fourth == (
|
|
426
|
+
HEAD_SHA_ONE,
|
|
427
|
+
HEAD_SHA_TWO,
|
|
428
|
+
HEAD_SHA_THREE,
|
|
429
|
+
HEAD_SHA_FOUR,
|
|
430
|
+
)
|
|
431
|
+
assert len(after_fourth) == 4
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def test_mixed_severities_are_not_nits_only() -> None:
|
|
435
|
+
all_findings = (
|
|
436
|
+
_retained_finding(severity=SEVERITY_NIT),
|
|
437
|
+
_retained_finding(severity=SEVERITY_BLOCKER),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
assert is_nits_only_findings(all_findings) is False
|
|
441
|
+
assert is_nits_only_findings(()) is False
|
|
@@ -23,6 +23,10 @@ from claude_chain_runner import ( # noqa: E402
|
|
|
23
23
|
ChainConfigurationError,
|
|
24
24
|
ChainInvocationOutcome,
|
|
25
25
|
)
|
|
26
|
+
from dev_env_scripts_constants.claude_chain_constants import ( # noqa: E402
|
|
27
|
+
TERMINAL_STATUS_CHAIN_EXHAUSTED,
|
|
28
|
+
TERMINAL_STATUS_SERVED,
|
|
29
|
+
)
|
|
26
30
|
from dev_env_scripts_constants.grok_worker_constants import ( # noqa: E402
|
|
27
31
|
AGENT_FLAG,
|
|
28
32
|
ALL_AGENT_FILENAMES_BY_ROLE,
|
|
@@ -133,6 +137,7 @@ def _claude_served(
|
|
|
133
137
|
stdout=stdout,
|
|
134
138
|
stderr="",
|
|
135
139
|
attempts=(ChainAttempt(command="claude", status="served"),),
|
|
140
|
+
terminal_status=TERMINAL_STATUS_SERVED,
|
|
136
141
|
)
|
|
137
142
|
|
|
138
143
|
|
|
@@ -143,6 +148,7 @@ def _claude_exhausted() -> ChainInvocationOutcome:
|
|
|
143
148
|
stdout="",
|
|
144
149
|
stderr="usage limit reached",
|
|
145
150
|
attempts=(ChainAttempt(command="claude", status="usage_limited"),),
|
|
151
|
+
terminal_status=TERMINAL_STATUS_CHAIN_EXHAUSTED,
|
|
146
152
|
)
|
|
147
153
|
|
|
148
154
|
|