claude-dev-env 1.94.0 → 2.0.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/advisor/CLAUDE.md +2 -2
- package/_shared/advisor/advisor-protocol.md +41 -33
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +17 -13
- package/_shared/advisor/scripts/model_tier_run_validator.py +27 -18
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +95 -25
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +17 -17
- package/_shared/advisor/scripts/tier_model_ids.py +18 -18
- package/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +2 -1
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +253 -1980
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +32 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/__init__.py +7 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +268 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +172 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +70 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +326 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +85 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +331 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +369 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/conftest.py +14 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_added_line_maps.py +118 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +17 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_arguments.py +29 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +99 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_blob_readers.py +69 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_file_sets.py +137 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +116 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_violation_scoping.py +75 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_wrapper_plumb_check.py +49 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/violation_scoping.py +328 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/wrapper_plumb_check.py +206 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +24 -17
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +19 -2
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +278 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +6 -39
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +43 -0
- package/_shared/pr-loop/worker-spawn.md +186 -0
- package/agents/code-verifier.md +1 -1
- package/bin/CLAUDE.md +10 -1
- package/bin/ever-shipped-skills.mjs +73 -0
- package/bin/expand_home_directory_tokens.mjs +1 -1
- package/bin/install.mjs +140 -7
- package/bin/install.prune.test.mjs +457 -0
- package/docs/CODE_RULES.md +1 -1
- package/hooks/advisory/refactor_guard.py +3 -4
- package/hooks/blocking/CLAUDE.md +7 -1
- package/hooks/blocking/block_main_commit.py +2 -2
- package/hooks/blocking/claude_md_orphan_file_blocker.py +75 -699
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +28 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/orphan_blocker_constants.py +18 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/decision.py +81 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/references.py +307 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/scan_plan.py +124 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/subtree_scan.py +179 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/conftest.py +10 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_decision.py +34 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_references.py +42 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_scan_plan.py +27 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_subtree_scan.py +30 -0
- package/hooks/blocking/code_rules_boolean_mustcheck.py +1 -1
- package/hooks/blocking/code_rules_enforcer.py +4 -0
- package/hooks/blocking/code_rules_mock_completeness.py +1 -1
- package/hooks/blocking/code_rules_optional_params.py +2 -2
- package/hooks/blocking/code_rules_shared.py +83 -1
- package/hooks/blocking/code_rules_test_assertions.py +1 -1
- package/hooks/blocking/code_rules_test_layout.py +9 -3
- package/hooks/blocking/code_rules_typeddict_stub.py +1 -1
- package/hooks/blocking/gh_pr_author_enforcer.py +1 -1
- package/hooks/blocking/inventory_intent_records/CLAUDE.md +26 -0
- package/hooks/blocking/inventory_intent_records/__init__.py +1 -0
- package/hooks/blocking/inventory_intent_records/config/__init__.py +1 -0
- package/hooks/blocking/inventory_intent_records/config/intent_records_constants.py +20 -0
- package/hooks/blocking/inventory_intent_records/records.py +271 -0
- package/hooks/blocking/inventory_intent_records/tests/conftest.py +10 -0
- package/hooks/blocking/inventory_intent_records/tests/test_records.py +80 -0
- package/hooks/blocking/package_inventory_stale_blocker.py +54 -384
- package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +26 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/config/inventory_blocker_constants.py +16 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/decision.py +84 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/inventory_detection.py +307 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/conftest.py +10 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_decision.py +38 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_inventory_detection.py +61 -0
- package/hooks/blocking/pii_payload_scan.py +138 -42
- package/hooks/blocking/pii_prevention_blocker.py +185 -291
- package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +24 -0
- package/hooks/blocking/pii_prevention_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/pii_prevention_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/pii_prevention_blocker_parts/config/repository_resolution_constants.py +28 -0
- package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +214 -0
- package/hooks/blocking/pii_prevention_blocker_parts/repository_resolution.py +208 -0
- package/hooks/blocking/plain_language_blocker.py +138 -4
- package/hooks/blocking/pr_description_command_parser.py +8 -4
- package/hooks/blocking/precommit_code_rules_gate.py +3 -3
- package/hooks/blocking/sensitive_file_protector.py +114 -48
- package/hooks/blocking/tdd_enforcer.py +97 -601
- package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +30 -0
- package/hooks/blocking/tdd_enforcer_parts/__init__.py +1 -0
- package/hooks/blocking/tdd_enforcer_parts/candidate_paths.py +142 -0
- package/hooks/blocking/tdd_enforcer_parts/config/__init__.py +1 -0
- package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +32 -0
- package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +268 -0
- package/hooks/blocking/tdd_enforcer_parts/decisions.py +92 -0
- package/hooks/blocking/tdd_enforcer_parts/freshness.py +80 -0
- package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +63 -0
- package/hooks/blocking/tdd_enforcer_parts/path_classification.py +119 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/conftest.py +10 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_candidate_paths.py +31 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +30 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +34 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_freshness.py +28 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +48 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_path_classification.py +36 -0
- package/hooks/blocking/test_code_rules_enforcer_scratchpad.py +105 -0
- package/hooks/blocking/test_code_rules_shared.py +181 -0
- package/hooks/blocking/test_inventory_deadlock_resolution.py +154 -0
- package/hooks/blocking/test_pii_payload_scan.py +168 -0
- package/hooks/blocking/test_plain_language_blocker_allowlist.py +184 -0
- package/hooks/blocking/test_sensitive_file_protector.py +185 -0
- package/hooks/blocking/test_tdd_enforcer_restore.py +108 -0
- package/hooks/blocking/test_tdd_enforcer_scratchpad.py +105 -0
- package/hooks/blocking/tests/conftest.py +10 -0
- package/hooks/blocking/tests/test_pii_prevention_blocker.py +260 -0
- package/hooks/blocking/tests/test_repository_exemption.py +105 -0
- package/hooks/blocking/tests/test_repository_resolution.py +108 -0
- package/hooks/diagnostic/hook_log_extractor.py +12 -10
- package/hooks/git-hooks/post_commit.py +3 -4
- package/hooks/hooks_constants/CLAUDE.md +4 -2
- package/hooks/hooks_constants/banned_identifiers_constants.py +0 -1
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/harness_scratchpad_constants.py +17 -0
- package/hooks/hooks_constants/local_identity.py +59 -8
- package/hooks/hooks_constants/pii_prevention_constants.py +0 -6
- package/hooks/hooks_constants/plain_language_blocker_constants.py +5 -0
- package/hooks/hooks_constants/sensitive_file_protector_constants.py +42 -0
- package/hooks/hooks_constants/test_local_identity.py +105 -3
- package/hooks/pyproject.toml +52 -4
- package/hooks/session/plugin_data_dir_cleanup.py +0 -1
- package/hooks/validation/mypy_validator.py +2 -2
- package/hooks/validators/CLAUDE.md +1 -1
- package/hooks/validators/README.md +2 -0
- package/hooks/validators/health_check.py +1 -0
- package/hooks/validators/mypy_integration.py +2 -0
- package/hooks/validators/python_style_checks.py +114 -136
- package/hooks/validators/python_style_helpers.py +95 -0
- package/hooks/validators/ruff_integration.py +3 -0
- package/hooks/validators/test_python_style_checks.py +0 -164
- package/hooks/validators/test_python_style_checks_decorator_gap.py +119 -0
- package/hooks/validators/test_python_style_fixes.py +251 -0
- package/hooks/validators/test_python_style_helpers.py +125 -0
- package/hooks/workflow/auto_formatter.py +5 -4
- package/package.json +1 -1
- package/rules/CLAUDE.md +1 -0
- package/rules/anti-corollary-tests.md +69 -0
- package/rules/bdd.md +1 -3
- package/rules/code-reviews.md +1 -1
- package/rules/gh-paginate.md +1 -1
- package/rules/plain-language.md +2 -0
- package/scripts/CLAUDE.md +4 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +6 -4
- package/scripts/dev_env_scripts_constants/code_review_constants.py +71 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +435 -0
- package/scripts/dev_env_scripts_constants/timing.py +7 -1
- package/scripts/grok_headless_runner.py +294 -0
- package/scripts/grok_worker_preflight.py +410 -0
- package/scripts/invoke_code_review.py +463 -0
- package/scripts/resolve_worker_spawn.py +619 -0
- package/scripts/spawn_grok_batch.py +672 -0
- package/scripts/test_grok_headless_runner.py +626 -0
- package/scripts/test_grok_worker_preflight.py +1054 -0
- package/scripts/test_invoke_code_review.py +672 -0
- package/scripts/test_resolve_worker_spawn.py +1014 -0
- package/scripts/test_spawn_grok_batch.py +1017 -0
- package/skills/CLAUDE.md +6 -3
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +72 -13
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +121 -14
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +78 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +121 -0
- package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +196 -6
- package/skills/autoconverge/CLAUDE.md +3 -3
- package/skills/autoconverge/SKILL.md +9 -3
- package/skills/autoconverge/reference/CLAUDE.md +2 -2
- package/skills/autoconverge/reference/convergence.md +33 -11
- package/skills/autoconverge/reference/stop-conditions.md +16 -5
- package/skills/autoconverge/workflow/CLAUDE.md +2 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +7 -2
- package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +300 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +5 -5
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +29 -29
- package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.mjs +202 -18
- package/skills/bugteam/CLAUDE.md +2 -2
- package/skills/bugteam/CONSTRAINTS.md +3 -2
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +18 -13
- package/skills/bugteam/reference/README.md +2 -3
- package/skills/bugteam/reference/audit-and-teammates.md +215 -35
- package/skills/bugteam/reference/design-rationale.md +1 -1
- package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -2
- package/skills/closeout/SKILL.md +153 -0
- package/skills/closeout/reference/handoff-prompt-template.md +72 -0
- package/skills/closeout/reference/issue-body-templates.md +108 -0
- package/skills/closeout/reference/pii-redaction-checklist.md +36 -0
- package/skills/codex-review/CLAUDE.md +46 -0
- package/skills/codex-review/SKILL.md +181 -0
- package/skills/codex-review/reference/CLAUDE.md +15 -0
- package/skills/codex-review/reference/cli-contract.md +253 -0
- package/skills/codex-review/reference/loop-integration.md +118 -0
- package/skills/codex-review/scripts/codex_down_classifier.py +98 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/__init__.py +1 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/classifier_constants.py +35 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +86 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/findings_constants.py +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +45 -0
- package/skills/codex-review/scripts/codex_usage_probe.py +573 -0
- package/skills/codex-review/scripts/fixtures/auth_failure_synthetic.txt +1 -0
- package/skills/codex-review/scripts/fixtures/config_load_failure_v0.125.0.txt +1 -0
- package/skills/codex-review/scripts/fixtures/freeform_findings_v0.144.3.txt +6 -0
- package/skills/codex-review/scripts/fixtures/model_rejection_v0.125.0.jsonl +5 -0
- package/skills/codex-review/scripts/fixtures/structured_findings.txt +13 -0
- package/skills/codex-review/scripts/fixtures/success_stream_v0.144.3.jsonl +6 -0
- package/skills/codex-review/scripts/fixtures/unknown_failure_synthetic.txt +1 -0
- package/skills/codex-review/scripts/fixtures/usage_limit_synthetic.txt +1 -0
- package/skills/codex-review/scripts/parse_codex_findings.py +207 -0
- package/skills/codex-review/scripts/run_codex_review.py +415 -0
- package/skills/codex-review/scripts/test_codex_down_classifier.py +143 -0
- package/skills/codex-review/scripts/test_codex_usage_probe.py +678 -0
- package/skills/codex-review/scripts/test_parse_codex_findings.py +130 -0
- package/skills/codex-review/scripts/test_run_codex_review.py +812 -0
- package/skills/codex-review/test_skill_scaffold.py +192 -0
- package/skills/grok-spawn/CLAUDE.md +28 -0
- package/skills/grok-spawn/SKILL.md +226 -0
- package/skills/grok-spawn/reference/flag-profiles.md +132 -0
- package/skills/grok-spawn/reference/worker-briefs.md +152 -0
- package/skills/grokify/SKILL.md +9 -1
- package/skills/grokify/capability-claims.test.mjs +28 -0
- package/skills/grokify/evals/README.md +72 -0
- package/skills/grokify/evals/parse-payload.test.mjs +171 -0
- package/skills/grokify/evals/run-capability-evals.mjs +545 -0
- package/skills/orchestrator/SKILL.md +32 -22
- package/skills/orchestrator-refresh/SKILL.md +12 -8
- package/skills/pr-converge/CLAUDE.md +1 -1
- package/skills/pr-converge/SKILL.md +34 -13
- package/skills/pr-converge/reference/convergence-gates.md +42 -15
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +130 -42
- package/skills/pr-converge/reference/state-schema.md +10 -0
- package/skills/pr-converge/scripts/CLAUDE.md +2 -0
- package/skills/pr-converge/scripts/_pr_converge_path_setup.py +5 -1
- package/skills/pr-converge/scripts/check_convergence.py +605 -29
- package/skills/pr-converge/scripts/check_convergence_availability.py +232 -0
- package/skills/pr-converge/scripts/check_convergence_gates.py +279 -235
- package/skills/pr-converge/scripts/check_convergence_thread_gates.py +1 -1
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -2
- package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +4 -0
- package/skills/pr-converge/scripts/test_check_convergence.py +71 -3
- package/skills/pr-converge/scripts/test_check_convergence_availability.py +326 -0
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -0
- package/skills/pr-converge/scripts/test_check_convergence_contract.py +89 -17
- package/skills/pr-converge/scripts/test_check_convergence_fixture.py +179 -0
- package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -68
- package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +24 -0
- package/skills/pr-converge/test_step5_host_branch.py +106 -0
- package/skills/pr-fix-protocol/SKILL.md +65 -0
- package/skills/pr-loop-cloud-transport/SKILL.md +2 -0
- package/skills/reviewer-gates/SKILL.md +7 -5
- package/skills/skill-builder/references/skill-modularity.md +1 -1
- package/skills/team-advisor/SKILL.md +16 -12
- package/system-prompts/software-engineer.xml +7 -6
- package/hooks/validators/test_verify_paths.py +0 -32
- package/hooks/validators/verify_paths.py +0 -57
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Host-aware helper that runs the built-in review slash command on opus.
|
|
3
|
+
|
|
4
|
+
Mode decision::
|
|
5
|
+
|
|
6
|
+
host=Claude, session_model=opus -> mode in_session (skill runs slash cmd)
|
|
7
|
+
host=Claude, session_model=sonnet -> mode chain (headless opus spawn)
|
|
8
|
+
host=ThirdParty, any model -> mode chain
|
|
9
|
+
|
|
10
|
+
Chain mode runs ``run_claude`` with argv from ``build_code_review_arguments``
|
|
11
|
+
(single-turn prompt, model opus, json output, bypassPermissions).
|
|
12
|
+
|
|
13
|
+
cwd is the PR working tree and stdin is redirected from the empty stream so
|
|
14
|
+
the spawn does not wait for interactive input. Result JSON on stdout only::
|
|
15
|
+
|
|
16
|
+
{"mode", "served_command", "returncode", "dirty_tree"}
|
|
17
|
+
|
|
18
|
+
A clean stamp requires a successful serve (``returncode == 0``, and for chain
|
|
19
|
+
mode a non-null ``served_command``) plus ``dirty_tree`` false. A failed chain
|
|
20
|
+
or config/host error leaves the tree clean but must not advance past
|
|
21
|
+
CODE_REVIEW — use ``is_code_review_clean_stamp_allowed``.
|
|
22
|
+
|
|
23
|
+
Import ``invoke_code_review`` for the outcome object, or run as a CLI::
|
|
24
|
+
|
|
25
|
+
python invoke_code_review.py --cwd <dir> --session-model <alias>
|
|
26
|
+
[--timeout-seconds N]
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import json
|
|
33
|
+
import subprocess
|
|
34
|
+
import sys
|
|
35
|
+
import threading
|
|
36
|
+
from collections.abc import Callable, Sequence
|
|
37
|
+
from dataclasses import dataclass
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
if str(Path(__file__).resolve().parent) not in sys.path:
|
|
41
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
42
|
+
|
|
43
|
+
_advisor_scripts_path = str(
|
|
44
|
+
Path(__file__).resolve().parent.parent / "_shared" / "advisor" / "scripts"
|
|
45
|
+
)
|
|
46
|
+
if _advisor_scripts_path not in sys.path:
|
|
47
|
+
sys.path.insert(0, _advisor_scripts_path)
|
|
48
|
+
|
|
49
|
+
from advisor_scripts_constants.model_tier_run_validator_constants import ( # noqa: E402
|
|
50
|
+
HOST_PROFILE_CLAUDE,
|
|
51
|
+
)
|
|
52
|
+
import claude_chain_runner as chain_runner # noqa: E402
|
|
53
|
+
from claude_chain_runner import ( # noqa: E402
|
|
54
|
+
ChainConfigurationError,
|
|
55
|
+
ChainInvocationOutcome,
|
|
56
|
+
run_claude,
|
|
57
|
+
)
|
|
58
|
+
from dev_env_scripts_constants.claude_chain_constants import ( # noqa: E402
|
|
59
|
+
CHAIN_CONFIG_ERROR_EXIT_CODE,
|
|
60
|
+
)
|
|
61
|
+
from dev_env_scripts_constants.code_review_constants import ( # noqa: E402
|
|
62
|
+
CLI_SESSION_MODEL_FLAG,
|
|
63
|
+
CODE_REVIEW_MODEL_ALIAS,
|
|
64
|
+
CODE_REVIEW_PROMPT,
|
|
65
|
+
GIT_BINARY,
|
|
66
|
+
GIT_PORCELAIN_FLAG,
|
|
67
|
+
GIT_STATUS_SUBCOMMAND,
|
|
68
|
+
HOST_PROFILE_ERROR_RETURNCODE,
|
|
69
|
+
IN_SESSION_RETURNCODE,
|
|
70
|
+
MODE_CHAIN,
|
|
71
|
+
MODE_IN_SESSION,
|
|
72
|
+
PERMISSION_MODE_BYPASS,
|
|
73
|
+
PERMISSION_MODE_FLAG,
|
|
74
|
+
RESULT_KEY_DIRTY_TREE,
|
|
75
|
+
RESULT_KEY_MODE,
|
|
76
|
+
RESULT_KEY_RETURNCODE,
|
|
77
|
+
RESULT_KEY_SERVED_COMMAND,
|
|
78
|
+
SUCCESSFUL_REVIEW_RETURNCODE,
|
|
79
|
+
)
|
|
80
|
+
from dev_env_scripts_constants.grok_worker_constants import ( # noqa: E402
|
|
81
|
+
CLI_TIMEOUT_FLAG,
|
|
82
|
+
CWD_FLAG,
|
|
83
|
+
MODEL_FLAG,
|
|
84
|
+
OUTPUT_FORMAT_FLAG,
|
|
85
|
+
OUTPUT_FORMAT_JSON,
|
|
86
|
+
SINGLE_TURN_FLAG,
|
|
87
|
+
)
|
|
88
|
+
from dev_env_scripts_constants.timing import ( # noqa: E402
|
|
89
|
+
DEFAULT_CODE_REVIEW_TIMEOUT_SECONDS,
|
|
90
|
+
)
|
|
91
|
+
from tier_model_ids import detect_host_profile # noqa: E402
|
|
92
|
+
|
|
93
|
+
_CHAIN_RUNNER_LOCK = threading.Lock()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _chain_runner_lock() -> threading.Lock:
|
|
97
|
+
"""Return the module lock that serializes chain-runner stdin/cwd swaps."""
|
|
98
|
+
return _CHAIN_RUNNER_LOCK
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class CodeReviewOutcome:
|
|
103
|
+
"""Outcome of a host-aware code-review invocation.
|
|
104
|
+
|
|
105
|
+
``mode`` is ``in_session`` when the skill should run the slash command
|
|
106
|
+
itself, or ``chain`` when a headless spawn already ran. ``served_command``
|
|
107
|
+
names the chain binary that served a chain run, or ``None`` otherwise.
|
|
108
|
+
``is_dirty_tree`` is True when ``git status --porcelain`` is non-empty
|
|
109
|
+
after a chain run (fixes applied).
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
mode: str
|
|
113
|
+
served_command: str | None
|
|
114
|
+
returncode: int
|
|
115
|
+
is_dirty_tree: bool
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
review_claude_runner = run_claude
|
|
119
|
+
review_host_profile_detector = detect_host_profile
|
|
120
|
+
review_git_status_runner = subprocess.run
|
|
121
|
+
|
|
122
|
+
TextCapturingSubprocessRunner = Callable[
|
|
123
|
+
...,
|
|
124
|
+
subprocess.CompletedProcess[str],
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def is_opus_session_model(session_model: str) -> bool:
|
|
129
|
+
"""Return True when *session_model* is the opus short alias (any letter case).
|
|
130
|
+
|
|
131
|
+
::
|
|
132
|
+
|
|
133
|
+
is_opus_session_model("opus") # ok: True
|
|
134
|
+
is_opus_session_model("Opus") # ok: True
|
|
135
|
+
is_opus_session_model("sonnet") # ok: False
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
session_model: Caller-stated session model short alias.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
True when the normalized alias matches the code-review model pin.
|
|
142
|
+
"""
|
|
143
|
+
return session_model.strip().lower() == CODE_REVIEW_MODEL_ALIAS
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def decide_review_mode(*, host_profile: str, session_model: str) -> str:
|
|
147
|
+
"""Return ``in_session`` or ``chain`` from host profile and session model.
|
|
148
|
+
|
|
149
|
+
::
|
|
150
|
+
|
|
151
|
+
decide_review_mode(host_profile="Claude", session_model="opus")
|
|
152
|
+
# ok: "in_session"
|
|
153
|
+
decide_review_mode(host_profile="Claude", session_model="sonnet")
|
|
154
|
+
# ok: "chain"
|
|
155
|
+
decide_review_mode(host_profile="ThirdParty", session_model="opus")
|
|
156
|
+
# ok: "chain"
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
host_profile: Detected host profile (``Claude`` or ``ThirdParty``).
|
|
160
|
+
session_model: Caller-stated session model short alias.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
``MODE_IN_SESSION`` only for Claude host on opus; otherwise ``MODE_CHAIN``.
|
|
164
|
+
"""
|
|
165
|
+
is_claude_host = host_profile == HOST_PROFILE_CLAUDE
|
|
166
|
+
if is_claude_host and is_opus_session_model(session_model):
|
|
167
|
+
return MODE_IN_SESSION
|
|
168
|
+
return MODE_CHAIN
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def build_code_review_arguments() -> list[str]:
|
|
172
|
+
"""Return the argv tokens passed to ``run_claude`` for a chain review.
|
|
173
|
+
|
|
174
|
+
::
|
|
175
|
+
|
|
176
|
+
build_code_review_arguments()
|
|
177
|
+
# ok: ["-p", CODE_REVIEW_PROMPT, "--model", "opus", ...]
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Ordered claude CLI arguments for the headless opus review slash command.
|
|
181
|
+
"""
|
|
182
|
+
return [
|
|
183
|
+
SINGLE_TURN_FLAG,
|
|
184
|
+
CODE_REVIEW_PROMPT,
|
|
185
|
+
MODEL_FLAG,
|
|
186
|
+
CODE_REVIEW_MODEL_ALIAS,
|
|
187
|
+
OUTPUT_FORMAT_FLAG,
|
|
188
|
+
OUTPUT_FORMAT_JSON,
|
|
189
|
+
PERMISSION_MODE_FLAG,
|
|
190
|
+
PERMISSION_MODE_BYPASS,
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def is_working_tree_dirty(working_directory: Path) -> bool:
|
|
195
|
+
"""Return True when the tree is dirty or ``git status`` fails.
|
|
196
|
+
|
|
197
|
+
::
|
|
198
|
+
|
|
199
|
+
is_working_tree_dirty(clean_repo) # ok: False
|
|
200
|
+
is_working_tree_dirty(dirty_repo) # ok: True
|
|
201
|
+
is_working_tree_dirty(broken_git) # ok: True (non-zero status)
|
|
202
|
+
|
|
203
|
+
A non-zero ``git status`` return code must not report clean: the caller
|
|
204
|
+
treats an unknown tree as dirty so a clean stamp cannot bypass the gate.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
working_directory: Git working tree to inspect.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
True when porcelain output is non-empty, or when git status exits
|
|
211
|
+
non-zero.
|
|
212
|
+
"""
|
|
213
|
+
completion = review_git_status_runner(
|
|
214
|
+
[GIT_BINARY, GIT_STATUS_SUBCOMMAND, GIT_PORCELAIN_FLAG],
|
|
215
|
+
cwd=str(working_directory),
|
|
216
|
+
capture_output=True,
|
|
217
|
+
text=True,
|
|
218
|
+
check=False,
|
|
219
|
+
)
|
|
220
|
+
if completion.returncode != 0:
|
|
221
|
+
return True
|
|
222
|
+
return bool(completion.stdout.strip())
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def is_successful_code_review(review_outcome: CodeReviewOutcome) -> bool:
|
|
226
|
+
"""Return True when the invocation completed a successful review serve.
|
|
227
|
+
|
|
228
|
+
::
|
|
229
|
+
|
|
230
|
+
is_successful_code_review(in_session_ok) # ok: True
|
|
231
|
+
is_successful_code_review(chain_served) # ok: True
|
|
232
|
+
is_successful_code_review(chain_failed) # ok: False
|
|
233
|
+
|
|
234
|
+
Success requires ``returncode == 0``. Chain mode also requires a non-null
|
|
235
|
+
``served_command``. In-session mode hands the slash command to the skill,
|
|
236
|
+
so ``served_command`` stays null by design.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
review_outcome: Structured outcome from ``invoke_code_review``.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
True when the outcome is a successful serve that may stamp clean if
|
|
243
|
+
the working tree is also clean.
|
|
244
|
+
"""
|
|
245
|
+
if review_outcome.returncode != SUCCESSFUL_REVIEW_RETURNCODE:
|
|
246
|
+
return False
|
|
247
|
+
if review_outcome.mode == MODE_CHAIN and review_outcome.served_command is None:
|
|
248
|
+
return False
|
|
249
|
+
return True
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def is_code_review_clean_stamp_allowed(review_outcome: CodeReviewOutcome) -> bool:
|
|
253
|
+
"""Return True when the outcome may set ``code_review_clean_at``.
|
|
254
|
+
|
|
255
|
+
::
|
|
256
|
+
|
|
257
|
+
is_code_review_clean_stamp_allowed(chain_clean_ok) # ok: True
|
|
258
|
+
is_code_review_clean_stamp_allowed(chain_failed) # ok: False
|
|
259
|
+
is_code_review_clean_stamp_allowed(chain_dirty_ok) # ok: False
|
|
260
|
+
|
|
261
|
+
Clean stamp requires a successful serve and a clean working tree.
|
|
262
|
+
``dirty_tree`` alone is not enough: a failed chain leaves the tree clean
|
|
263
|
+
and must stay in CODE_REVIEW.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
review_outcome: Structured outcome from ``invoke_code_review``.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
True only when the review succeeded and ``is_dirty_tree`` is False.
|
|
270
|
+
"""
|
|
271
|
+
if not is_successful_code_review(review_outcome):
|
|
272
|
+
return False
|
|
273
|
+
if review_outcome.is_dirty_tree:
|
|
274
|
+
return False
|
|
275
|
+
return True
|
|
276
|
+
|
|
277
|
+
def _run_claude_with_empty_stdin(
|
|
278
|
+
all_claude_arguments: list[str],
|
|
279
|
+
*,
|
|
280
|
+
timeout_seconds: int,
|
|
281
|
+
working_directory: Path,
|
|
282
|
+
) -> ChainInvocationOutcome:
|
|
283
|
+
working_directory_path = str(working_directory)
|
|
284
|
+
with _CHAIN_RUNNER_LOCK:
|
|
285
|
+
previous_runner = chain_runner.chain_subprocess_runner
|
|
286
|
+
|
|
287
|
+
def _runner_with_empty_stdin(
|
|
288
|
+
all_invocation_tokens: Sequence[str],
|
|
289
|
+
*all_positionals: object,
|
|
290
|
+
**all_keywords: object,
|
|
291
|
+
) -> subprocess.CompletedProcess[str]:
|
|
292
|
+
del all_positionals
|
|
293
|
+
maybe_timeout = all_keywords.get("timeout")
|
|
294
|
+
timeout_for_run: float | None
|
|
295
|
+
if isinstance(maybe_timeout, (int, float)):
|
|
296
|
+
timeout_for_run = float(maybe_timeout)
|
|
297
|
+
else:
|
|
298
|
+
timeout_for_run = None
|
|
299
|
+
completed_process: subprocess.CompletedProcess[str] = previous_runner(
|
|
300
|
+
all_invocation_tokens,
|
|
301
|
+
capture_output=True,
|
|
302
|
+
text=True,
|
|
303
|
+
timeout=timeout_for_run,
|
|
304
|
+
check=False,
|
|
305
|
+
stdin=subprocess.DEVNULL,
|
|
306
|
+
cwd=working_directory_path,
|
|
307
|
+
)
|
|
308
|
+
return completed_process
|
|
309
|
+
|
|
310
|
+
empty_stdin_runner: TextCapturingSubprocessRunner = _runner_with_empty_stdin
|
|
311
|
+
setattr(chain_runner, "chain_subprocess_runner", empty_stdin_runner)
|
|
312
|
+
try:
|
|
313
|
+
return review_claude_runner(
|
|
314
|
+
all_claude_arguments, timeout_seconds=timeout_seconds
|
|
315
|
+
)
|
|
316
|
+
finally:
|
|
317
|
+
setattr(chain_runner, "chain_subprocess_runner", previous_runner)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _in_session_outcome() -> CodeReviewOutcome:
|
|
321
|
+
return CodeReviewOutcome(
|
|
322
|
+
mode=MODE_IN_SESSION,
|
|
323
|
+
served_command=None,
|
|
324
|
+
returncode=IN_SESSION_RETURNCODE,
|
|
325
|
+
is_dirty_tree=False,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _chain_outcome(
|
|
330
|
+
chain_outcome: ChainInvocationOutcome,
|
|
331
|
+
*,
|
|
332
|
+
working_directory: Path,
|
|
333
|
+
) -> CodeReviewOutcome:
|
|
334
|
+
return CodeReviewOutcome(
|
|
335
|
+
mode=MODE_CHAIN,
|
|
336
|
+
served_command=chain_outcome.served_command,
|
|
337
|
+
returncode=chain_outcome.returncode,
|
|
338
|
+
is_dirty_tree=is_working_tree_dirty(working_directory),
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _failure_code_review_outcome(returncode: int) -> CodeReviewOutcome:
|
|
343
|
+
return CodeReviewOutcome(
|
|
344
|
+
mode=MODE_CHAIN,
|
|
345
|
+
served_command=None,
|
|
346
|
+
returncode=returncode,
|
|
347
|
+
is_dirty_tree=False,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def invoke_code_review(
|
|
351
|
+
*,
|
|
352
|
+
working_directory: Path,
|
|
353
|
+
session_model: str,
|
|
354
|
+
timeout_seconds: int,
|
|
355
|
+
) -> CodeReviewOutcome:
|
|
356
|
+
"""Run or hand off ``/code-review`` based on host profile and session model.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
working_directory: PR working tree used as cwd for the chain spawn.
|
|
360
|
+
session_model: Caller-stated session model short alias (for example
|
|
361
|
+
``opus`` or ``sonnet``).
|
|
362
|
+
timeout_seconds: Timeout applied to each chain binary invocation.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Structured outcome including mode, served binary, return code, and
|
|
366
|
+
whether the working tree is dirty after a chain run.
|
|
367
|
+
"""
|
|
368
|
+
host_profile = review_host_profile_detector()
|
|
369
|
+
review_mode = decide_review_mode(
|
|
370
|
+
host_profile=host_profile,
|
|
371
|
+
session_model=session_model,
|
|
372
|
+
)
|
|
373
|
+
if review_mode == MODE_IN_SESSION:
|
|
374
|
+
return _in_session_outcome()
|
|
375
|
+
all_claude_arguments = build_code_review_arguments()
|
|
376
|
+
chain_outcome = _run_claude_with_empty_stdin(
|
|
377
|
+
all_claude_arguments,
|
|
378
|
+
timeout_seconds=timeout_seconds,
|
|
379
|
+
working_directory=working_directory,
|
|
380
|
+
)
|
|
381
|
+
return _chain_outcome(chain_outcome, working_directory=working_directory)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def encode_code_review_outcome(
|
|
385
|
+
review_outcome: CodeReviewOutcome,
|
|
386
|
+
) -> dict[str, object]:
|
|
387
|
+
"""Encode a code-review outcome as the JSON-serializable payload.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
review_outcome: The invoker outcome to encode.
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
A plain dict matching the CLI JSON shape.
|
|
394
|
+
"""
|
|
395
|
+
return {
|
|
396
|
+
RESULT_KEY_MODE: review_outcome.mode,
|
|
397
|
+
RESULT_KEY_SERVED_COMMAND: review_outcome.served_command,
|
|
398
|
+
RESULT_KEY_RETURNCODE: review_outcome.returncode,
|
|
399
|
+
RESULT_KEY_DIRTY_TREE: review_outcome.is_dirty_tree,
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _build_argument_parser() -> argparse.ArgumentParser:
|
|
404
|
+
parser = argparse.ArgumentParser(
|
|
405
|
+
description=(
|
|
406
|
+
"Run built-in /code-review at high effort on opus, host-aware."
|
|
407
|
+
)
|
|
408
|
+
)
|
|
409
|
+
parser.add_argument(
|
|
410
|
+
CWD_FLAG,
|
|
411
|
+
dest="working_directory",
|
|
412
|
+
required=True,
|
|
413
|
+
type=Path,
|
|
414
|
+
help="PR working tree used as cwd for the chain spawn.",
|
|
415
|
+
)
|
|
416
|
+
parser.add_argument(
|
|
417
|
+
CLI_SESSION_MODEL_FLAG,
|
|
418
|
+
dest="session_model",
|
|
419
|
+
required=True,
|
|
420
|
+
help="Caller session model short alias (for example opus or sonnet).",
|
|
421
|
+
)
|
|
422
|
+
parser.add_argument(
|
|
423
|
+
CLI_TIMEOUT_FLAG,
|
|
424
|
+
dest="timeout_seconds",
|
|
425
|
+
type=int,
|
|
426
|
+
default=DEFAULT_CODE_REVIEW_TIMEOUT_SECONDS,
|
|
427
|
+
help="Timeout in seconds applied to each chain binary invocation.",
|
|
428
|
+
)
|
|
429
|
+
return parser
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def main(all_command_arguments: list[str]) -> int:
|
|
433
|
+
"""Run the invoker for CLI arguments and print the JSON outcome.
|
|
434
|
+
|
|
435
|
+
``ChainConfigurationError`` and host ``ValueError`` still emit result JSON
|
|
436
|
+
on stdout (no traceback-only failure). The non-zero return code and null
|
|
437
|
+
``served_command`` block a clean stamp.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
all_command_arguments: The argument vector after the program name.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
The outcome return code (``0`` for in-session; chain return code otherwise).
|
|
444
|
+
"""
|
|
445
|
+
parser = _build_argument_parser()
|
|
446
|
+
parsed_arguments = parser.parse_args(all_command_arguments)
|
|
447
|
+
try:
|
|
448
|
+
review_outcome = invoke_code_review(
|
|
449
|
+
working_directory=parsed_arguments.working_directory,
|
|
450
|
+
session_model=parsed_arguments.session_model,
|
|
451
|
+
timeout_seconds=parsed_arguments.timeout_seconds,
|
|
452
|
+
)
|
|
453
|
+
except ChainConfigurationError:
|
|
454
|
+
review_outcome = _failure_code_review_outcome(CHAIN_CONFIG_ERROR_EXIT_CODE)
|
|
455
|
+
except ValueError:
|
|
456
|
+
review_outcome = _failure_code_review_outcome(HOST_PROFILE_ERROR_RETURNCODE)
|
|
457
|
+
encoded_payload = encode_code_review_outcome(review_outcome)
|
|
458
|
+
sys.stdout.write(json.dumps(encoded_payload) + "\n")
|
|
459
|
+
return review_outcome.returncode
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
if __name__ == "__main__":
|
|
463
|
+
sys.exit(main(sys.argv[1:]))
|