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
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
"""Named constants for the host-aware `/code-review` invoker.
|
|
1
|
+
"""Named constants for the host-aware `/code-review` invoker and review loop.
|
|
2
2
|
|
|
3
3
|
::
|
|
4
4
|
|
|
5
5
|
ALL_EFFORT_TOKENS_IN_ASCENDING_ORDER
|
|
6
6
|
ok: ("low", "medium", "high", "xhigh", "max")
|
|
7
7
|
flag: "ultra" (rejected; needs an interactive terminal)
|
|
8
|
+
ALL_FINDING_SEVERITIES
|
|
9
|
+
ok: ("blocker", "high", "medium", "low", "nit")
|
|
10
|
+
ALL_LOOP_TERMINALS
|
|
11
|
+
ok: ("clean", "nits_fixed", "advisor_blocked")
|
|
8
12
|
RECORD_STAMP_FLAG
|
|
9
13
|
ok: "--record-stamp"
|
|
10
14
|
|
|
11
15
|
Effort tokens re-export the hooks enforcement constants (single source).
|
|
12
|
-
Scalar flags, JSON keys,
|
|
16
|
+
Scalar flags, JSON keys, mint-loop messages, finding severity vocabulary,
|
|
17
|
+
reviewed-head counting, and loop terminal resolution live here.
|
|
13
18
|
"""
|
|
14
19
|
|
|
15
20
|
from __future__ import annotations
|
|
@@ -17,6 +22,7 @@ from __future__ import annotations
|
|
|
17
22
|
import importlib.util
|
|
18
23
|
import os
|
|
19
24
|
import sys
|
|
25
|
+
from collections.abc import Mapping, Sequence
|
|
20
26
|
from pathlib import Path
|
|
21
27
|
from types import ModuleType
|
|
22
28
|
|
|
@@ -223,3 +229,398 @@ STAMP_STORE_RECORD_CLEAN_STAMP_NAME: str = "record_clean_stamp"
|
|
|
223
229
|
|
|
224
230
|
STAMP_STORE_RESOLVE_REPO_ROOT_NAME: str = "resolve_repo_root"
|
|
225
231
|
"""Attribute name of the repo-root resolver on the stamp store module."""
|
|
232
|
+
|
|
233
|
+
SEVERITY_BLOCKER: str = "blocker"
|
|
234
|
+
"""Finding severity for a release-blocking defect."""
|
|
235
|
+
|
|
236
|
+
SEVERITY_HIGH: str = "high"
|
|
237
|
+
"""Finding severity for a high-impact defect that is not a release blocker."""
|
|
238
|
+
|
|
239
|
+
SEVERITY_MEDIUM: str = "medium"
|
|
240
|
+
"""Finding severity for a moderate maintainer-action defect."""
|
|
241
|
+
|
|
242
|
+
SEVERITY_LOW: str = "low"
|
|
243
|
+
"""Finding severity for a low-impact non-nit defect."""
|
|
244
|
+
|
|
245
|
+
SEVERITY_NIT: str = "nit"
|
|
246
|
+
"""Finding severity for a mechanical clarity, format, or typo fix only."""
|
|
247
|
+
|
|
248
|
+
ALL_FINDING_SEVERITIES: tuple[str, ...] = (
|
|
249
|
+
SEVERITY_BLOCKER,
|
|
250
|
+
SEVERITY_HIGH,
|
|
251
|
+
SEVERITY_MEDIUM,
|
|
252
|
+
SEVERITY_LOW,
|
|
253
|
+
SEVERITY_NIT,
|
|
254
|
+
)
|
|
255
|
+
"""Frozen severity vocabulary every retained finding must use."""
|
|
256
|
+
|
|
257
|
+
VERDICT_CONFIRMED: str = "CONFIRMED"
|
|
258
|
+
"""Verification verdict when the trigger and wrong outcome are named."""
|
|
259
|
+
|
|
260
|
+
VERDICT_PLAUSIBLE: str = "PLAUSIBLE"
|
|
261
|
+
"""Verification verdict when the mechanism is real and the trigger is uncertain."""
|
|
262
|
+
|
|
263
|
+
VERDICT_REFUTED: str = "REFUTED"
|
|
264
|
+
"""Verification verdict when the candidate is factually wrong or guarded."""
|
|
265
|
+
|
|
266
|
+
ALL_VERIFICATION_VERDICTS: tuple[str, ...] = (
|
|
267
|
+
VERDICT_CONFIRMED,
|
|
268
|
+
VERDICT_PLAUSIBLE,
|
|
269
|
+
VERDICT_REFUTED,
|
|
270
|
+
)
|
|
271
|
+
"""Frozen verification-verdict vocabulary from the medium verify phase."""
|
|
272
|
+
|
|
273
|
+
ALL_RETAINED_VERIFICATION_VERDICTS: tuple[str, ...] = (
|
|
274
|
+
VERDICT_CONFIRMED,
|
|
275
|
+
VERDICT_PLAUSIBLE,
|
|
276
|
+
)
|
|
277
|
+
"""Verdicts that keep a candidate in the retained findings list."""
|
|
278
|
+
|
|
279
|
+
TERMINAL_CLEAN: str = "clean"
|
|
280
|
+
"""Loop terminal when a reviewed head retains zero findings."""
|
|
281
|
+
|
|
282
|
+
TERMINAL_NITS_FIXED: str = "nits_fixed"
|
|
283
|
+
"""Loop terminal when every retained finding is a fixed nit after gates."""
|
|
284
|
+
|
|
285
|
+
TERMINAL_ADVISOR_BLOCKED: str = "advisor_blocked"
|
|
286
|
+
"""Loop terminal when classification needs an advisor that cannot be reached."""
|
|
287
|
+
|
|
288
|
+
ALL_LOOP_TERMINALS: tuple[str, ...] = (
|
|
289
|
+
TERMINAL_CLEAN,
|
|
290
|
+
TERMINAL_NITS_FIXED,
|
|
291
|
+
TERMINAL_ADVISOR_BLOCKED,
|
|
292
|
+
)
|
|
293
|
+
"""Frozen set of review-loop terminal statuses."""
|
|
294
|
+
|
|
295
|
+
FINDING_FIELD_SEVERITY: str = "severity"
|
|
296
|
+
"""Structured finding field that holds one of ``ALL_FINDING_SEVERITIES``."""
|
|
297
|
+
|
|
298
|
+
FINDING_FIELD_VERDICT: str = "verdict"
|
|
299
|
+
"""Structured finding field that holds a verification verdict."""
|
|
300
|
+
|
|
301
|
+
RESULT_KEY_TERMINAL: str = "terminal"
|
|
302
|
+
"""JSON result key naming the review-loop terminal status."""
|
|
303
|
+
|
|
304
|
+
RESULT_KEY_DRAFT_PRESERVED: str = "draft_preserved"
|
|
305
|
+
"""JSON result key holding whether the pull request stays draft."""
|
|
306
|
+
|
|
307
|
+
RESULT_KEY_REVIEWED_HEAD_COUNT: str = "reviewed_head_count"
|
|
308
|
+
"""JSON result key holding how many distinct heads the loop reviewed."""
|
|
309
|
+
|
|
310
|
+
RESULT_KEY_SURVIVING_FINDINGS: str = "surviving_findings"
|
|
311
|
+
"""JSON result key holding structured findings that remain at terminal."""
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def is_known_finding_severity(severity: str) -> bool:
|
|
315
|
+
"""Return whether ``severity`` is one of the frozen five tokens.
|
|
316
|
+
|
|
317
|
+
::
|
|
318
|
+
|
|
319
|
+
is_known_finding_severity("nit") # ok: True
|
|
320
|
+
is_known_finding_severity("P1") # flag: False
|
|
321
|
+
is_known_finding_severity("") # flag: False
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
severity: Candidate severity token from a structured finding.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
True when ``severity`` is in ``ALL_FINDING_SEVERITIES``.
|
|
328
|
+
"""
|
|
329
|
+
return severity in ALL_FINDING_SEVERITIES
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def is_nit_finding_severity(severity: str) -> bool:
|
|
333
|
+
"""Return whether ``severity`` is exactly the nit token.
|
|
334
|
+
|
|
335
|
+
::
|
|
336
|
+
|
|
337
|
+
is_nit_finding_severity("nit") # ok: True
|
|
338
|
+
is_nit_finding_severity("low") # flag: False
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
severity: Candidate severity token from a structured finding.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
True when ``severity`` equals ``SEVERITY_NIT``.
|
|
345
|
+
"""
|
|
346
|
+
return severity == SEVERITY_NIT
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def is_retained_verification_verdict(verdict: str) -> bool:
|
|
350
|
+
"""Return whether ``verdict`` keeps a finding in the retained set.
|
|
351
|
+
|
|
352
|
+
::
|
|
353
|
+
|
|
354
|
+
is_retained_verification_verdict("CONFIRMED") # ok: True
|
|
355
|
+
is_retained_verification_verdict("REFUTED") # flag: False
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
verdict: Verification vote from the medium verify phase.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
True when ``verdict`` is CONFIRMED or PLAUSIBLE.
|
|
362
|
+
"""
|
|
363
|
+
return verdict in ALL_RETAINED_VERIFICATION_VERDICTS
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def finding_carries_severity_and_verdict(
|
|
367
|
+
*,
|
|
368
|
+
severity: object,
|
|
369
|
+
verdict: object,
|
|
370
|
+
) -> bool:
|
|
371
|
+
"""Return whether one retained finding carries both required fields.
|
|
372
|
+
|
|
373
|
+
::
|
|
374
|
+
|
|
375
|
+
finding_carries_severity_and_verdict(
|
|
376
|
+
severity="high", verdict="CONFIRMED"
|
|
377
|
+
) # ok: True
|
|
378
|
+
finding_carries_severity_and_verdict(
|
|
379
|
+
severity="high", verdict=None
|
|
380
|
+
) # flag: False
|
|
381
|
+
|
|
382
|
+
A retained finding needs a known severity and a retained verification
|
|
383
|
+
verdict. Missing either field, or a REFUTED verdict, fails the contract.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
severity: Severity token from a structured finding, or missing value.
|
|
387
|
+
verdict: Verification verdict from a structured finding, or missing.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
True when severity and retained verdict are both present and valid.
|
|
391
|
+
"""
|
|
392
|
+
if not isinstance(severity, str) or not isinstance(verdict, str):
|
|
393
|
+
return False
|
|
394
|
+
if not is_known_finding_severity(severity):
|
|
395
|
+
return False
|
|
396
|
+
return is_retained_verification_verdict(verdict)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def all_findings_carry_severity_and_verdict(
|
|
400
|
+
all_findings: Sequence[Mapping[str, object]],
|
|
401
|
+
) -> bool:
|
|
402
|
+
"""Return whether every finding carries severity and a retained verdict.
|
|
403
|
+
|
|
404
|
+
::
|
|
405
|
+
|
|
406
|
+
all_findings_carry_severity_and_verdict([]) # ok: True
|
|
407
|
+
all_findings_carry_severity_and_verdict(
|
|
408
|
+
[{"severity": "nit", "verdict": "CONFIRMED"}]
|
|
409
|
+
) # ok: True
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
all_findings: Structured findings retained after verification.
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
True when every finding passes ``finding_carries_severity_and_verdict``.
|
|
416
|
+
"""
|
|
417
|
+
for each_finding in all_findings:
|
|
418
|
+
if not finding_carries_severity_and_verdict(
|
|
419
|
+
severity=each_finding.get(FINDING_FIELD_SEVERITY),
|
|
420
|
+
verdict=each_finding.get(FINDING_FIELD_VERDICT),
|
|
421
|
+
):
|
|
422
|
+
return False
|
|
423
|
+
return True
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def has_unclassified_finding(all_findings: Sequence[Mapping[str, object]]) -> bool:
|
|
427
|
+
"""Return whether any finding lacks a known severity token.
|
|
428
|
+
|
|
429
|
+
::
|
|
430
|
+
|
|
431
|
+
has_unclassified_finding([{"verdict": "CONFIRMED"}]) # ok: True
|
|
432
|
+
has_unclassified_finding(
|
|
433
|
+
[{"severity": "nit", "verdict": "CONFIRMED"}]
|
|
434
|
+
) # flag: False
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
all_findings: Structured findings under terminal evaluation.
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
True when any finding omits severity or uses an unknown token.
|
|
441
|
+
"""
|
|
442
|
+
for each_finding in all_findings:
|
|
443
|
+
severity = each_finding.get(FINDING_FIELD_SEVERITY)
|
|
444
|
+
if not isinstance(severity, str):
|
|
445
|
+
return True
|
|
446
|
+
if not is_known_finding_severity(severity):
|
|
447
|
+
return True
|
|
448
|
+
return False
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def has_non_nit_finding(all_findings: Sequence[Mapping[str, object]]) -> bool:
|
|
452
|
+
"""Return whether any finding carries a known non-nit severity.
|
|
453
|
+
|
|
454
|
+
::
|
|
455
|
+
|
|
456
|
+
has_non_nit_finding([{"severity": "high"}]) # ok: True
|
|
457
|
+
has_non_nit_finding([{"severity": "nit"}]) # flag: False
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
all_findings: Structured findings under terminal evaluation.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
True when any finding's severity is known and not ``nit``.
|
|
464
|
+
"""
|
|
465
|
+
for each_finding in all_findings:
|
|
466
|
+
severity = each_finding.get(FINDING_FIELD_SEVERITY)
|
|
467
|
+
if not isinstance(severity, str):
|
|
468
|
+
continue
|
|
469
|
+
if not is_known_finding_severity(severity):
|
|
470
|
+
continue
|
|
471
|
+
if not is_nit_finding_severity(severity):
|
|
472
|
+
return True
|
|
473
|
+
return False
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def is_nits_only_findings(all_findings: Sequence[Mapping[str, object]]) -> bool:
|
|
477
|
+
"""Return whether every finding is a classified nit and at least one exists.
|
|
478
|
+
|
|
479
|
+
::
|
|
480
|
+
|
|
481
|
+
is_nits_only_findings([{"severity": "nit"}]) # ok: True
|
|
482
|
+
is_nits_only_findings([]) # flag: False
|
|
483
|
+
is_nits_only_findings(
|
|
484
|
+
[{"severity": "nit"}, {"severity": "high"}]
|
|
485
|
+
) # flag: False
|
|
486
|
+
|
|
487
|
+
An empty list is clean, not nits-only. Unclassified findings fail the
|
|
488
|
+
nits-only check so they route to classification before a terminal.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
all_findings: Structured findings under terminal evaluation.
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
True when the list is non-empty, fully classified, and all nits.
|
|
495
|
+
"""
|
|
496
|
+
if not all_findings:
|
|
497
|
+
return False
|
|
498
|
+
if has_unclassified_finding(all_findings):
|
|
499
|
+
return False
|
|
500
|
+
return not has_non_nit_finding(all_findings)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def record_reviewed_head(
|
|
504
|
+
all_reviewed_head_shas: tuple[str, ...],
|
|
505
|
+
head_sha: str,
|
|
506
|
+
) -> tuple[str, ...]:
|
|
507
|
+
"""Append ``head_sha`` once when it is a new distinct head.
|
|
508
|
+
|
|
509
|
+
::
|
|
510
|
+
|
|
511
|
+
record_reviewed_head((), "aaa") # ok: ("aaa",)
|
|
512
|
+
record_reviewed_head(("aaa",), "aaa") # ok: ("aaa",) re-review
|
|
513
|
+
record_reviewed_head(("a", "b", "c"), "d") # ok: ("a", "b", "c", "d")
|
|
514
|
+
|
|
515
|
+
A re-review of the same head does not increment the count. There is no
|
|
516
|
+
head count limit — every new head is recorded.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
all_reviewed_head_shas: Ordered distinct heads already reviewed.
|
|
520
|
+
head_sha: Git head under review for this pass.
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
The prior tuple, or the prior tuple plus ``head_sha`` when new.
|
|
524
|
+
"""
|
|
525
|
+
if head_sha in all_reviewed_head_shas:
|
|
526
|
+
return all_reviewed_head_shas
|
|
527
|
+
return all_reviewed_head_shas + (head_sha,)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def resolve_review_loop_terminal(
|
|
531
|
+
*,
|
|
532
|
+
all_findings: Sequence[Mapping[str, object]],
|
|
533
|
+
reviewed_head_count: int,
|
|
534
|
+
is_gates_passed: bool,
|
|
535
|
+
is_nits_applied: bool,
|
|
536
|
+
is_advisor_unreachable: bool = False,
|
|
537
|
+
) -> str | None:
|
|
538
|
+
"""Resolve the review-loop terminal, or None when the loop continues.
|
|
539
|
+
|
|
540
|
+
::
|
|
541
|
+
|
|
542
|
+
resolve_review_loop_terminal(
|
|
543
|
+
all_findings=(), reviewed_head_count=1,
|
|
544
|
+
is_gates_passed=True, is_nits_applied=False,
|
|
545
|
+
) # ok: "clean"
|
|
546
|
+
resolve_review_loop_terminal(
|
|
547
|
+
all_findings=(), reviewed_head_count=1,
|
|
548
|
+
is_gates_passed=False, is_nits_applied=False,
|
|
549
|
+
) # flag: None (gates still open)
|
|
550
|
+
resolve_review_loop_terminal(
|
|
551
|
+
all_findings=[{"severity": "high", "verdict": "CONFIRMED"}],
|
|
552
|
+
reviewed_head_count=5, is_gates_passed=True, is_nits_applied=False,
|
|
553
|
+
) # flag: None (open non-nit work continues)
|
|
554
|
+
|
|
555
|
+
Empty findings return clean only when gates pass. Nits-only findings
|
|
556
|
+
return nits_fixed after each finding carries severity and a retained
|
|
557
|
+
verdict, the nits are applied, and gates pass. An unreachable advisor
|
|
558
|
+
needed for classification returns advisor_blocked. Open non-nit work
|
|
559
|
+
returns None so the caller re-enters. There is no head-count stop.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
all_findings: Structured findings retained for this head.
|
|
563
|
+
reviewed_head_count: Distinct heads reviewed so far, including this one.
|
|
564
|
+
is_gates_passed: Whether required checks passed for this decision.
|
|
565
|
+
is_nits_applied: Whether every nit on the target was fixed.
|
|
566
|
+
is_advisor_unreachable: Whether classification needs a missing advisor.
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
One of ``ALL_LOOP_TERMINALS``, or None when the loop continues.
|
|
570
|
+
"""
|
|
571
|
+
_ = reviewed_head_count
|
|
572
|
+
if is_advisor_unreachable and has_unclassified_finding(all_findings):
|
|
573
|
+
return TERMINAL_ADVISOR_BLOCKED
|
|
574
|
+
if not all_findings:
|
|
575
|
+
if is_gates_passed:
|
|
576
|
+
return TERMINAL_CLEAN
|
|
577
|
+
return None
|
|
578
|
+
if (
|
|
579
|
+
is_nits_only_findings(all_findings)
|
|
580
|
+
and all_findings_carry_severity_and_verdict(all_findings)
|
|
581
|
+
and is_nits_applied
|
|
582
|
+
and is_gates_passed
|
|
583
|
+
):
|
|
584
|
+
return TERMINAL_NITS_FIXED
|
|
585
|
+
return None
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def encode_review_loop_terminal_result(
|
|
589
|
+
*,
|
|
590
|
+
terminal: str,
|
|
591
|
+
all_surviving_findings: Sequence[Mapping[str, object]],
|
|
592
|
+
reviewed_head_count: int,
|
|
593
|
+
is_draft_preserved: bool,
|
|
594
|
+
) -> dict[str, object]:
|
|
595
|
+
"""Serialize a review-loop terminal for hand-off and reporting.
|
|
596
|
+
|
|
597
|
+
::
|
|
598
|
+
|
|
599
|
+
encode_review_loop_terminal_result(
|
|
600
|
+
terminal="advisor_blocked",
|
|
601
|
+
all_surviving_findings=[{"severity": "high", "verdict": "CONFIRMED"}],
|
|
602
|
+
reviewed_head_count=2,
|
|
603
|
+
is_draft_preserved=False,
|
|
604
|
+
)["draft_preserved"] # ok: True (blocked terminals force draft)
|
|
605
|
+
|
|
606
|
+
``advisor_blocked`` keeps the pull request draft even when the caller
|
|
607
|
+
passes a false draft flag. Other terminals use the caller's flag.
|
|
608
|
+
Every terminal uses the same JSON shape.
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
terminal: One of ``ALL_LOOP_TERMINALS``.
|
|
612
|
+
all_surviving_findings: Findings still open at the terminal.
|
|
613
|
+
reviewed_head_count: Distinct heads reviewed before this terminal.
|
|
614
|
+
is_draft_preserved: Whether the pull request stays in draft.
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
A JSON-ready mapping with terminal, draft flag, head count, findings.
|
|
618
|
+
"""
|
|
619
|
+
is_blocked_terminal = terminal == TERMINAL_ADVISOR_BLOCKED
|
|
620
|
+
should_preserve_draft = is_blocked_terminal or is_draft_preserved
|
|
621
|
+
return {
|
|
622
|
+
RESULT_KEY_TERMINAL: terminal,
|
|
623
|
+
RESULT_KEY_DRAFT_PRESERVED: should_preserve_draft,
|
|
624
|
+
RESULT_KEY_REVIEWED_HEAD_COUNT: reviewed_head_count,
|
|
625
|
+
RESULT_KEY_SURVIVING_FINDINGS: list(all_surviving_findings),
|
|
626
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Named constants for the host-neutral Grok run ledger and patch artifacts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
LEDGER_SCHEMA_VERSION: str = "1.0.0"
|
|
6
|
+
"""Schema version stamped on every ledger document and task record."""
|
|
7
|
+
|
|
8
|
+
LEDGER_FILENAME: str = "grok-run-ledger.json"
|
|
9
|
+
"""Default ledger filename under a run-state directory."""
|
|
10
|
+
|
|
11
|
+
PATCH_MANIFEST_FILENAME: str = "patch-manifest.json"
|
|
12
|
+
"""Default patch-manifest filename under a run-state directory."""
|
|
13
|
+
|
|
14
|
+
TASK_STATUS_PENDING: str = "pending"
|
|
15
|
+
"""Task is recorded and waiting for dispatch."""
|
|
16
|
+
|
|
17
|
+
TASK_STATUS_IN_PROGRESS: str = "in_progress"
|
|
18
|
+
"""Task has exactly one live owner and is being worked."""
|
|
19
|
+
|
|
20
|
+
TASK_STATUS_COMPLETED: str = "completed"
|
|
21
|
+
"""Task reached a successful terminal state."""
|
|
22
|
+
|
|
23
|
+
TASK_STATUS_ADVISOR_BLOCKED: str = "advisor_blocked"
|
|
24
|
+
"""Task stopped because the advisor bind or verdict failed closed."""
|
|
25
|
+
|
|
26
|
+
TASK_STATUS_PENDING_REVIEW: str = "pending_review"
|
|
27
|
+
"""Task was invalidated by snapshot drift and needs re-review."""
|
|
28
|
+
|
|
29
|
+
ALL_LEGAL_TASK_STATUSES: frozenset[str] = frozenset(
|
|
30
|
+
{
|
|
31
|
+
TASK_STATUS_PENDING,
|
|
32
|
+
TASK_STATUS_IN_PROGRESS,
|
|
33
|
+
TASK_STATUS_COMPLETED,
|
|
34
|
+
TASK_STATUS_ADVISOR_BLOCKED,
|
|
35
|
+
TASK_STATUS_PENDING_REVIEW,
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
"""Every legal task status the ledger accepts."""
|
|
39
|
+
|
|
40
|
+
UTF8_ENCODING: str = "utf-8"
|
|
41
|
+
"""Text encoding for ledger and patch-manifest files."""
|
|
42
|
+
|
|
43
|
+
JSON_INDENT: int = 2
|
|
44
|
+
"""Indent width for pretty-printed ledger and patch-manifest JSON."""
|
|
45
|
+
|
|
46
|
+
TEMPORARY_LEDGER_PREFIX: str = ".ledger-"
|
|
47
|
+
"""Prefix for atomic ledger temp files before replace."""
|
|
48
|
+
|
|
49
|
+
TEMPORARY_LEDGER_SUFFIX: str = ".tmp"
|
|
50
|
+
"""Suffix for atomic ledger temp files before replace."""
|
|
@@ -215,8 +215,8 @@ LAUNCH_FAILURE_STDERR_PREFIX: str = "failed to launch: "
|
|
|
215
215
|
KILL_GRACE_TIMEOUT_SECONDS: int = 10
|
|
216
216
|
"""Seconds to wait for a killed process to reap its pipes before giving up on its streams.
|
|
217
217
|
|
|
218
|
-
Gates the drain that follows a kill.
|
|
219
|
-
|
|
218
|
+
Gates the drain that follows a kill. The shared process-tree helper carries its
|
|
219
|
+
own bound on the kill command, so the two bound different operations.
|
|
220
220
|
"""
|
|
221
221
|
|
|
222
222
|
MAXIMUM_WORKER_TIMEOUT_SECONDS: int = 5400
|
|
@@ -235,28 +235,6 @@ MAXIMUM_WORKER_TIMEOUT_ERROR_TEMPLATE: str = (
|
|
|
235
235
|
)
|
|
236
236
|
"""Rejection message for an over-ceiling timeout, shared by the batch parse and the runner."""
|
|
237
237
|
|
|
238
|
-
WINDOWS_OS_NAME: str = "nt"
|
|
239
|
-
"""``os.name`` value that selects the Windows branch of the process-tree kill."""
|
|
240
|
-
|
|
241
|
-
WINDOWS_TASKKILL_COMMAND: str = "taskkill"
|
|
242
|
-
"""Windows command that ends a process by id."""
|
|
243
|
-
|
|
244
|
-
WINDOWS_TASKKILL_TREE_FLAG: str = "/T"
|
|
245
|
-
"""``taskkill`` flag that extends the kill to every descendant process."""
|
|
246
|
-
|
|
247
|
-
WINDOWS_TASKKILL_FORCE_FLAG: str = "/F"
|
|
248
|
-
"""``taskkill`` flag that forces termination rather than requesting it."""
|
|
249
|
-
|
|
250
|
-
WINDOWS_TASKKILL_PID_FLAG: str = "/PID"
|
|
251
|
-
"""``taskkill`` flag that names the target process id."""
|
|
252
|
-
|
|
253
|
-
PROCESS_TREE_KILL_TIMEOUT_SECONDS: int = 10
|
|
254
|
-
"""Seconds allowed for the tree-kill command itself before it is abandoned.
|
|
255
|
-
|
|
256
|
-
Gates the kill command. ``KILL_GRACE_TIMEOUT_SECONDS`` gates the drain that
|
|
257
|
-
follows a kill, so the two bound different operations.
|
|
258
|
-
"""
|
|
259
|
-
|
|
260
238
|
MIN_WORKER_TIMEOUT_SECONDS: int = 1
|
|
261
239
|
"""Minimum accepted worker timeout_seconds, in seconds.
|
|
262
240
|
|
|
@@ -396,6 +374,32 @@ WORKER_SPEC_IS_REPO_ONLY_KEY: str = "is_repo_only"
|
|
|
396
374
|
WORKER_SPEC_AGENT_NAME_KEY: str = "agent_name"
|
|
397
375
|
"""JSON key for one worker's optional agent definition name."""
|
|
398
376
|
|
|
377
|
+
ALL_KNOWN_WORKER_SPEC_KEYS: frozenset[str] = frozenset(
|
|
378
|
+
{
|
|
379
|
+
WORKER_SPEC_ROLE_NAME_KEY,
|
|
380
|
+
WORKER_SPEC_PROMPT_PARTS_KEY,
|
|
381
|
+
WORKER_SPEC_CWD_KEY,
|
|
382
|
+
WORKER_SPEC_TOOL_PROFILE_KEY,
|
|
383
|
+
WORKER_SPEC_TIMEOUT_KEY,
|
|
384
|
+
WORKER_SPEC_IS_REPO_ONLY_KEY,
|
|
385
|
+
WORKER_SPEC_AGENT_NAME_KEY,
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
"""Every JSON key a batch worker entry accepts.
|
|
389
|
+
|
|
390
|
+
A worker entry carrying any other key fails to load, so a key the launcher
|
|
391
|
+
would drop is named to the operator rather than passing for a setting that
|
|
392
|
+
took effect.
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
WORKER_SPEC_KEY_JOIN_SEPARATOR: str = ", "
|
|
396
|
+
"""Separator between key names listed in an unknown-worker-key error message."""
|
|
397
|
+
|
|
398
|
+
UNKNOWN_WORKER_KEY_ERROR_TEMPLATE: str = (
|
|
399
|
+
"unknown worker key(s): {unknown_keys}; accepted keys: {accepted_keys}"
|
|
400
|
+
)
|
|
401
|
+
"""Message raised when a worker entry carries a key outside the accepted set."""
|
|
402
|
+
|
|
399
403
|
CLI_BATCH_SPEC_FLAG: str = "--spec"
|
|
400
404
|
"""CLI flag that points the batch launcher at a JSON batch specification file."""
|
|
401
405
|
|
|
@@ -499,3 +503,107 @@ CLI_ENABLE_CLAUDE_TIER_FLAG: str = "--enable-claude-tier"
|
|
|
499
503
|
|
|
500
504
|
EMPTY_OUTPUT: str = ""
|
|
501
505
|
"""Empty captured output when no tier produced stdout."""
|
|
506
|
+
|
|
507
|
+
BATCH_SPEC_ADVISOR_KEY: str = "advisor"
|
|
508
|
+
"""JSON key for the optional batch-level worker-advisor block."""
|
|
509
|
+
|
|
510
|
+
ADVISOR_SPEC_LAUNCHER_KEY: str = "launcher"
|
|
511
|
+
"""JSON key for the lead-supplied advisor launcher executable name."""
|
|
512
|
+
|
|
513
|
+
ADVISOR_SPEC_MODEL_KEY: str = "model"
|
|
514
|
+
"""JSON key for the advisor model name."""
|
|
515
|
+
|
|
516
|
+
ADVISOR_SPEC_EFFORT_KEY: str = "effort"
|
|
517
|
+
"""JSON key for the advisor effort level."""
|
|
518
|
+
|
|
519
|
+
DEFAULT_ADVISOR_LAUNCHER_PLACEHOLDER: str = "ADVISOR_LAUNCHER_PLACEHOLDER"
|
|
520
|
+
"""Committed placeholder; real launcher arrives only from the lead's batch spec."""
|
|
521
|
+
|
|
522
|
+
DEFAULT_ADVISOR_MODEL: str = "opus"
|
|
523
|
+
"""Default advisor model when the batch advisor block omits model."""
|
|
524
|
+
|
|
525
|
+
DEFAULT_ADVISOR_EFFORT: str = "high"
|
|
526
|
+
"""Default advisor effort when the batch advisor block omits effort."""
|
|
527
|
+
|
|
528
|
+
MAXIMUM_WORKER_ADVISOR_CORRECTIONS: int = 3
|
|
529
|
+
"""Maximum CORRECTION/PLAN re-consults per worker before advisor_blocked."""
|
|
530
|
+
|
|
531
|
+
MAXIMUM_ADVISOR_TIMEOUT_SECONDS: int = 600
|
|
532
|
+
"""Timeout for one advisor launcher subprocess call, in seconds."""
|
|
533
|
+
|
|
534
|
+
ADVISOR_SIGNAL_ENDORSE: str = "ENDORSE"
|
|
535
|
+
"""Opening signal that accepts a worker report."""
|
|
536
|
+
|
|
537
|
+
ADVISOR_SIGNAL_CORRECTION: str = "CORRECTION"
|
|
538
|
+
"""Opening signal that requires a re-consult up to the correction cap."""
|
|
539
|
+
|
|
540
|
+
ADVISOR_SIGNAL_PLAN: str = "PLAN"
|
|
541
|
+
"""Opening signal treated like CORRECTION for re-consult counting."""
|
|
542
|
+
|
|
543
|
+
ADVISOR_SIGNAL_STOP: str = "STOP"
|
|
544
|
+
"""Opening signal that ends the worker as advisor_blocked immediately."""
|
|
545
|
+
|
|
546
|
+
ALL_KNOWN_ADVISOR_SIGNALS: frozenset[str] = frozenset(
|
|
547
|
+
{
|
|
548
|
+
ADVISOR_SIGNAL_ENDORSE,
|
|
549
|
+
ADVISOR_SIGNAL_CORRECTION,
|
|
550
|
+
ADVISOR_SIGNAL_PLAN,
|
|
551
|
+
ADVISOR_SIGNAL_STOP,
|
|
552
|
+
}
|
|
553
|
+
)
|
|
554
|
+
"""The four allowed first tokens of an advisor reply."""
|
|
555
|
+
|
|
556
|
+
CLASSIFICATION_ADVISOR_BLOCKED: str = "advisor_blocked"
|
|
557
|
+
"""Worker classification when advisor bind, resume, or verdict fails closed."""
|
|
558
|
+
|
|
559
|
+
PENDING_BIND_SENTINEL: str = "PENDING_BIND"
|
|
560
|
+
"""Unbound advisor session or signal sentinel; must fail closed before launch."""
|
|
561
|
+
|
|
562
|
+
ADVISOR_PROMPT_HEADER_TEMPLATE: str = (
|
|
563
|
+
"Worker advisor session: {session_id}\n"
|
|
564
|
+
"Advisor model: {model}\n"
|
|
565
|
+
"Advisor effort: {effort}\n"
|
|
566
|
+
"Report only after the same session endorses or corrections are applied.\n\n"
|
|
567
|
+
)
|
|
568
|
+
"""Prefix injected into each worker prompt after a successful pre-dispatch bind."""
|
|
569
|
+
|
|
570
|
+
SUMMARY_ADVISOR_SESSION_ID_KEY: str = "advisor_session_id"
|
|
571
|
+
"""Per-worker report JSON key for the unique advisor session id."""
|
|
572
|
+
|
|
573
|
+
SUMMARY_ADVISOR_SIGNAL_KEY: str = "advisor_completion_signal"
|
|
574
|
+
"""Per-worker report JSON key for the final advisor opening signal."""
|
|
575
|
+
|
|
576
|
+
SUMMARY_ADVISOR_LAUNCHER_KEY: str = "advisor_launcher"
|
|
577
|
+
"""Per-worker report JSON key for the spec-supplied launcher (runtime only)."""
|
|
578
|
+
|
|
579
|
+
ADVISOR_CLI_PRINT_FLAG: str = "-p"
|
|
580
|
+
"""Print/non-interactive flag passed to the advisor launcher."""
|
|
581
|
+
|
|
582
|
+
ADVISOR_CLI_MODEL_FLAG: str = "--model"
|
|
583
|
+
"""Model flag passed to the advisor launcher."""
|
|
584
|
+
|
|
585
|
+
ADVISOR_CLI_EFFORT_FLAG: str = "--effort"
|
|
586
|
+
"""Effort flag passed to the advisor launcher."""
|
|
587
|
+
|
|
588
|
+
ADVISOR_CLI_OUTPUT_FORMAT_FLAG: str = "--output-format"
|
|
589
|
+
"""Output-format flag passed to the advisor launcher."""
|
|
590
|
+
|
|
591
|
+
ADVISOR_CLI_OUTPUT_FORMAT_JSON: str = "json"
|
|
592
|
+
"""JSON output format value for advisor launcher calls."""
|
|
593
|
+
|
|
594
|
+
ADVISOR_CLI_RESUME_FLAG: str = "--resume"
|
|
595
|
+
"""Resume flag for post-report advisor consults."""
|
|
596
|
+
|
|
597
|
+
ADVISOR_BIND_PROMPT_TEMPLATE: str = (
|
|
598
|
+
"You are the unique worker advisor for role {role_name}. "
|
|
599
|
+
"Answer only. Open with exactly one of: ENDORSE | CORRECTION | PLAN | STOP. "
|
|
600
|
+
"Pre-dispatch: ENDORSE this worker assignment if safe, else CORRECTION."
|
|
601
|
+
)
|
|
602
|
+
"""Prompt body used for the pre-dispatch advisor bind."""
|
|
603
|
+
|
|
604
|
+
ADVISOR_VERDICT_PROMPT_TEMPLATE: str = (
|
|
605
|
+
"You are the unique worker advisor for role {role_name}, session {session_id}. "
|
|
606
|
+
"Answer only. Open with exactly one of: ENDORSE | CORRECTION | PLAN | STOP. "
|
|
607
|
+
"Post-report review of the worker report follows.\n\n{report_text}"
|
|
608
|
+
)
|
|
609
|
+
"""Prompt body used for the post-report advisor verdict."""
|