claude-dev-env 1.92.1 → 1.93.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 +6 -2
- package/_shared/CLAUDE.md +2 -1
- package/_shared/advisor/CLAUDE.md +21 -0
- package/_shared/advisor/advisor-protocol.md +142 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/__init__.py +1 -0
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +86 -0
- package/_shared/advisor/scripts/model_tier_run_validator.py +246 -0
- package/_shared/advisor/scripts/pyproject.toml +3 -0
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +302 -0
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +180 -0
- package/_shared/advisor/scripts/tier_model_ids.py +164 -0
- package/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/precatch-rubric.md +65 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +137 -5
- package/_shared/pr-loop/scripts/post_audit_thread.py +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +16 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +2 -1
- package/_shared/pr-loop/scripts/reviewer_availability.py +42 -12
- package/_shared/pr-loop/scripts/reviews_disabled.py +65 -21
- package/_shared/pr-loop/scripts/terminology_sweep.py +69 -21
- package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -1
- package/_shared/pr-loop/scripts/tests/fixtures/{copilot_internal_user_jonecho.json → copilot_internal_user_example.json} +1 -1
- package/_shared/pr-loop/scripts/tests/test_agent_config_carveout.py +1 -1
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +146 -2
- package/_shared/pr-loop/scripts/tests/test_copilot_quota.py +8 -8
- package/_shared/pr-loop/scripts/tests/test_reviewer_availability.py +18 -5
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +56 -5
- package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +104 -17
- package/agents/CLAUDE.md +3 -2
- package/agents/clasp-deployment-orchestrator.md +2 -2
- package/agents/code-advisor.md +7 -5
- package/agents/code-verifier.md +6 -2
- package/agents/session-advisor.md +27 -0
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -1
- package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +2 -2
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
- package/audit-rubrics/prompts/category-c-resource-cleanup.md +2 -2
- package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +2 -2
- package/audit-rubrics/prompts/category-e-dead-code.md +2 -2
- package/audit-rubrics/prompts/category-f-silent-failures.md +2 -2
- package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
- package/audit-rubrics/prompts/category-h-security-boundaries.md +2 -2
- package/audit-rubrics/prompts/category-i-concurrency.md +2 -2
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +2 -2
- package/audit-rubrics/prompts/category-k-codebase-conflicts.md +2 -2
- package/audit-rubrics/prompts/category-l-behavior-equivalence.md +2 -2
- package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +2 -2
- package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +2 -2
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +2 -2
- package/bin/CLAUDE.md +1 -0
- package/bin/expand_home_directory_tokens.mjs +75 -0
- package/bin/install.mjs +21 -7
- package/bin/install.test.mjs +220 -10
- package/hooks/CLAUDE.md +1 -1
- package/hooks/blocking/CLAUDE.md +4 -1
- package/hooks/blocking/_path_setup.py +13 -0
- package/hooks/blocking/bash_pre_tool_use_dispatcher.py +281 -0
- package/hooks/blocking/code_rules_dead_module_constant.py +88 -16
- package/hooks/blocking/code_rules_enforcer.py +6 -0
- package/hooks/blocking/code_rules_imports_logging.py +34 -1
- package/hooks/blocking/code_rules_paths_syspath.py +1 -1
- package/hooks/blocking/code_rules_test_layout.py +272 -0
- package/hooks/blocking/nas_ssh_binary_enforcer.py +22 -12
- package/hooks/blocking/pii_prevention_blocker.py +781 -0
- package/hooks/blocking/pii_scanner.py +340 -0
- package/hooks/blocking/pre_tool_use_dispatcher.py +3 -3
- package/hooks/blocking/stop_dispatcher.py +84 -0
- package/hooks/blocking/test__path_setup.py +39 -0
- package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +61 -0
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +294 -0
- package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +51 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +103 -0
- package/hooks/blocking/test_code_rules_enforcer_dot_test_pattern.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +11 -11
- package/hooks/blocking/test_code_rules_enforcer_naive_datetime.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_test_layout.py +111 -0
- package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +76 -0
- package/hooks/blocking/test_destructive_command_blocker.py +29 -9
- package/hooks/blocking/test_gh_body_arg_blocker.py +3 -3
- package/hooks/blocking/test_nas_ssh_binary_enforcer.py +96 -39
- package/hooks/blocking/test_pii_prevention_blocker.py +905 -0
- package/hooks/blocking/test_pii_prevention_windows_git_path.py +41 -0
- package/hooks/blocking/test_pii_scanner.py +165 -0
- package/hooks/blocking/test_pr_description_enforcer_pr_number.py +1 -1
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +14 -13
- package/hooks/blocking/test_stop_dispatcher.py +187 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +1 -1
- package/hooks/blocking/test_volatile_path_in_post_blocker.py +3 -3
- package/hooks/blocking/volatile_path_in_post_blocker.py +54 -5
- package/hooks/diagnostic/migrations/README.md +25 -26
- package/hooks/diagnostic/test_hook_log_extractor.py +10 -10
- package/hooks/hooks.json +10 -120
- package/hooks/hooks_constants/CLAUDE.md +12 -3
- package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +78 -0
- package/hooks/hooks_constants/dead_module_constant_constants.py +1 -0
- package/hooks/hooks_constants/hosted_hook_runner.py +73 -0
- package/hooks/hooks_constants/local_identity.py +182 -0
- package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +4 -37
- package/hooks/hooks_constants/pii_prevention_constants.py +299 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/hooks/hooks_constants/python_style_checks_constants.py +15 -0
- package/hooks/hooks_constants/stop_dispatcher_constants.py +26 -0
- package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +87 -0
- package/hooks/hooks_constants/test_hardcoded_user_path_constants.py +2 -2
- package/hooks/hooks_constants/test_hosted_hook_runner.py +101 -0
- package/hooks/hooks_constants/test_layout_constants.py +28 -0
- package/hooks/hooks_constants/test_local_identity.py +88 -0
- package/hooks/hooks_constants/test_session_env_cleanup_constants.py +1 -1
- package/hooks/hooks_constants/test_stop_dispatcher_constants.py +32 -0
- package/hooks/validators/README.md +5 -6
- package/hooks/validators/__init__.py +2 -2
- package/hooks/validators/python_style_checks.py +298 -243
- package/hooks/validators/run_all_validators.py +6 -0
- package/hooks/validators/test_python_style_checks.py +278 -163
- package/package.json +2 -2
- package/rules/CLAUDE.md +2 -1
- package/rules/bdd.md +1 -1
- package/rules/nas-ssh-invocation.md +6 -4
- package/rules/no-justification-noise.md +61 -0
- package/scripts/test_setup_project_paths.py +1 -1
- package/skills/CLAUDE.md +6 -3
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +2 -2
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +1 -1
- package/skills/_shared/pr-loop/scripts/test__path_resolver.py +42 -3
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +11 -11
- package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +26 -3
- package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +49 -25
- package/skills/autoconverge/CLAUDE.md +3 -3
- package/skills/autoconverge/SKILL.md +113 -35
- package/skills/autoconverge/reference/CLAUDE.md +2 -2
- package/skills/autoconverge/reference/convergence.md +44 -22
- package/skills/autoconverge/reference/gotchas.md +11 -0
- package/skills/autoconverge/reference/stop-conditions.md +23 -13
- package/skills/autoconverge/workflow/CLAUDE.md +2 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +15 -19
- package/skills/autoconverge/workflow/converge.contract.test.mjs +43 -42
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +141 -16
- package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +2 -2
- package/skills/autoconverge/workflow/converge.mjs +329 -68
- package/skills/autoconverge/workflow/converge.path-aware.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.precatch.test.mjs +152 -0
- package/skills/autoconverge/workflow/converge.run-input.test.mjs +3 -3
- package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +15 -15
- package/skills/autoconverge/workflow/test_convergence_summary.py +6 -6
- package/skills/bdd-protocol/SKILL.md +4 -5
- package/skills/bdd-protocol/references/anti-patterns.md +1 -1
- package/skills/bugteam/SKILL.md +8 -0
- package/skills/bugteam/reference/copilot-gap-analysis.md +20 -20
- package/skills/bugteam/reference/obstacles/audit-walk-categories.md +1 -1
- package/skills/bugteam/reference/team-setup.md +7 -5
- package/skills/bugteam/scripts/reflow_skill_md.py +1 -1
- package/skills/copilot-finding-triage/SKILL.md +124 -0
- package/skills/copilot-finding-triage/reference/tier-rubric.md +49 -0
- package/skills/copilot-finding-triage/scripts/conftest.py +8 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/__init__.py +0 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/__init__.py +0 -0
- package/skills/copilot-finding-triage/scripts/copilot_finding_triage_constants/config/notify_ntfy_constants.py +32 -0
- package/skills/copilot-finding-triage/scripts/notify_ntfy.py +192 -0
- package/skills/copilot-finding-triage/scripts/test_notify_ntfy.py +141 -0
- package/skills/copilot-finding-triage/templates/notification.md +34 -0
- package/skills/copilot-review/SKILL.md +8 -6
- package/skills/findbugs/SKILL.md +4 -0
- package/skills/fixbugs/SKILL.md +8 -7
- package/skills/gotcha/CLAUDE.md +2 -2
- package/skills/gotcha/SKILL.md +4 -4
- package/skills/log-audit/SKILL.md +4 -6
- package/skills/monitor-open-prs/CLAUDE.md +1 -1
- package/skills/monitor-open-prs/SKILL.md +6 -1
- package/skills/orchestrator/SKILL.md +74 -78
- package/skills/orchestrator-refresh/SKILL.md +24 -12
- package/skills/post-audit-findings/SKILL.md +5 -9
- package/skills/pr-consistency-audit/SKILL.md +5 -1
- package/skills/pr-converge/CLAUDE.md +1 -1
- package/skills/pr-converge/SKILL.md +86 -47
- package/skills/pr-converge/reference/CLAUDE.md +1 -1
- package/skills/pr-converge/reference/convergence-gates.md +20 -17
- package/skills/pr-converge/reference/examples.md +63 -47
- package/skills/pr-converge/reference/fix-protocol.md +15 -15
- package/skills/pr-converge/reference/ground-rules.md +11 -7
- package/skills/pr-converge/reference/multi-pr-orchestration.md +6 -5
- package/skills/pr-converge/reference/per-tick.md +129 -107
- package/skills/pr-converge/reference/state-schema.md +15 -10
- package/skills/pr-converge/scripts/check_convergence.py +5 -4
- package/skills/pr-converge/scripts/test_check_convergence.py +6 -4
- package/skills/pr-fix-protocol/SKILL.md +3 -8
- package/skills/pr-loop-cloud-transport/SKILL.md +102 -0
- package/skills/pr-loop-cloud-transport/reference/identity-and-hooks.md +40 -0
- package/skills/pr-loop-cloud-transport/reference/substitution-matrix.md +48 -0
- package/skills/pr-loop-lifecycle/SKILL.md +5 -9
- package/skills/pr-scope-resolve/SKILL.md +3 -6
- package/skills/privacy-hygiene/SKILL.md +114 -0
- package/skills/qbug/SKILL.md +8 -8
- package/skills/rebase/SKILL.md +5 -1
- package/skills/refine/SKILL.md +4 -5
- package/skills/reviewer-gates/SKILL.md +7 -11
- package/skills/session-log/SKILL.md +4 -1
- package/skills/skill-builder/SKILL.md +3 -6
- package/skills/structure-prompt/SKILL.md +4 -5
- package/skills/team-advisor/SKILL.md +56 -0
- package/skills/test_markdown_link_integrity.py +10 -6
- package/skills/update/SKILL.md +5 -1
- package/skills/usage-pause/SKILL.md +14 -5
- package/skills/usage-pause/scripts/resolve_usage_window.py +83 -5
- package/skills/usage-pause/scripts/test_resolve_usage_window.py +185 -17
- package/skills/usage-pause/scripts/usage_pause_constants/resolve_usage_window_constants.py +4 -3
- package/skills/verified-build/SKILL.md +4 -9
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Pre-catch lens checklist
|
|
2
|
+
|
|
3
|
+
The shared checklist the internal pre-catch lenses read on demand.
|
|
4
|
+
|
|
5
|
+
## Lane 1 — Deterministic sweep
|
|
6
|
+
|
|
7
|
+
The mechanical, static, and test classes belong to a deterministic run. The static-sweep
|
|
8
|
+
step owns them: it runs the CODE_RULES gate (`code_rules_gate.py --base origin/main`),
|
|
9
|
+
`ruff`, `mypy`, and stem-matched `pytest` over the changed files. A reading lens trusts the
|
|
10
|
+
sweep to have cleared them and reviews sweep-clean code.
|
|
11
|
+
|
|
12
|
+
- Every CODE_RULES violation the gate reports is a finding.
|
|
13
|
+
- Every `ruff` and `mypy` diagnostic on a changed file is a finding.
|
|
14
|
+
- Every failing test in a changed production module's paired test is a finding.
|
|
15
|
+
- **Proof of absence:** the gate exits 0, `ruff` and `mypy` report no diagnostic
|
|
16
|
+
on the changed files, and the stem-matched tests pass.
|
|
17
|
+
|
|
18
|
+
## Lane 2 — Doc-vs-code parity
|
|
19
|
+
|
|
20
|
+
Every doc claim in the diff matches the code it describes. Reuse the
|
|
21
|
+
`pr-consistency-audit` skill's canonical-source cross-reference method
|
|
22
|
+
(`~/.claude/skills/pr-consistency-audit/SKILL.md`, canonical source first) and the
|
|
23
|
+
drift rubric at
|
|
24
|
+
`~/.claude/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md`.
|
|
25
|
+
|
|
26
|
+
- Every line citation resolves to the line it names.
|
|
27
|
+
- Every referenced file or script path exists.
|
|
28
|
+
- Every symbol is attributed to the file that defines it.
|
|
29
|
+
- Every inventory table, env-var table, count claim, and ordering claim matches
|
|
30
|
+
the code.
|
|
31
|
+
- **Proof of absence:** each referenced path, citation, symbol, and table checked
|
|
32
|
+
against its source and found in step.
|
|
33
|
+
|
|
34
|
+
## Lane 3 — Test-assertion completeness
|
|
35
|
+
|
|
36
|
+
Every changed or new production path carries a test that exercises its behavior.
|
|
37
|
+
|
|
38
|
+
- Every changed or new production path has a paired test that calls it and asserts
|
|
39
|
+
on its return value or side effect.
|
|
40
|
+
- A changed test pins the behavior it covers rather than hiding it behind a mock.
|
|
41
|
+
- **Proof of absence:** each changed production path paired with a behavior test
|
|
42
|
+
that fails when the path regresses.
|
|
43
|
+
|
|
44
|
+
## Lane 4 — PR-description-vs-diff two-way parity
|
|
45
|
+
|
|
46
|
+
The PR body and the diff describe the same change, both directions.
|
|
47
|
+
|
|
48
|
+
- Every claim in the PR body maps to a hunk in the diff.
|
|
49
|
+
- Every hunk in the diff maps to a claim in the PR body.
|
|
50
|
+
- Flag any invented path, invented count, or out-of-scope change.
|
|
51
|
+
- **Proof of absence:** the two-way map complete — every claim has a hunk and
|
|
52
|
+
every hunk has a claim.
|
|
53
|
+
|
|
54
|
+
## Lane 5 — Adversarial audit
|
|
55
|
+
|
|
56
|
+
The bug-audit lens runs the adversarial second pass the audit contract specifies
|
|
57
|
+
(`~/.claude/_shared/pr-loop/audit-contract.md`): assume the first A-P pass missed
|
|
58
|
+
at least three P1 bugs, then find them.
|
|
59
|
+
|
|
60
|
+
- Return new file:line findings for the bugs the first pass missed.
|
|
61
|
+
- Or return a per-category proof-of-absence entry naming each re-examined category
|
|
62
|
+
and why it holds.
|
|
63
|
+
- A bare "nothing new" is not an acceptable result for this pass.
|
|
64
|
+
- **Proof of absence:** a per-category adversarial-probe entry for each re-examined
|
|
65
|
+
A-P category with no new finding.
|
|
@@ -13,7 +13,7 @@ Python scripts invoked at runtime by the PR-loop skills. Each script is a standa
|
|
|
13
13
|
| `revoke_project_claude_permissions.py` | Removes the allow-rules and entries that `grant_project_claude_permissions.py` wrote; safe to run when no prior grant exists |
|
|
14
14
|
| `code_rules_gate.py` | Pre-commit gate that runs `code_rules_enforcer` checks on staged Python files before a fix commit lands, and the terminology sweep over the staged diff |
|
|
15
15
|
| `terminology_sweep.py` | Flags a prose term that near-misses an identifier introduced on added code lines of a unified diff (shared leading word, divergent tail) |
|
|
16
|
-
| `reviews_disabled.py` | Shared helper for the
|
|
16
|
+
| `reviews_disabled.py` | Shared helper for the reviewer opt-out and opt-in gates; parses `CLAUDE_REVIEWS_DISABLED` and `CLAUDE_REVIEWS_ENABLED` tokens to decide which reviewers run (bugbot is off by default and runs only when `CLAUDE_REVIEWS_ENABLED` lists it) |
|
|
17
17
|
| `copilot_quota.py` | Copilot premium-request quota pre-check: resolves a configured GitHub account, reads its remaining `premium_interactions` quota via `gh api copilot_internal/user`, and exits 0 (run Copilot) or non-zero (skip: out of quota, API down, or no account configured) |
|
|
18
18
|
| `reviewer_availability.py` | Unified reviewer-availability entry point for Copilot and Bugbot: reuses `copilot_quota.py` and `reviews_disabled.py` and exits 0 when the named `--reviewer` may be spawned, non-zero when it is opted out or (for Copilot) out of quota |
|
|
19
19
|
| `fix_hookspath.py` | Repairs a malformed `core.hooksPath` global git config entry |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import ast
|
|
3
3
|
import importlib.util
|
|
4
|
+
import os
|
|
4
5
|
import re
|
|
5
6
|
import subprocess
|
|
6
7
|
import sys
|
|
@@ -15,13 +16,18 @@ from pr_loop_shared_constants.code_rules_gate_constants import ( # noqa: E402
|
|
|
15
16
|
ALL_CODE_FILE_EXTENSIONS,
|
|
16
17
|
ALL_GIT_DIFF_CACHED_NAME_ONLY_NULL_TERMINATED_COMMAND,
|
|
17
18
|
ALL_GIT_DIFF_NAME_ONLY_NULL_TERMINATED_COMMAND_PREFIX,
|
|
19
|
+
ALL_POSIX_VENV_PYTHON_RELATIVE_PATH_SEGMENTS,
|
|
18
20
|
ALL_PYTEST_CONFIG_FILE_SECTIONS,
|
|
19
21
|
ALL_PYTEST_MODULE_INVOCATION,
|
|
20
22
|
ALL_TEST_FILENAME_GLOB_SUFFIXES,
|
|
21
23
|
ALL_TEST_FILENAME_SUFFIXES,
|
|
24
|
+
ALL_VENV_DIRECTORY_NAMES,
|
|
25
|
+
ALL_WINDOWS_VENV_PYTHON_RELATIVE_PATH_SEGMENTS,
|
|
22
26
|
BANNED_NOUN_DEFINITION_LINE_GROUP_INDEX,
|
|
23
27
|
BANNED_NOUN_SPAN_GROUP_INDEX,
|
|
24
28
|
BANNED_NOUN_VIOLATION_PATTERN,
|
|
29
|
+
CODE_RULES_GATE_PYTHON_ENV_VAR,
|
|
30
|
+
CODE_RULES_GATE_PYTHONPATH_ENV_VAR,
|
|
25
31
|
DUPLICATE_BODY_DEFINITION_LINE_GROUP_INDEX,
|
|
26
32
|
DUPLICATE_BODY_SPAN_GROUP_INDEX,
|
|
27
33
|
DUPLICATE_BODY_VIOLATION_PATTERN,
|
|
@@ -36,7 +42,10 @@ from pr_loop_shared_constants.code_rules_gate_constants import ( # noqa: E402
|
|
|
36
42
|
ISOLATION_SPAN_GROUP_INDEX,
|
|
37
43
|
ISOLATION_VIOLATION_PATTERN,
|
|
38
44
|
MAX_VIOLATIONS_PER_CHECK,
|
|
45
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MAJOR,
|
|
46
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MINOR,
|
|
39
47
|
PYTHON_FILE_EXTENSION,
|
|
48
|
+
PYTHONPATH_ENV_VAR,
|
|
40
49
|
STAGED_PYTEST_TIMEOUT_SECONDS,
|
|
41
50
|
STAGED_TEST_FAILURE_HEADER,
|
|
42
51
|
STAGED_TEST_GROUP_FAILURE_MESSAGE,
|
|
@@ -1620,12 +1629,97 @@ def _group_staged_tests_by_root(
|
|
|
1620
1629
|
return tests_by_root
|
|
1621
1630
|
|
|
1622
1631
|
|
|
1623
|
-
def
|
|
1632
|
+
def _venv_python_relative_path() -> tuple[str, ...]:
|
|
1633
|
+
"""Return the venv-relative python executable path segments for this platform.
|
|
1634
|
+
|
|
1635
|
+
Returns:
|
|
1636
|
+
The Windows ``Scripts/python.exe`` segments on Windows, the POSIX
|
|
1637
|
+
``bin/python`` segments on every other platform.
|
|
1638
|
+
"""
|
|
1639
|
+
if os.name == "nt":
|
|
1640
|
+
return ALL_WINDOWS_VENV_PYTHON_RELATIVE_PATH_SEGMENTS
|
|
1641
|
+
return ALL_POSIX_VENV_PYTHON_RELATIVE_PATH_SEGMENTS
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
def _venv_python_executable(repository_root: Path) -> Path | None:
|
|
1645
|
+
"""Return the first existing venv interpreter under *repository_root*.
|
|
1646
|
+
|
|
1647
|
+
Checks each directory name in ``ALL_VENV_DIRECTORY_NAMES`` in order and
|
|
1648
|
+
returns the platform-specific interpreter path the first time it exists
|
|
1649
|
+
on disk.
|
|
1650
|
+
|
|
1651
|
+
Args:
|
|
1652
|
+
repository_root: The repository root to search for a venv directory.
|
|
1653
|
+
|
|
1654
|
+
Returns:
|
|
1655
|
+
The resolved interpreter path, or None when no candidate exists.
|
|
1656
|
+
"""
|
|
1657
|
+
relative_python_path = _venv_python_relative_path()
|
|
1658
|
+
for each_venv_directory_name in ALL_VENV_DIRECTORY_NAMES:
|
|
1659
|
+
candidate_path = repository_root.joinpath(
|
|
1660
|
+
each_venv_directory_name, *relative_python_path
|
|
1661
|
+
)
|
|
1662
|
+
if candidate_path.is_file():
|
|
1663
|
+
return candidate_path
|
|
1664
|
+
return None
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
def _resolve_gate_python_executable(repository_root: Path) -> str:
|
|
1668
|
+
"""Return the interpreter the staged-test subprocess runs under.
|
|
1669
|
+
|
|
1670
|
+
Resolves in order: the ``CODE_RULES_GATE_PYTHON`` environment variable
|
|
1671
|
+
when set, then the first existing project venv interpreter under
|
|
1672
|
+
*repository_root*, then the interpreter running the gate itself.
|
|
1673
|
+
|
|
1674
|
+
Args:
|
|
1675
|
+
repository_root: The repository root used to locate a project venv.
|
|
1676
|
+
|
|
1677
|
+
Returns:
|
|
1678
|
+
The absolute path (or command name) of the interpreter to invoke.
|
|
1679
|
+
"""
|
|
1680
|
+
configured_python = os.environ.get(CODE_RULES_GATE_PYTHON_ENV_VAR)
|
|
1681
|
+
if configured_python:
|
|
1682
|
+
return configured_python
|
|
1683
|
+
venv_python = _venv_python_executable(repository_root)
|
|
1684
|
+
if venv_python is not None:
|
|
1685
|
+
return str(venv_python)
|
|
1686
|
+
return sys.executable
|
|
1687
|
+
|
|
1688
|
+
|
|
1689
|
+
def _staged_pytest_environment() -> dict[str, str]:
|
|
1690
|
+
"""Return the subprocess environment for the staged-test pytest run.
|
|
1691
|
+
|
|
1692
|
+
Starts from ``repository_environment()`` (the GIT_-scrubbed process
|
|
1693
|
+
environment) and, when ``CODE_RULES_GATE_PYTHONPATH`` is set, prepends
|
|
1694
|
+
its value to ``PYTHONPATH`` so a resolved venv interpreter that lacks the
|
|
1695
|
+
project on its default path can still import it.
|
|
1696
|
+
|
|
1697
|
+
Returns:
|
|
1698
|
+
The environment mapping to pass to the staged-test subprocess.
|
|
1699
|
+
"""
|
|
1700
|
+
environment = repository_environment()
|
|
1701
|
+
configured_pythonpath = os.environ.get(CODE_RULES_GATE_PYTHONPATH_ENV_VAR)
|
|
1702
|
+
if not configured_pythonpath:
|
|
1703
|
+
return environment
|
|
1704
|
+
existing_pythonpath = environment.get(PYTHONPATH_ENV_VAR, "")
|
|
1705
|
+
environment[PYTHONPATH_ENV_VAR] = (
|
|
1706
|
+
configured_pythonpath
|
|
1707
|
+
if not existing_pythonpath
|
|
1708
|
+
else os.pathsep.join([configured_pythonpath, existing_pythonpath])
|
|
1709
|
+
)
|
|
1710
|
+
return environment
|
|
1711
|
+
|
|
1712
|
+
|
|
1713
|
+
def _run_pytest_for_group(
|
|
1714
|
+
group_root: Path, all_group_test_paths: list[Path], repository_root: Path
|
|
1715
|
+
) -> int:
|
|
1624
1716
|
"""Run pytest over one group's test files with the working directory at its root.
|
|
1625
1717
|
|
|
1626
1718
|
Args:
|
|
1627
1719
|
group_root: The owning test root used as the pytest working directory.
|
|
1628
1720
|
all_group_test_paths: The staged test files that share *group_root*.
|
|
1721
|
+
repository_root: The repository root used to resolve a project venv
|
|
1722
|
+
interpreter when ``CODE_RULES_GATE_PYTHON`` is not set.
|
|
1629
1723
|
|
|
1630
1724
|
Returns:
|
|
1631
1725
|
0 when the group's tests pass or collect nothing; pytest's non-zero
|
|
@@ -1633,14 +1727,14 @@ def _run_pytest_for_group(group_root: Path, all_group_test_paths: list[Path]) ->
|
|
|
1633
1727
|
"""
|
|
1634
1728
|
pytest_process = subprocess.run(
|
|
1635
1729
|
[
|
|
1636
|
-
|
|
1730
|
+
_resolve_gate_python_executable(repository_root),
|
|
1637
1731
|
*ALL_PYTEST_MODULE_INVOCATION,
|
|
1638
1732
|
*[str(each_path) for each_path in all_group_test_paths],
|
|
1639
1733
|
],
|
|
1640
1734
|
cwd=str(group_root),
|
|
1641
1735
|
timeout=STAGED_PYTEST_TIMEOUT_SECONDS,
|
|
1642
1736
|
check=False,
|
|
1643
|
-
env=
|
|
1737
|
+
env=_staged_pytest_environment(),
|
|
1644
1738
|
)
|
|
1645
1739
|
if pytest_process.returncode == PYTEST_NO_TESTS_COLLECTED_EXIT_CODE:
|
|
1646
1740
|
return 0
|
|
@@ -1670,7 +1764,7 @@ def run_staged_test_files(repository_root: Path) -> int:
|
|
|
1670
1764
|
first_failing_exit_code = 0
|
|
1671
1765
|
for each_group_root in sorted(tests_by_root):
|
|
1672
1766
|
group_exit_code = _run_pytest_for_group(
|
|
1673
|
-
each_group_root, tests_by_root[each_group_root]
|
|
1767
|
+
each_group_root, tests_by_root[each_group_root], repository_root
|
|
1674
1768
|
)
|
|
1675
1769
|
if group_exit_code != 0:
|
|
1676
1770
|
print(
|
|
@@ -1684,6 +1778,42 @@ def run_staged_test_files(repository_root: Path) -> int:
|
|
|
1684
1778
|
return first_failing_exit_code
|
|
1685
1779
|
|
|
1686
1780
|
|
|
1781
|
+
def _staged_pytest_exit_code_for_current_python(repository_root: Path) -> int:
|
|
1782
|
+
"""Run the staged test files, or skip them on a Python below the minimum.
|
|
1783
|
+
|
|
1784
|
+
::
|
|
1785
|
+
|
|
1786
|
+
running (3, 11), minimum (3, 12) -> log a notice and return 0 (skipped)
|
|
1787
|
+
running (3, 12), minimum (3, 12) -> run pytest over the staged tests
|
|
1788
|
+
|
|
1789
|
+
The staged tests call newer-Python APIs, so an older interpreter fails them
|
|
1790
|
+
for the environment rather than for a code defect. On such a runtime this
|
|
1791
|
+
step is skipped so the gap does not block the commit, while the terminology
|
|
1792
|
+
and code-rules checks still run.
|
|
1793
|
+
|
|
1794
|
+
Args:
|
|
1795
|
+
repository_root: The repository root whose staged test files run.
|
|
1796
|
+
|
|
1797
|
+
Returns:
|
|
1798
|
+
0 when the running Python is below the staged-test minimum, otherwise the
|
|
1799
|
+
exit code from running the staged test files.
|
|
1800
|
+
"""
|
|
1801
|
+
running_version = (sys.version_info.major, sys.version_info.minor)
|
|
1802
|
+
minimum_version = (
|
|
1803
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MAJOR,
|
|
1804
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MINOR,
|
|
1805
|
+
)
|
|
1806
|
+
if running_version < minimum_version:
|
|
1807
|
+
print(
|
|
1808
|
+
f"code_rules_gate: Python {running_version} is below the staged-test "
|
|
1809
|
+
f"minimum {minimum_version}; skipping the staged "
|
|
1810
|
+
"pytest step (environment gap, not a code failure).",
|
|
1811
|
+
file=sys.stderr,
|
|
1812
|
+
)
|
|
1813
|
+
return 0
|
|
1814
|
+
return run_staged_test_files(repository_root)
|
|
1815
|
+
|
|
1816
|
+
|
|
1687
1817
|
def _report_terminology_findings(all_findings: list[str]) -> None:
|
|
1688
1818
|
"""Print the terminology-sweep findings, when any, to standard error.
|
|
1689
1819
|
|
|
@@ -1782,7 +1912,9 @@ def main(all_arguments: list[str]) -> int:
|
|
|
1782
1912
|
if arguments.staged:
|
|
1783
1913
|
all_terminology_findings = staged_terminology_findings(repository_root)
|
|
1784
1914
|
_report_terminology_findings(all_terminology_findings)
|
|
1785
|
-
staged_test_exit_code =
|
|
1915
|
+
staged_test_exit_code = _staged_pytest_exit_code_for_current_python(
|
|
1916
|
+
repository_root
|
|
1917
|
+
)
|
|
1786
1918
|
staged_file_paths = paths_from_git_staged(repository_root)
|
|
1787
1919
|
staged_file_paths = filter_paths_under_prefixes(
|
|
1788
1920
|
staged_file_paths,
|
|
@@ -223,7 +223,7 @@ def parse_command_line_arguments(all_arguments: list[str]) -> argparse.Namespace
|
|
|
223
223
|
parser.add_argument(
|
|
224
224
|
CLI_FLAG_REPO,
|
|
225
225
|
required=True,
|
|
226
|
-
help="Repository name (e.g., claude-
|
|
226
|
+
help="Repository name (e.g., claude-dev-env).",
|
|
227
227
|
)
|
|
228
228
|
parser.add_argument(
|
|
229
229
|
CLI_FLAG_PR_NUMBER,
|
|
@@ -14,7 +14,7 @@ Named constants for every script in `_shared/pr-loop/scripts/`. Each module owns
|
|
|
14
14
|
| `post_audit_thread_constants.py` | `post_audit_thread.py` — HTTP status codes, retry counts, GitHub API paths |
|
|
15
15
|
| `preflight_constants.py` | `preflight.py` — env-var names, git subcommands, pytest exit codes, test discovery patterns |
|
|
16
16
|
| `preflight_self_heal_constants.py` | `preflight_self_heal.py` — git config keys and local-scope detection strings |
|
|
17
|
-
| `reviews_disabled_constants.py` | `reviews_disabled.py` — `CLAUDE_REVIEWS_DISABLED` token taxonomy |
|
|
17
|
+
| `reviews_disabled_constants.py` | `reviews_disabled.py` — `CLAUDE_REVIEWS_DISABLED` and `CLAUDE_REVIEWS_ENABLED` env-var names and token taxonomy |
|
|
18
18
|
| `copilot_quota_constants.py` | `copilot_quota.py` — `COPILOT_QUOTA_ACCOUNT` env-var name, `copilot_internal/user` API path, the `premium_interactions` gating field names, the four skip/run exit codes, and the default `.env` path |
|
|
19
19
|
| `terminology_sweep_constants.py` | `terminology_sweep.py` — identifier and prose-token regexes, diff-parsing prefixes, code-file extensions, test-file detection patterns, and the finding-message template |
|
|
20
20
|
| `reviewer_availability_constants.py` | `reviewer_availability.py` — the available/down exit codes for the unified reviewer-availability pre-check |
|
|
@@ -88,6 +88,18 @@ ALL_PYTEST_MODULE_INVOCATION: tuple[str, ...] = (
|
|
|
88
88
|
"-q",
|
|
89
89
|
)
|
|
90
90
|
|
|
91
|
+
CODE_RULES_GATE_PYTHON_ENV_VAR: str = "CODE_RULES_GATE_PYTHON"
|
|
92
|
+
|
|
93
|
+
CODE_RULES_GATE_PYTHONPATH_ENV_VAR: str = "CODE_RULES_GATE_PYTHONPATH"
|
|
94
|
+
|
|
95
|
+
PYTHONPATH_ENV_VAR: str = "PYTHONPATH"
|
|
96
|
+
|
|
97
|
+
ALL_VENV_DIRECTORY_NAMES: tuple[str, ...] = (".venv", "venv")
|
|
98
|
+
|
|
99
|
+
ALL_WINDOWS_VENV_PYTHON_RELATIVE_PATH_SEGMENTS: tuple[str, ...] = ("Scripts", "python.exe")
|
|
100
|
+
|
|
101
|
+
ALL_POSIX_VENV_PYTHON_RELATIVE_PATH_SEGMENTS: tuple[str, ...] = ("bin", "python")
|
|
102
|
+
|
|
91
103
|
STAGED_PYTEST_TIMEOUT_SECONDS: int = 600
|
|
92
104
|
|
|
93
105
|
STAGED_TEST_FAILURE_HEADER: str = (
|
|
@@ -119,3 +131,7 @@ STAGED_TEST_GROUP_FAILURE_MESSAGE: str = (
|
|
|
119
131
|
"code_rules_gate: staged test group rooted at {group_root} "
|
|
120
132
|
"failed under pytest; commit blocked."
|
|
121
133
|
)
|
|
134
|
+
|
|
135
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MAJOR: int = 3
|
|
136
|
+
|
|
137
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MINOR: int = 12
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
"""Configuration constants for the
|
|
1
|
+
"""Configuration constants for the reviewer opt-out and opt-in gates."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME: str = "CLAUDE_REVIEWS_DISABLED"
|
|
6
|
+
CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME: str = "CLAUDE_REVIEWS_ENABLED"
|
|
6
7
|
CLAUDE_REVIEWS_DISABLED_TOKEN_SEPARATOR: str = ","
|
|
7
8
|
CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN: str = "bugteam"
|
|
8
9
|
CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN: str = "bugbot"
|
|
@@ -11,9 +11,11 @@ Copilot counts as down in two cases: it is opted out via
|
|
|
11
11
|
than quota available (out of quota, the quota API or account down, or no
|
|
12
12
|
account configured).
|
|
13
13
|
|
|
14
|
-
Bugbot carries no quota or availability API. It
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
Bugbot carries no quota or availability API. It is off by default and counts
|
|
15
|
+
as down whenever it is disabled for the run: the default unless
|
|
16
|
+
``CLAUDE_REVIEWS_ENABLED`` lists ``bugbot``, and always when
|
|
17
|
+
``CLAUDE_REVIEWS_DISABLED`` lists it. A genuine runtime outage shows up later
|
|
18
|
+
as a poll timeout, not here.
|
|
17
19
|
|
|
18
20
|
The exit code tells the caller what to do. Exit 0 means the reviewer is
|
|
19
21
|
available and may be spawned. The documented down code (3) means skip it. Any
|
|
@@ -42,8 +44,13 @@ from pr_loop_shared_constants.reviews_disabled_constants import (
|
|
|
42
44
|
CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN,
|
|
43
45
|
CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN,
|
|
44
46
|
CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME,
|
|
47
|
+
CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME,
|
|
48
|
+
)
|
|
49
|
+
from reviews_disabled import (
|
|
50
|
+
is_bugbot_disabled_via_env,
|
|
51
|
+
is_bugbot_opted_out_via_env,
|
|
52
|
+
is_copilot_disabled_via_env,
|
|
45
53
|
)
|
|
46
|
-
from reviews_disabled import is_bugbot_disabled_via_env, is_copilot_disabled_via_env
|
|
47
54
|
|
|
48
55
|
|
|
49
56
|
@dataclass(frozen=True)
|
|
@@ -84,22 +91,45 @@ def _evaluate_copilot_availability(env_file_path: Path) -> ReviewerAvailability:
|
|
|
84
91
|
return ReviewerAvailability(EXIT_CODE_REVIEWER_AVAILABLE, quota_decision.message)
|
|
85
92
|
|
|
86
93
|
|
|
94
|
+
def _bugbot_down_message() -> str:
|
|
95
|
+
"""Name the cause that keeps Cursor Bugbot off for this run.
|
|
96
|
+
|
|
97
|
+
An opt-out and the off-by-default state read differently to the operator:
|
|
98
|
+
one names ``CLAUDE_REVIEWS_DISABLED``, the other points at the
|
|
99
|
+
``CLAUDE_REVIEWS_ENABLED`` opt-in the run is missing.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The opt-out message when ``CLAUDE_REVIEWS_DISABLED`` lists ``bugbot``,
|
|
103
|
+
and the off-by-default opt-in message otherwise.
|
|
104
|
+
"""
|
|
105
|
+
if is_bugbot_opted_out_via_env():
|
|
106
|
+
return (
|
|
107
|
+
f"reviewer-availability: bugbot is disabled via "
|
|
108
|
+
f"{CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME} — skipping."
|
|
109
|
+
)
|
|
110
|
+
return (
|
|
111
|
+
f"reviewer-availability: bugbot is off by default — set "
|
|
112
|
+
f"{CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME}={CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN} "
|
|
113
|
+
f"to opt in — skipping."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
87
117
|
def _evaluate_bugbot_availability() -> ReviewerAvailability:
|
|
88
118
|
"""Decide whether Cursor Bugbot is available to spawn.
|
|
89
119
|
|
|
90
120
|
Bugbot carries no quota or availability API, so this checks only the
|
|
91
|
-
deterministic
|
|
121
|
+
deterministic enable/disable env gate. Bugbot is off by default: it is
|
|
122
|
+
available only when ``CLAUDE_REVIEWS_ENABLED`` lists ``bugbot`` and
|
|
123
|
+
``CLAUDE_REVIEWS_DISABLED`` does not.
|
|
92
124
|
|
|
93
125
|
Returns:
|
|
94
|
-
A ReviewerAvailability that is down
|
|
95
|
-
|
|
126
|
+
A ReviewerAvailability that is down whenever Bugbot is disabled for
|
|
127
|
+
the run — the default unless ``CLAUDE_REVIEWS_ENABLED`` lists
|
|
128
|
+
``bugbot``, and always when ``CLAUDE_REVIEWS_DISABLED`` lists it — and
|
|
129
|
+
available otherwise.
|
|
96
130
|
"""
|
|
97
131
|
if is_bugbot_disabled_via_env():
|
|
98
|
-
return ReviewerAvailability(
|
|
99
|
-
EXIT_CODE_REVIEWER_DOWN,
|
|
100
|
-
f"reviewer-availability: bugbot is disabled via "
|
|
101
|
-
f"{CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME} — skipping.",
|
|
102
|
-
)
|
|
132
|
+
return ReviewerAvailability(EXIT_CODE_REVIEWER_DOWN, _bugbot_down_message())
|
|
103
133
|
return ReviewerAvailability(
|
|
104
134
|
EXIT_CODE_REVIEWER_AVAILABLE,
|
|
105
135
|
"reviewer-availability: bugbot is available.",
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Decide which PR reviewers run from the opt-out and opt-in env token lists.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
::
|
|
4
|
+
|
|
5
|
+
enabled lists bugbot, disabled empty -> bugbot ok: runs
|
|
6
|
+
no lists set -> bugbot flag: off (default)
|
|
7
|
+
enabled lists bugbot, disabled bugbot -> bugbot flag: off (opt-out wins)
|
|
8
|
+
disabled empty -> bugteam ok: runs
|
|
9
|
+
disabled lists copilot -> copilot flag: off
|
|
10
|
+
|
|
11
|
+
Bugbot is off by default and runs only when the enabled list names it.
|
|
12
|
+
Bugteam and copilot run by default and stop only when the disabled list
|
|
13
|
+
names them; both lists parse case-insensitively and tolerate whitespace.
|
|
6
14
|
"""
|
|
7
15
|
|
|
8
16
|
from __future__ import annotations
|
|
@@ -17,6 +25,7 @@ from pr_loop_shared_constants.reviews_disabled_constants import (
|
|
|
17
25
|
CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN,
|
|
18
26
|
CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME,
|
|
19
27
|
CLAUDE_REVIEWS_DISABLED_TOKEN_SEPARATOR,
|
|
28
|
+
CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME,
|
|
20
29
|
EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV,
|
|
21
30
|
)
|
|
22
31
|
|
|
@@ -27,18 +36,24 @@ __all__ = [
|
|
|
27
36
|
"CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN",
|
|
28
37
|
"CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME",
|
|
29
38
|
"CLAUDE_REVIEWS_DISABLED_TOKEN_SEPARATOR",
|
|
39
|
+
"CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME",
|
|
30
40
|
"EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV",
|
|
31
41
|
"is_bugbot_disabled_via_env",
|
|
42
|
+
"is_bugbot_opted_out_via_env",
|
|
32
43
|
"is_bugteam_disabled_via_env",
|
|
33
44
|
"is_copilot_disabled_via_env",
|
|
34
45
|
"main",
|
|
35
46
|
]
|
|
36
47
|
|
|
37
48
|
|
|
38
|
-
def
|
|
39
|
-
|
|
49
|
+
def _is_reviewer_listed_in_env(
|
|
50
|
+
environment_variable_name: str, reviewer_token: str
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Check whether an environment variable lists the given reviewer token.
|
|
40
53
|
|
|
41
54
|
Args:
|
|
55
|
+
environment_variable_name: The environment variable to read, either
|
|
56
|
+
the reviews-disabled or the reviews-enabled variable name.
|
|
42
57
|
reviewer_token: The reviewer token to look for, already lowercase
|
|
43
58
|
(for example the bugteam or bugbot token constant).
|
|
44
59
|
|
|
@@ -46,16 +61,14 @@ def _is_reviewer_disabled_via_env(reviewer_token: str) -> bool:
|
|
|
46
61
|
True when the env var contains ``reviewer_token`` as one of its
|
|
47
62
|
comma-separated entries (case-insensitive, whitespace-tolerant).
|
|
48
63
|
"""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
reviews_token_separator = CLAUDE_REVIEWS_DISABLED_TOKEN_SEPARATOR
|
|
65
|
+
listed_reviewers_text = os.environ.get(environment_variable_name, "")
|
|
66
|
+
all_listed_tokens = frozenset(
|
|
52
67
|
each_raw_token.strip().lower()
|
|
53
|
-
for each_raw_token in
|
|
54
|
-
reviews_disabled_token_separator
|
|
55
|
-
)
|
|
68
|
+
for each_raw_token in listed_reviewers_text.split(reviews_token_separator)
|
|
56
69
|
if each_raw_token.strip()
|
|
57
70
|
)
|
|
58
|
-
return reviewer_token in
|
|
71
|
+
return reviewer_token in all_listed_tokens
|
|
59
72
|
|
|
60
73
|
|
|
61
74
|
def is_bugteam_disabled_via_env() -> bool:
|
|
@@ -64,16 +77,45 @@ def is_bugteam_disabled_via_env() -> bool:
|
|
|
64
77
|
Returns:
|
|
65
78
|
True when the env var lists the ``bugteam`` token.
|
|
66
79
|
"""
|
|
67
|
-
return
|
|
80
|
+
return _is_reviewer_listed_in_env(
|
|
81
|
+
CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME, CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN
|
|
82
|
+
)
|
|
68
83
|
|
|
69
84
|
|
|
70
85
|
def is_bugbot_disabled_via_env() -> bool:
|
|
86
|
+
"""Check whether Cursor Bugbot is disabled for this run.
|
|
87
|
+
|
|
88
|
+
Cursor Bugbot is off by default. It runs only when
|
|
89
|
+
``CLAUDE_REVIEWS_ENABLED`` lists ``bugbot``, and a ``bugbot`` token in
|
|
90
|
+
``CLAUDE_REVIEWS_DISABLED`` forces it off even when the opt-in lists it.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
True when ``CLAUDE_REVIEWS_DISABLED`` lists ``bugbot`` or when
|
|
94
|
+
``CLAUDE_REVIEWS_ENABLED`` does not list ``bugbot``.
|
|
95
|
+
"""
|
|
96
|
+
is_opted_out = _is_reviewer_listed_in_env(
|
|
97
|
+
CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME, CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN
|
|
98
|
+
)
|
|
99
|
+
is_opted_in = _is_reviewer_listed_in_env(
|
|
100
|
+
CLAUDE_REVIEWS_ENABLED_ENV_VAR_NAME, CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN
|
|
101
|
+
)
|
|
102
|
+
return is_opted_out or not is_opted_in
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def is_bugbot_opted_out_via_env() -> bool:
|
|
71
106
|
"""Check whether CLAUDE_REVIEWS_DISABLED opts Cursor Bugbot out.
|
|
72
107
|
|
|
108
|
+
The opt-out forces Bugbot off even when ``CLAUDE_REVIEWS_ENABLED`` lists
|
|
109
|
+
``bugbot``. This reports only the opt-out signal, where
|
|
110
|
+
``is_bugbot_disabled_via_env`` also reports off for the default case in
|
|
111
|
+
which neither env var names ``bugbot``.
|
|
112
|
+
|
|
73
113
|
Returns:
|
|
74
|
-
True when
|
|
114
|
+
True when ``CLAUDE_REVIEWS_DISABLED`` lists the ``bugbot`` token.
|
|
75
115
|
"""
|
|
76
|
-
return
|
|
116
|
+
return _is_reviewer_listed_in_env(
|
|
117
|
+
CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME, CLAUDE_REVIEWS_DISABLED_BUGBOT_TOKEN
|
|
118
|
+
)
|
|
77
119
|
|
|
78
120
|
|
|
79
121
|
def is_copilot_disabled_via_env() -> bool:
|
|
@@ -82,11 +124,13 @@ def is_copilot_disabled_via_env() -> bool:
|
|
|
82
124
|
Returns:
|
|
83
125
|
True when the env var lists the ``copilot`` token.
|
|
84
126
|
"""
|
|
85
|
-
return
|
|
127
|
+
return _is_reviewer_listed_in_env(
|
|
128
|
+
CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME, CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN
|
|
129
|
+
)
|
|
86
130
|
|
|
87
131
|
|
|
88
132
|
def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
|
|
89
|
-
"""Parse command-line arguments for the reviewer opt-out check.
|
|
133
|
+
"""Parse command-line arguments for the reviewer opt-out and opt-in gate check.
|
|
90
134
|
|
|
91
135
|
Args:
|
|
92
136
|
all_argv: Argument list excluding the program name, typically
|
|
@@ -105,19 +149,19 @@ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
|
|
|
105
149
|
CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN,
|
|
106
150
|
CLAUDE_REVIEWS_DISABLED_COPILOT_TOKEN,
|
|
107
151
|
],
|
|
108
|
-
help="Reviewer token to test against CLAUDE_REVIEWS_DISABLED",
|
|
152
|
+
help="Reviewer token to test against the CLAUDE_REVIEWS_DISABLED / CLAUDE_REVIEWS_ENABLED gates",
|
|
109
153
|
)
|
|
110
154
|
return parser.parse_args(all_argv)
|
|
111
155
|
|
|
112
156
|
|
|
113
157
|
def main(all_arguments: list[str]) -> int:
|
|
114
|
-
"""Exit 0 when the named reviewer is disabled
|
|
158
|
+
"""Exit 0 when the named reviewer is disabled for this run.
|
|
115
159
|
|
|
116
160
|
Args:
|
|
117
161
|
all_arguments: Argument list excluding the program name.
|
|
118
162
|
|
|
119
163
|
Returns:
|
|
120
|
-
0 when the named reviewer is
|
|
164
|
+
0 when the named reviewer is disabled for this run, 1 otherwise.
|
|
121
165
|
"""
|
|
122
166
|
arguments = parse_arguments(all_arguments)
|
|
123
167
|
disabled_checker_by_reviewer = {
|