claude-dev-env 1.92.1 → 1.93.1
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 +260 -14
- 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 +20 -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 +292 -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 +365 -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_config_field.py +2 -2
- 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 +190 -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 +295 -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,19 @@ 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,
|
|
31
|
+
COMMAND_LINE_ARGUMENT_SEPARATOR_LENGTH,
|
|
25
32
|
DUPLICATE_BODY_DEFINITION_LINE_GROUP_INDEX,
|
|
26
33
|
DUPLICATE_BODY_SPAN_GROUP_INDEX,
|
|
27
34
|
DUPLICATE_BODY_VIOLATION_PATTERN,
|
|
@@ -35,8 +42,12 @@ from pr_loop_shared_constants.code_rules_gate_constants import ( # noqa: E402
|
|
|
35
42
|
ISOLATION_DEFINITION_LINE_GROUP_INDEX,
|
|
36
43
|
ISOLATION_SPAN_GROUP_INDEX,
|
|
37
44
|
ISOLATION_VIOLATION_PATTERN,
|
|
45
|
+
MAXIMUM_STAGED_PYTEST_COMMAND_LINE_CHARACTERS,
|
|
38
46
|
MAX_VIOLATIONS_PER_CHECK,
|
|
47
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MAJOR,
|
|
48
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MINOR,
|
|
39
49
|
PYTHON_FILE_EXTENSION,
|
|
50
|
+
PYTHONPATH_ENV_VAR,
|
|
40
51
|
STAGED_PYTEST_TIMEOUT_SECONDS,
|
|
41
52
|
STAGED_TEST_FAILURE_HEADER,
|
|
42
53
|
STAGED_TEST_GROUP_FAILURE_MESSAGE,
|
|
@@ -1620,33 +1631,230 @@ def _group_staged_tests_by_root(
|
|
|
1620
1631
|
return tests_by_root
|
|
1621
1632
|
|
|
1622
1633
|
|
|
1623
|
-
def
|
|
1624
|
-
"""
|
|
1634
|
+
def _venv_python_relative_path() -> tuple[str, ...]:
|
|
1635
|
+
"""Return the venv-relative python executable path segments for this platform.
|
|
1636
|
+
|
|
1637
|
+
Returns:
|
|
1638
|
+
The Windows ``Scripts/python.exe`` segments on Windows, the POSIX
|
|
1639
|
+
``bin/python`` segments on every other platform.
|
|
1640
|
+
"""
|
|
1641
|
+
if os.name == "nt":
|
|
1642
|
+
return ALL_WINDOWS_VENV_PYTHON_RELATIVE_PATH_SEGMENTS
|
|
1643
|
+
return ALL_POSIX_VENV_PYTHON_RELATIVE_PATH_SEGMENTS
|
|
1644
|
+
|
|
1645
|
+
|
|
1646
|
+
def _venv_python_executable(repository_root: Path) -> Path | None:
|
|
1647
|
+
"""Return the first existing venv interpreter under *repository_root*.
|
|
1648
|
+
|
|
1649
|
+
Checks each directory name in ``ALL_VENV_DIRECTORY_NAMES`` in order and
|
|
1650
|
+
returns the platform-specific interpreter path the first time it exists
|
|
1651
|
+
on disk.
|
|
1625
1652
|
|
|
1626
1653
|
Args:
|
|
1627
|
-
|
|
1628
|
-
|
|
1654
|
+
repository_root: The repository root to search for a venv directory.
|
|
1655
|
+
|
|
1656
|
+
Returns:
|
|
1657
|
+
The resolved interpreter path, or None when no candidate exists.
|
|
1658
|
+
"""
|
|
1659
|
+
relative_python_path = _venv_python_relative_path()
|
|
1660
|
+
for each_venv_directory_name in ALL_VENV_DIRECTORY_NAMES:
|
|
1661
|
+
candidate_path = repository_root.joinpath(
|
|
1662
|
+
each_venv_directory_name, *relative_python_path
|
|
1663
|
+
)
|
|
1664
|
+
if candidate_path.is_file():
|
|
1665
|
+
return candidate_path
|
|
1666
|
+
return None
|
|
1667
|
+
|
|
1668
|
+
|
|
1669
|
+
def _resolve_gate_python_executable(repository_root: Path) -> str:
|
|
1670
|
+
"""Return the interpreter the staged-test subprocess runs under.
|
|
1671
|
+
|
|
1672
|
+
Resolves in order: the ``CODE_RULES_GATE_PYTHON`` environment variable
|
|
1673
|
+
when set, then the first existing project venv interpreter under
|
|
1674
|
+
*repository_root*, then the interpreter running the gate itself.
|
|
1675
|
+
|
|
1676
|
+
Args:
|
|
1677
|
+
repository_root: The repository root used to locate a project venv.
|
|
1678
|
+
|
|
1679
|
+
Returns:
|
|
1680
|
+
The absolute path (or command name) of the interpreter to invoke.
|
|
1681
|
+
"""
|
|
1682
|
+
configured_python = os.environ.get(CODE_RULES_GATE_PYTHON_ENV_VAR)
|
|
1683
|
+
if configured_python:
|
|
1684
|
+
return configured_python
|
|
1685
|
+
venv_python = _venv_python_executable(repository_root)
|
|
1686
|
+
if venv_python is not None:
|
|
1687
|
+
return str(venv_python)
|
|
1688
|
+
return sys.executable
|
|
1689
|
+
|
|
1690
|
+
|
|
1691
|
+
def _staged_pytest_environment() -> dict[str, str]:
|
|
1692
|
+
"""Return the subprocess environment for the staged-test pytest run.
|
|
1693
|
+
|
|
1694
|
+
Starts from ``repository_environment()`` (the GIT_-scrubbed process
|
|
1695
|
+
environment) and, when ``CODE_RULES_GATE_PYTHONPATH`` is set, prepends
|
|
1696
|
+
its value to ``PYTHONPATH`` so a resolved venv interpreter that lacks the
|
|
1697
|
+
project on its default path can still import it.
|
|
1698
|
+
|
|
1699
|
+
Returns:
|
|
1700
|
+
The environment mapping to pass to the staged-test subprocess.
|
|
1701
|
+
"""
|
|
1702
|
+
environment = repository_environment()
|
|
1703
|
+
configured_pythonpath = os.environ.get(CODE_RULES_GATE_PYTHONPATH_ENV_VAR)
|
|
1704
|
+
if not configured_pythonpath:
|
|
1705
|
+
return environment
|
|
1706
|
+
existing_pythonpath = environment.get(PYTHONPATH_ENV_VAR, "")
|
|
1707
|
+
environment[PYTHONPATH_ENV_VAR] = (
|
|
1708
|
+
configured_pythonpath
|
|
1709
|
+
if not existing_pythonpath
|
|
1710
|
+
else os.pathsep.join([configured_pythonpath, existing_pythonpath])
|
|
1711
|
+
)
|
|
1712
|
+
return environment
|
|
1713
|
+
|
|
1714
|
+
|
|
1715
|
+
def _relative_pytest_argument(test_path: Path, group_root: Path) -> str:
|
|
1716
|
+
"""Express *test_path* as a pytest argument relative to *group_root*.
|
|
1717
|
+
|
|
1718
|
+
*group_root* is already the pytest working directory, so the relative form
|
|
1719
|
+
names the same file in far fewer characters.
|
|
1720
|
+
|
|
1721
|
+
Example::
|
|
1722
|
+
|
|
1723
|
+
group_root = C:/repo/package
|
|
1724
|
+
ok: C:/repo/package/tests/test_a.py -> "tests/test_a.py"
|
|
1725
|
+
flag: C:/elsewhere/test_b.py -> no relative form; passed absolute
|
|
1726
|
+
|
|
1727
|
+
Args:
|
|
1728
|
+
test_path: The staged test file to express as a pytest argument.
|
|
1729
|
+
group_root: The pytest working directory for the group.
|
|
1730
|
+
|
|
1731
|
+
Returns:
|
|
1732
|
+
The path relative to *group_root* when *test_path* sits under it,
|
|
1733
|
+
otherwise the absolute path unchanged.
|
|
1734
|
+
"""
|
|
1735
|
+
try:
|
|
1736
|
+
return str(test_path.relative_to(group_root))
|
|
1737
|
+
except ValueError:
|
|
1738
|
+
return str(test_path)
|
|
1739
|
+
|
|
1740
|
+
|
|
1741
|
+
def _batched_pytest_arguments(
|
|
1742
|
+
all_pytest_arguments: list[str], character_budget: int
|
|
1743
|
+
) -> list[list[str]]:
|
|
1744
|
+
"""Split pytest path arguments into command-line-length-safe batches.
|
|
1745
|
+
|
|
1746
|
+
Windows rejects a command line past 32767 characters with WinError 206, so a
|
|
1747
|
+
large staged set passed in one invocation crashes the gate and denies the
|
|
1748
|
+
commit. Each batch takes arguments until the next one would push its joined
|
|
1749
|
+
length past *character_budget*.
|
|
1750
|
+
|
|
1751
|
+
Example::
|
|
1752
|
+
|
|
1753
|
+
character_budget = 10
|
|
1754
|
+
arguments = ["aaaa", "bbbb", "cccc"]
|
|
1755
|
+
ok: [["aaaa", "bbbb"], ["cccc"]] - every batch within budget
|
|
1756
|
+
flag: [["aaaa", "bbbb", "cccc"]] - 14 characters, over budget
|
|
1757
|
+
|
|
1758
|
+
Args:
|
|
1759
|
+
all_pytest_arguments: The path arguments to distribute, in order.
|
|
1760
|
+
character_budget: The joined length each batch must stay within. An
|
|
1761
|
+
argument wider than the budget on its own still lands in a batch
|
|
1762
|
+
by itself, so no argument is ever dropped.
|
|
1629
1763
|
|
|
1630
1764
|
Returns:
|
|
1631
|
-
|
|
1632
|
-
|
|
1765
|
+
One argument list per pytest invocation, preserving input order.
|
|
1766
|
+
"""
|
|
1767
|
+
all_batches: list[list[str]] = []
|
|
1768
|
+
current_batch: list[str] = []
|
|
1769
|
+
current_length = 0
|
|
1770
|
+
for each_argument in all_pytest_arguments:
|
|
1771
|
+
argument_length = len(each_argument) + COMMAND_LINE_ARGUMENT_SEPARATOR_LENGTH
|
|
1772
|
+
if current_batch and current_length + argument_length > character_budget:
|
|
1773
|
+
all_batches.append(current_batch)
|
|
1774
|
+
current_batch = []
|
|
1775
|
+
current_length = 0
|
|
1776
|
+
current_batch.append(each_argument)
|
|
1777
|
+
current_length += argument_length
|
|
1778
|
+
if current_batch:
|
|
1779
|
+
all_batches.append(current_batch)
|
|
1780
|
+
return all_batches
|
|
1781
|
+
|
|
1782
|
+
|
|
1783
|
+
def _pytest_batch_exit_code(all_batch_command: list[str], group_root: Path) -> int:
|
|
1784
|
+
"""Run one pytest invocation and normalize its exit code.
|
|
1785
|
+
|
|
1786
|
+
Args:
|
|
1787
|
+
all_batch_command: The full argv for this pytest invocation.
|
|
1788
|
+
group_root: The owning test root used as the working directory.
|
|
1789
|
+
|
|
1790
|
+
Returns:
|
|
1791
|
+
0 when the batch passes or collects no tests; pytest's non-zero exit
|
|
1792
|
+
code otherwise.
|
|
1633
1793
|
"""
|
|
1634
1794
|
pytest_process = subprocess.run(
|
|
1635
|
-
|
|
1636
|
-
sys.executable,
|
|
1637
|
-
*ALL_PYTEST_MODULE_INVOCATION,
|
|
1638
|
-
*[str(each_path) for each_path in all_group_test_paths],
|
|
1639
|
-
],
|
|
1795
|
+
all_batch_command,
|
|
1640
1796
|
cwd=str(group_root),
|
|
1641
1797
|
timeout=STAGED_PYTEST_TIMEOUT_SECONDS,
|
|
1642
1798
|
check=False,
|
|
1643
|
-
env=
|
|
1799
|
+
env=_staged_pytest_environment(),
|
|
1644
1800
|
)
|
|
1645
1801
|
if pytest_process.returncode == PYTEST_NO_TESTS_COLLECTED_EXIT_CODE:
|
|
1646
1802
|
return 0
|
|
1647
1803
|
return pytest_process.returncode
|
|
1648
1804
|
|
|
1649
1805
|
|
|
1806
|
+
def _run_pytest_for_group(
|
|
1807
|
+
group_root: Path, all_group_test_paths: list[Path], repository_root: Path
|
|
1808
|
+
) -> int:
|
|
1809
|
+
"""Run pytest over one group's test files with the working directory at its root.
|
|
1810
|
+
|
|
1811
|
+
Paths are passed relative to *group_root* (the subprocess working directory)
|
|
1812
|
+
and split into batches whose joined command line stays within
|
|
1813
|
+
``MAXIMUM_STAGED_PYTEST_COMMAND_LINE_CHARACTERS``, so a large staged set never
|
|
1814
|
+
trips the Windows 32767-character limit. A staged set that fits the budget runs
|
|
1815
|
+
as one invocation, exactly as an unbatched run did.
|
|
1816
|
+
|
|
1817
|
+
Example::
|
|
1818
|
+
|
|
1819
|
+
ok: 3 staged tests -> 1 pytest invocation, argv well under the budget
|
|
1820
|
+
flag: 800 staged tests -> 1 pytest invocation, argv over the Windows limit
|
|
1821
|
+
(WinError 206); batching splits it across several
|
|
1822
|
+
|
|
1823
|
+
Args:
|
|
1824
|
+
group_root: The owning test root used as the pytest working directory.
|
|
1825
|
+
all_group_test_paths: The staged test files that share *group_root*.
|
|
1826
|
+
repository_root: The repository root used to resolve a project venv
|
|
1827
|
+
interpreter when ``CODE_RULES_GATE_PYTHON`` is not set.
|
|
1828
|
+
|
|
1829
|
+
Returns:
|
|
1830
|
+
0 when every batch passes or collects nothing; the first failing batch's
|
|
1831
|
+
non-zero pytest exit code otherwise.
|
|
1832
|
+
"""
|
|
1833
|
+
all_fixed_command = [
|
|
1834
|
+
_resolve_gate_python_executable(repository_root),
|
|
1835
|
+
*ALL_PYTEST_MODULE_INVOCATION,
|
|
1836
|
+
]
|
|
1837
|
+
fixed_command_length = sum(
|
|
1838
|
+
len(each_part) + COMMAND_LINE_ARGUMENT_SEPARATOR_LENGTH
|
|
1839
|
+
for each_part in all_fixed_command
|
|
1840
|
+
)
|
|
1841
|
+
all_batches = _batched_pytest_arguments(
|
|
1842
|
+
[
|
|
1843
|
+
_relative_pytest_argument(each_path, group_root)
|
|
1844
|
+
for each_path in all_group_test_paths
|
|
1845
|
+
],
|
|
1846
|
+
MAXIMUM_STAGED_PYTEST_COMMAND_LINE_CHARACTERS - fixed_command_length,
|
|
1847
|
+
)
|
|
1848
|
+
first_failing_exit_code = 0
|
|
1849
|
+
for each_batch in all_batches:
|
|
1850
|
+
batch_exit_code = _pytest_batch_exit_code(
|
|
1851
|
+
[*all_fixed_command, *each_batch], group_root
|
|
1852
|
+
)
|
|
1853
|
+
if batch_exit_code != 0 and first_failing_exit_code == 0:
|
|
1854
|
+
first_failing_exit_code = batch_exit_code
|
|
1855
|
+
return first_failing_exit_code
|
|
1856
|
+
|
|
1857
|
+
|
|
1650
1858
|
def run_staged_test_files(repository_root: Path) -> int:
|
|
1651
1859
|
"""Run pytest over the staged test files and return the gate exit code.
|
|
1652
1860
|
|
|
@@ -1670,7 +1878,7 @@ def run_staged_test_files(repository_root: Path) -> int:
|
|
|
1670
1878
|
first_failing_exit_code = 0
|
|
1671
1879
|
for each_group_root in sorted(tests_by_root):
|
|
1672
1880
|
group_exit_code = _run_pytest_for_group(
|
|
1673
|
-
each_group_root, tests_by_root[each_group_root]
|
|
1881
|
+
each_group_root, tests_by_root[each_group_root], repository_root
|
|
1674
1882
|
)
|
|
1675
1883
|
if group_exit_code != 0:
|
|
1676
1884
|
print(
|
|
@@ -1684,6 +1892,42 @@ def run_staged_test_files(repository_root: Path) -> int:
|
|
|
1684
1892
|
return first_failing_exit_code
|
|
1685
1893
|
|
|
1686
1894
|
|
|
1895
|
+
def _staged_pytest_exit_code_for_current_python(repository_root: Path) -> int:
|
|
1896
|
+
"""Run the staged test files, or skip them on a Python below the minimum.
|
|
1897
|
+
|
|
1898
|
+
::
|
|
1899
|
+
|
|
1900
|
+
running (3, 11), minimum (3, 12) -> log a notice and return 0 (skipped)
|
|
1901
|
+
running (3, 12), minimum (3, 12) -> run pytest over the staged tests
|
|
1902
|
+
|
|
1903
|
+
The staged tests call newer-Python APIs, so an older interpreter fails them
|
|
1904
|
+
for the environment rather than for a code defect. On such a runtime this
|
|
1905
|
+
step is skipped so the gap does not block the commit, while the terminology
|
|
1906
|
+
and code-rules checks still run.
|
|
1907
|
+
|
|
1908
|
+
Args:
|
|
1909
|
+
repository_root: The repository root whose staged test files run.
|
|
1910
|
+
|
|
1911
|
+
Returns:
|
|
1912
|
+
0 when the running Python is below the staged-test minimum, otherwise the
|
|
1913
|
+
exit code from running the staged test files.
|
|
1914
|
+
"""
|
|
1915
|
+
running_version = (sys.version_info.major, sys.version_info.minor)
|
|
1916
|
+
minimum_version = (
|
|
1917
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MAJOR,
|
|
1918
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MINOR,
|
|
1919
|
+
)
|
|
1920
|
+
if running_version < minimum_version:
|
|
1921
|
+
print(
|
|
1922
|
+
f"code_rules_gate: Python {running_version} is below the staged-test "
|
|
1923
|
+
f"minimum {minimum_version}; skipping the staged "
|
|
1924
|
+
"pytest step (environment gap, not a code failure).",
|
|
1925
|
+
file=sys.stderr,
|
|
1926
|
+
)
|
|
1927
|
+
return 0
|
|
1928
|
+
return run_staged_test_files(repository_root)
|
|
1929
|
+
|
|
1930
|
+
|
|
1687
1931
|
def _report_terminology_findings(all_findings: list[str]) -> None:
|
|
1688
1932
|
"""Print the terminology-sweep findings, when any, to standard error.
|
|
1689
1933
|
|
|
@@ -1782,7 +2026,9 @@ def main(all_arguments: list[str]) -> int:
|
|
|
1782
2026
|
if arguments.staged:
|
|
1783
2027
|
all_terminology_findings = staged_terminology_findings(repository_root)
|
|
1784
2028
|
_report_terminology_findings(all_terminology_findings)
|
|
1785
|
-
staged_test_exit_code =
|
|
2029
|
+
staged_test_exit_code = _staged_pytest_exit_code_for_current_python(
|
|
2030
|
+
repository_root
|
|
2031
|
+
)
|
|
1786
2032
|
staged_file_paths = paths_from_git_staged(repository_root)
|
|
1787
2033
|
staged_file_paths = filter_paths_under_prefixes(
|
|
1788
2034
|
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,8 +88,24 @@ 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
|
|
|
105
|
+
MAXIMUM_STAGED_PYTEST_COMMAND_LINE_CHARACTERS: int = 24000
|
|
106
|
+
|
|
107
|
+
COMMAND_LINE_ARGUMENT_SEPARATOR_LENGTH: int = 1
|
|
108
|
+
|
|
93
109
|
STAGED_TEST_FAILURE_HEADER: str = (
|
|
94
110
|
"code_rules_gate: staged test file(s) failed under pytest; commit blocked."
|
|
95
111
|
)
|
|
@@ -119,3 +135,7 @@ STAGED_TEST_GROUP_FAILURE_MESSAGE: str = (
|
|
|
119
135
|
"code_rules_gate: staged test group rooted at {group_root} "
|
|
120
136
|
"failed under pytest; commit blocked."
|
|
121
137
|
)
|
|
138
|
+
|
|
139
|
+
MINIMUM_STAGED_PYTEST_PYTHON_MAJOR: int = 3
|
|
140
|
+
|
|
141
|
+
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.",
|