claude-dev-env 2.7.0 → 2.8.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/_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/pr-loop/worker-spawn.md +3 -1
- 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/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 +309 -12
- 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/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/package.json +1 -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/scripts/CLAUDE.md +4 -4
- 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_worker_constants.py +83 -13
- package/scripts/grok_headless_runner.py +148 -18
- package/scripts/resolve_worker_spawn.py +56 -10
- package/scripts/spawn_grok_batch.py +81 -23
- package/scripts/test_claude_chain_runner.py +358 -0
- package/scripts/test_grok_headless_runner.py +547 -10
- package/scripts/test_invoke_code_review.py +298 -0
- package/scripts/test_resolve_worker_spawn.py +185 -15
- package/scripts/test_spawn_grok_batch.py +326 -22
- package/scripts/tests/CLAUDE.md +1 -0
- package/scripts/tests/test_grok_worker_constants.py +59 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +46 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +133 -8
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +107 -1
- package/skills/autoconverge/workflow/converge.mjs +113 -31
- 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 +9 -8
- 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/xhigh.md +30 -12
- 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 -3
- package/skills/grok-spawn/reference/flag-profiles.md +3 -1
- package/skills/orchestrator/SKILL.md +4 -1
- package/skills/orchestrator-refresh/SKILL.md +5 -1
- package/skills/team-advisor/SKILL.md +4 -1
|
@@ -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,
|
|
@@ -35,17 +39,20 @@ from dev_env_scripts_constants.grok_worker_constants import ( # noqa: E402
|
|
|
35
39
|
CLI_TIMEOUT_FLAG,
|
|
36
40
|
CLASSIFICATION_AUTH_FAILURE,
|
|
37
41
|
CLASSIFICATION_OK,
|
|
42
|
+
CLASSIFICATION_TIMEOUT,
|
|
38
43
|
CLASSIFICATION_USAGE_LIMIT,
|
|
39
44
|
CWD_FLAG,
|
|
40
45
|
DEFAULT_ROLE,
|
|
41
|
-
DEFAULT_SPAWN_MAX_TURNS,
|
|
42
46
|
DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
47
|
+
MAXIMUM_WORKER_TIMEOUT_SECONDS,
|
|
48
|
+
MIN_WORKER_TIMEOUT_SECONDS,
|
|
43
49
|
OUTPUT_FORMAT_FLAG,
|
|
44
50
|
OUTPUT_FORMAT_JSON,
|
|
45
51
|
PROMPT_FILE_FLAG,
|
|
46
52
|
REASON_CLAUDE_AGENT_REQUIRED,
|
|
47
53
|
REASON_GROK_AUTH_FAILED,
|
|
48
54
|
REASON_PROMPT_FILE_MISSING,
|
|
55
|
+
REASON_TIMEOUT_OUT_OF_BOUNDS,
|
|
49
56
|
RESULT_KEY_ATTEMPTS,
|
|
50
57
|
RESULT_KEY_OK,
|
|
51
58
|
RESULT_KEY_OUTPUT,
|
|
@@ -59,11 +66,16 @@ from dev_env_scripts_constants.grok_worker_constants import ( # noqa: E402
|
|
|
59
66
|
TIER_CLAUDE_HEADLESS,
|
|
60
67
|
TIER_GROK,
|
|
61
68
|
)
|
|
62
|
-
|
|
69
|
+
import grok_headless_runner # noqa: E402
|
|
70
|
+
from grok_headless_runner import ( # noqa: E402
|
|
71
|
+
GrokRunnerOutcome,
|
|
72
|
+
run_headless_worker,
|
|
73
|
+
)
|
|
63
74
|
from grok_worker_preflight import PreflightOutcome # noqa: E402
|
|
64
75
|
|
|
65
76
|
HOST_PROFILE_CLAUDE = "Claude"
|
|
66
77
|
HOST_PROFILE_THIRD_PARTY = "ThirdParty"
|
|
78
|
+
NON_POSITIVE_TIMEOUT_SECONDS = 0
|
|
67
79
|
|
|
68
80
|
FIXTURE_GROK_STDOUT = '{"tier":"grok","status":"done"}'
|
|
69
81
|
FIXTURE_CLAUDE_STDOUT = '{"tier":"claude","status":"done"}'
|
|
@@ -72,6 +84,7 @@ FIXTURE_GROK_RETURNCODE = 0
|
|
|
72
84
|
FIXTURE_CLAUDE_RETURNCODE = 0
|
|
73
85
|
FIXTURE_FAILED_RETURNCODE = 1
|
|
74
86
|
FIXTURE_ROLE = "code-quality-agent"
|
|
87
|
+
MIN_WORKER_TIMEOUT_SECONDS_CONSTANT_NAME = "MIN_WORKER_TIMEOUT_SECONDS"
|
|
75
88
|
LARGE_PROMPT_CHARACTER_COUNT = 40000
|
|
76
89
|
WINDOWS_SAFE_ARGV_ELEMENT_CEILING = 8192
|
|
77
90
|
EXPECTED_PRIMARY_AGENT_FOR_DEFAULT_ROLE = Path(
|
|
@@ -124,6 +137,7 @@ def _claude_served(
|
|
|
124
137
|
stdout=stdout,
|
|
125
138
|
stderr="",
|
|
126
139
|
attempts=(ChainAttempt(command="claude", status="served"),),
|
|
140
|
+
terminal_status=TERMINAL_STATUS_SERVED,
|
|
127
141
|
)
|
|
128
142
|
|
|
129
143
|
|
|
@@ -134,6 +148,7 @@ def _claude_exhausted() -> ChainInvocationOutcome:
|
|
|
134
148
|
stdout="",
|
|
135
149
|
stderr="usage limit reached",
|
|
136
150
|
attempts=(ChainAttempt(command="claude", status="usage_limited"),),
|
|
151
|
+
terminal_status=TERMINAL_STATUS_CHAIN_EXHAUSTED,
|
|
137
152
|
)
|
|
138
153
|
|
|
139
154
|
|
|
@@ -261,7 +276,6 @@ def test_grok_ok_serves_tier_one(
|
|
|
261
276
|
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
262
277
|
is_claude_tier_enabled=False,
|
|
263
278
|
run_state_directory=run_state_directory,
|
|
264
|
-
max_turns=DEFAULT_SPAWN_MAX_TURNS,
|
|
265
279
|
)
|
|
266
280
|
|
|
267
281
|
assert spawn_outcome.is_ok is True
|
|
@@ -297,7 +311,6 @@ def test_grok_usage_limited_on_claude_host_requires_agent(
|
|
|
297
311
|
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
298
312
|
is_claude_tier_enabled=False,
|
|
299
313
|
run_state_directory=run_state_directory,
|
|
300
|
-
max_turns=DEFAULT_SPAWN_MAX_TURNS,
|
|
301
314
|
)
|
|
302
315
|
|
|
303
316
|
assert spawn_outcome.is_ok is False
|
|
@@ -313,6 +326,128 @@ def test_grok_usage_limited_on_claude_host_requires_agent(
|
|
|
313
326
|
assert spawn_outcome.all_attempts[1].reason == REASON_CLAUDE_AGENT_REQUIRED
|
|
314
327
|
|
|
315
328
|
|
|
329
|
+
def test_out_of_bounds_timeout_is_reported_as_config_not_worker_failure(
|
|
330
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
331
|
+
) -> None:
|
|
332
|
+
"""A refused timeout prints a structured outcome and exits 3, never a traceback.
|
|
333
|
+
|
|
334
|
+
::
|
|
335
|
+
|
|
336
|
+
--timeout-seconds 0 ok: reason timeout_out_of_bounds, exit 3
|
|
337
|
+
--timeout-seconds 5401 ok: reason timeout_out_of_bounds, exit 3
|
|
338
|
+
"""
|
|
339
|
+
prompt_file, working_directory, run_state_directory = _paths(tmp_path)
|
|
340
|
+
_install_seams(monkeypatch, grok_outcome=_grok_ok())
|
|
341
|
+
monkeypatch.setattr(
|
|
342
|
+
dispatcher, "spawn_grok_runner", grok_headless_runner.run_headless_worker
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
for each_refused_timeout in (
|
|
346
|
+
NON_POSITIVE_TIMEOUT_SECONDS,
|
|
347
|
+
MAXIMUM_WORKER_TIMEOUT_SECONDS + 1,
|
|
348
|
+
):
|
|
349
|
+
exit_code = dispatcher.main(
|
|
350
|
+
[
|
|
351
|
+
CLI_ROLE_FLAG,
|
|
352
|
+
FIXTURE_ROLE,
|
|
353
|
+
PROMPT_FILE_FLAG,
|
|
354
|
+
str(prompt_file),
|
|
355
|
+
CWD_FLAG,
|
|
356
|
+
str(working_directory),
|
|
357
|
+
CLI_TIMEOUT_FLAG,
|
|
358
|
+
str(each_refused_timeout),
|
|
359
|
+
CLI_RUN_STATE_DIR_FLAG,
|
|
360
|
+
str(run_state_directory),
|
|
361
|
+
]
|
|
362
|
+
)
|
|
363
|
+
parsed_payload = json.loads(capsys.readouterr().out)
|
|
364
|
+
all_attempt_reasons = [
|
|
365
|
+
each_attempt[ATTEMPT_KEY_REASON]
|
|
366
|
+
for each_attempt in parsed_payload[RESULT_KEY_ATTEMPTS]
|
|
367
|
+
]
|
|
368
|
+
|
|
369
|
+
assert exit_code == SPAWN_CONFIG_ERROR_EXIT_CODE, each_refused_timeout
|
|
370
|
+
assert parsed_payload[RESULT_KEY_OK] is False, each_refused_timeout
|
|
371
|
+
assert parsed_payload[RESULT_KEY_TIER_USED] is None, each_refused_timeout
|
|
372
|
+
assert REASON_TIMEOUT_OUT_OF_BOUNDS in all_attempt_reasons, each_refused_timeout
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def test_out_of_bounds_timeout_is_refused_when_the_grok_tier_is_unreachable(
|
|
376
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
377
|
+
) -> None:
|
|
378
|
+
"""The bounds hold on a host whose preflight never reaches the grok runner.
|
|
379
|
+
|
|
380
|
+
::
|
|
381
|
+
|
|
382
|
+
preflight unusable, --timeout-seconds 5401
|
|
383
|
+
ok: reason timeout_out_of_bounds, exit 3, no tier invoked
|
|
384
|
+
"""
|
|
385
|
+
prompt_file, working_directory, run_state_directory = _paths(tmp_path)
|
|
386
|
+
call_log = _install_seams(
|
|
387
|
+
monkeypatch,
|
|
388
|
+
preflight_outcome=_fallthrough_preflight(REASON_GROK_AUTH_FAILED),
|
|
389
|
+
claude_outcome=_claude_served(),
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
exit_code = dispatcher.main(
|
|
393
|
+
[
|
|
394
|
+
CLI_ROLE_FLAG,
|
|
395
|
+
FIXTURE_ROLE,
|
|
396
|
+
PROMPT_FILE_FLAG,
|
|
397
|
+
str(prompt_file),
|
|
398
|
+
CWD_FLAG,
|
|
399
|
+
str(working_directory),
|
|
400
|
+
CLI_TIMEOUT_FLAG,
|
|
401
|
+
str(MAXIMUM_WORKER_TIMEOUT_SECONDS + 1),
|
|
402
|
+
CLI_RUN_STATE_DIR_FLAG,
|
|
403
|
+
str(run_state_directory),
|
|
404
|
+
]
|
|
405
|
+
)
|
|
406
|
+
parsed_payload = json.loads(capsys.readouterr().out)
|
|
407
|
+
all_attempt_reasons = [
|
|
408
|
+
each_attempt[ATTEMPT_KEY_REASON]
|
|
409
|
+
for each_attempt in parsed_payload[RESULT_KEY_ATTEMPTS]
|
|
410
|
+
]
|
|
411
|
+
|
|
412
|
+
assert exit_code == SPAWN_CONFIG_ERROR_EXIT_CODE
|
|
413
|
+
assert REASON_TIMEOUT_OUT_OF_BOUNDS in all_attempt_reasons
|
|
414
|
+
assert call_log.preflight_calls == 0
|
|
415
|
+
assert call_log.claude_calls == 0
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def test_timed_out_grok_worker_is_recorded_as_timeout_not_served(
|
|
419
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
420
|
+
) -> None:
|
|
421
|
+
"""A killed worker reaches the dispatcher's trail as timeout, never as served.
|
|
422
|
+
|
|
423
|
+
::
|
|
424
|
+
|
|
425
|
+
grok tier times out ok: attempt reason timeout, tier_used moves on
|
|
426
|
+
"""
|
|
427
|
+
prompt_file, working_directory, run_state_directory = _paths(tmp_path)
|
|
428
|
+
call_log = _install_seams(
|
|
429
|
+
monkeypatch,
|
|
430
|
+
grok_outcome=_grok_failure(CLASSIFICATION_TIMEOUT),
|
|
431
|
+
claude_outcome=_claude_served(),
|
|
432
|
+
host_profile=HOST_PROFILE_THIRD_PARTY,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
spawn_outcome = dispatcher.resolve_worker_spawn(
|
|
436
|
+
role=FIXTURE_ROLE,
|
|
437
|
+
prompt_file=prompt_file,
|
|
438
|
+
working_directory=working_directory,
|
|
439
|
+
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
440
|
+
is_claude_tier_enabled=False,
|
|
441
|
+
run_state_directory=run_state_directory,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
assert call_log.grok_calls == 1
|
|
445
|
+
assert spawn_outcome.all_attempts[0].tier == TIER_GROK
|
|
446
|
+
assert spawn_outcome.all_attempts[0].is_ok is False
|
|
447
|
+
assert spawn_outcome.all_attempts[0].reason == CLASSIFICATION_TIMEOUT
|
|
448
|
+
assert spawn_outcome.tier_used != TIER_GROK
|
|
449
|
+
|
|
450
|
+
|
|
316
451
|
def test_grok_auth_failed_on_third_party_runs_tier_three(
|
|
317
452
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
318
453
|
) -> None:
|
|
@@ -331,7 +466,6 @@ def test_grok_auth_failed_on_third_party_runs_tier_three(
|
|
|
331
466
|
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
332
467
|
is_claude_tier_enabled=False,
|
|
333
468
|
run_state_directory=run_state_directory,
|
|
334
|
-
max_turns=DEFAULT_SPAWN_MAX_TURNS,
|
|
335
469
|
)
|
|
336
470
|
|
|
337
471
|
assert spawn_outcome.is_ok is True
|
|
@@ -423,6 +557,40 @@ def test_config_error_returns_exit_three(
|
|
|
423
557
|
assert parsed_payload[RESULT_KEY_RETURNCODE] == SPAWN_CONFIG_ERROR_EXIT_CODE
|
|
424
558
|
|
|
425
559
|
|
|
560
|
+
def test_below_floor_timeout_returns_json_config_exit(
|
|
561
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
562
|
+
) -> None:
|
|
563
|
+
"""A below-floor timeout reads as a config error, not an escaping traceback."""
|
|
564
|
+
prompt_file, working_directory, run_state_directory = _paths(tmp_path)
|
|
565
|
+
_install_seams(monkeypatch, grok_outcome=_grok_ok())
|
|
566
|
+
monkeypatch.setattr(dispatcher, "spawn_grok_runner", run_headless_worker)
|
|
567
|
+
below_floor_timeout_seconds = MIN_WORKER_TIMEOUT_SECONDS - 1
|
|
568
|
+
|
|
569
|
+
exit_code = dispatcher.main(
|
|
570
|
+
[
|
|
571
|
+
CLI_ROLE_FLAG,
|
|
572
|
+
FIXTURE_ROLE,
|
|
573
|
+
PROMPT_FILE_FLAG,
|
|
574
|
+
str(prompt_file),
|
|
575
|
+
CWD_FLAG,
|
|
576
|
+
str(working_directory),
|
|
577
|
+
CLI_TIMEOUT_FLAG,
|
|
578
|
+
str(below_floor_timeout_seconds),
|
|
579
|
+
CLI_RUN_STATE_DIR_FLAG,
|
|
580
|
+
str(run_state_directory),
|
|
581
|
+
]
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
assert exit_code == SPAWN_CONFIG_ERROR_EXIT_CODE
|
|
585
|
+
captured = capsys.readouterr()
|
|
586
|
+
assert captured.err == ""
|
|
587
|
+
parsed_payload = json.loads(captured.out)
|
|
588
|
+
assert parsed_payload[RESULT_KEY_OK] is False
|
|
589
|
+
assert parsed_payload[RESULT_KEY_TIER_USED] is None
|
|
590
|
+
assert parsed_payload[RESULT_KEY_RETURNCODE] == SPAWN_CONFIG_ERROR_EXIT_CODE
|
|
591
|
+
assert MIN_WORKER_TIMEOUT_SECONDS_CONSTANT_NAME in parsed_payload[RESULT_KEY_OUTPUT]
|
|
592
|
+
|
|
593
|
+
|
|
426
594
|
def test_attempts_array_ordering_across_tiers(
|
|
427
595
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
428
596
|
) -> None:
|
|
@@ -441,7 +609,6 @@ def test_attempts_array_ordering_across_tiers(
|
|
|
441
609
|
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
442
610
|
is_claude_tier_enabled=True,
|
|
443
611
|
run_state_directory=run_state_directory,
|
|
444
|
-
max_turns=DEFAULT_SPAWN_MAX_TURNS,
|
|
445
612
|
)
|
|
446
613
|
|
|
447
614
|
all_tiers = [each_attempt.tier for each_attempt in spawn_outcome.all_attempts]
|
|
@@ -515,7 +682,6 @@ def test_preflight_fallthrough_skips_grok_runner(
|
|
|
515
682
|
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
516
683
|
is_claude_tier_enabled=False,
|
|
517
684
|
run_state_directory=run_state_directory,
|
|
518
|
-
max_turns=DEFAULT_SPAWN_MAX_TURNS,
|
|
519
685
|
)
|
|
520
686
|
|
|
521
687
|
assert call_log.grok_calls == 0
|
|
@@ -533,9 +699,15 @@ def test_detect_host_profile_is_consumed_not_reimplemented() -> None:
|
|
|
533
699
|
assert "THIRD_PARTY" not in source_text
|
|
534
700
|
|
|
535
701
|
|
|
536
|
-
def
|
|
702
|
+
def test_dispatcher_grok_invocation_carries_no_turn_cap(
|
|
537
703
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
538
704
|
) -> None:
|
|
705
|
+
"""The dispatcher hands the runner no turn cap; the timeout is the only bound.
|
|
706
|
+
|
|
707
|
+
::
|
|
708
|
+
|
|
709
|
+
grok tier kwargs ok: timeout_seconds present, max_turns absent
|
|
710
|
+
"""
|
|
539
711
|
prompt_file, working_directory, run_state_directory = _paths(tmp_path)
|
|
540
712
|
call_log = _install_seams(
|
|
541
713
|
monkeypatch,
|
|
@@ -559,7 +731,10 @@ def test_default_max_turns_reaches_grok_kwargs(
|
|
|
559
731
|
|
|
560
732
|
assert exit_code == SPAWN_SERVED_EXIT_CODE
|
|
561
733
|
assert call_log.grok_keyword_arguments is not None
|
|
562
|
-
assert
|
|
734
|
+
assert "max_turns" not in call_log.grok_keyword_arguments
|
|
735
|
+
assert call_log.grok_keyword_arguments["timeout_seconds"] == (
|
|
736
|
+
DEFAULT_WORKER_TIMEOUT_SECONDS
|
|
737
|
+
)
|
|
563
738
|
parsed_payload = json.loads(capsys.readouterr().out)
|
|
564
739
|
assert parsed_payload[RESULT_KEY_OK] is True
|
|
565
740
|
|
|
@@ -645,7 +820,6 @@ def test_default_role_maps_to_primary_agent_stem(
|
|
|
645
820
|
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
646
821
|
is_claude_tier_enabled=False,
|
|
647
822
|
run_state_directory=run_state_directory,
|
|
648
|
-
max_turns=DEFAULT_SPAWN_MAX_TURNS,
|
|
649
823
|
)
|
|
650
824
|
|
|
651
825
|
assert spawn_outcome.is_ok is True
|
|
@@ -674,7 +848,6 @@ def test_tier_three_argv_includes_agent_for_default_role(
|
|
|
674
848
|
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
675
849
|
is_claude_tier_enabled=False,
|
|
676
850
|
run_state_directory=run_state_directory,
|
|
677
|
-
max_turns=DEFAULT_SPAWN_MAX_TURNS,
|
|
678
851
|
)
|
|
679
852
|
|
|
680
853
|
assert spawn_outcome.tier_used == TIER_CLAUDE_HEADLESS
|
|
@@ -708,7 +881,6 @@ def test_large_prompt_stays_out_of_claude_argv(
|
|
|
708
881
|
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
709
882
|
is_claude_tier_enabled=False,
|
|
710
883
|
run_state_directory=run_state_directory,
|
|
711
|
-
max_turns=DEFAULT_SPAWN_MAX_TURNS,
|
|
712
884
|
)
|
|
713
885
|
|
|
714
886
|
assert spawn_outcome.tier_used == TIER_CLAUDE_HEADLESS
|
|
@@ -926,8 +1098,7 @@ def test_headless_chain_runner_lock_serializes_distinct_cwds(
|
|
|
926
1098
|
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
927
1099
|
is_claude_tier_enabled=False,
|
|
928
1100
|
run_state_directory=run_state_directory,
|
|
929
|
-
|
|
930
|
-
)
|
|
1101
|
+
)
|
|
931
1102
|
except (OSError, RuntimeError, ValueError, AssertionError) as raised_error:
|
|
932
1103
|
all_errors.append(raised_error)
|
|
933
1104
|
|
|
@@ -1005,7 +1176,6 @@ def test_usage_limit_fallover_delivers_full_prompt_to_each_binary(
|
|
|
1005
1176
|
timeout_seconds=DEFAULT_WORKER_TIMEOUT_SECONDS,
|
|
1006
1177
|
is_claude_tier_enabled=False,
|
|
1007
1178
|
run_state_directory=run_state_directory,
|
|
1008
|
-
max_turns=DEFAULT_SPAWN_MAX_TURNS,
|
|
1009
1179
|
)
|
|
1010
1180
|
|
|
1011
1181
|
assert prompt_text_by_command["claude"] == FIXTURE_PROMPT_TEXT
|