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
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
"""Flag a prose term that names a code identifier a hair differently.
|
|
2
2
|
|
|
3
|
-
A change adds the field ``
|
|
4
|
-
call it the ``premium-request``
|
|
5
|
-
searches one never finds the other.
|
|
3
|
+
A change adds the field ``premium_request_interactions`` while the same
|
|
4
|
+
branch's docs call it the ``premium-request-budget`` field: one thing, two
|
|
5
|
+
names, and a reader who searches one never finds the other.
|
|
6
6
|
|
|
7
7
|
::
|
|
8
8
|
|
|
9
|
-
code adds:
|
|
10
|
-
prose adds: the premium-request
|
|
11
|
-
flag: premium-request vs
|
|
12
|
-
ok: premium-interactions -- exact hyphen form, agrees
|
|
13
|
-
ok:
|
|
9
|
+
code adds: premium_request_interactions the identifier
|
|
10
|
+
prose adds: the premium-request-budget field a hyphenated term
|
|
11
|
+
flag: premium-request-budget vs premium_request_interactions -- only the final word differs
|
|
12
|
+
ok: premium-request-interactions -- exact hyphen form, agrees
|
|
13
|
+
ok: premium-report-total vs premium_request_interactions -- an earlier word differs, ordinary prose
|
|
14
14
|
|
|
15
15
|
The sweep reads a unified diff, collects the multi-word identifiers added on
|
|
16
16
|
code lines, and scans each added prose line for a hyphen or space variant that
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
renames only the final word of an identifier: it agrees on every earlier word
|
|
18
|
+
and differs on the last. A term that diverges in any earlier word, or shares
|
|
19
|
+
only one leading word, is ordinary prose. It also spares English compound
|
|
20
|
+
tails, singular/plural forms, stopword windows, and words that are themselves
|
|
21
|
+
tokens of another introduced identifier.
|
|
20
22
|
"""
|
|
21
23
|
|
|
22
24
|
import argparse
|
|
@@ -345,6 +347,47 @@ def _tuples_match_ignoring_plural(
|
|
|
345
347
|
)
|
|
346
348
|
|
|
347
349
|
|
|
350
|
+
def _diverges_only_in_final_token(
|
|
351
|
+
candidate_tuple: IdentifierTuple, identifier_tuple: IdentifierTuple
|
|
352
|
+
) -> bool:
|
|
353
|
+
"""Return whether two equal-length tuples agree on every token but the last.
|
|
354
|
+
|
|
355
|
+
::
|
|
356
|
+
|
|
357
|
+
candidate: ("premium", "request", "budget")
|
|
358
|
+
identifier: ("premium", "request", "interactions")
|
|
359
|
+
flag: every earlier token agrees, so the prose renames the final word
|
|
360
|
+
ok: ("premium", "report", "total") -- an earlier token diverges too
|
|
361
|
+
|
|
362
|
+
A near-miss renames a single trailing word of an identifier, so every token
|
|
363
|
+
before the last agrees by an exact or singular/plural match while the final
|
|
364
|
+
token differs. A tuple no longer than the two-token identifier minimum
|
|
365
|
+
cannot carry two agreeing leading tokens, so it never qualifies.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
candidate_tuple: The prose term's lowercase token tuple.
|
|
369
|
+
identifier_tuple: An introduced identifier's lowercase token tuple.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
True when both tuples run longer than the two-token identifier minimum,
|
|
373
|
+
share the same length, agree by exact or singular/plural match on every
|
|
374
|
+
token but the last, and differ on the final token.
|
|
375
|
+
"""
|
|
376
|
+
if (
|
|
377
|
+
len(candidate_tuple) <= MINIMUM_IDENTIFIER_TOKEN_COUNT
|
|
378
|
+
or len(candidate_tuple) != len(identifier_tuple)
|
|
379
|
+
):
|
|
380
|
+
return False
|
|
381
|
+
if _tokens_are_plural_variants(candidate_tuple[-1], identifier_tuple[-1]):
|
|
382
|
+
return False
|
|
383
|
+
return all(
|
|
384
|
+
_tokens_are_plural_variants(each_candidate_token, each_identifier_token)
|
|
385
|
+
for each_candidate_token, each_identifier_token in zip(
|
|
386
|
+
candidate_tuple[:-1], identifier_tuple[:-1]
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
348
391
|
def _word_is_identifier_vocabulary(
|
|
349
392
|
prose_word: str, all_identifier_tokens: frozenset[str]
|
|
350
393
|
) -> bool:
|
|
@@ -391,16 +434,19 @@ def _near_miss_identifier(
|
|
|
391
434
|
lines, used to spare prose built from real code vocabulary.
|
|
392
435
|
|
|
393
436
|
Returns:
|
|
394
|
-
The first identifier
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
(``
|
|
402
|
-
|
|
403
|
-
|
|
437
|
+
The first identifier the candidate renames in only its final token. Such a
|
|
438
|
+
candidate agrees on every earlier token, matches the identifier's
|
|
439
|
+
length, and differs on the last token. None marks the candidate
|
|
440
|
+
ordinary prose instead, on any of these grounds: it matches an
|
|
441
|
+
identifier exactly; it diverges in a token before the last
|
|
442
|
+
(``premium report total`` against ``premium_request_interactions``);
|
|
443
|
+
it holds no more than the two-token identifier minimum; it ends in a
|
|
444
|
+
common English compound tail word (``read-only``, ``data-driven``); it
|
|
445
|
+
contains an English stopword (``to a``, ``each image``); it differs
|
|
446
|
+
from the identifier only by a singular/plural form of one or more
|
|
447
|
+
tokens (``test files`` against ``test_file``); or every diverging word
|
|
448
|
+
is itself a token of some introduced identifier (``target width box``
|
|
449
|
+
when ``box_height`` is also in the diff).
|
|
404
450
|
"""
|
|
405
451
|
if not candidate_tuple or candidate_tuple in all_identifier_tuples:
|
|
406
452
|
return None
|
|
@@ -415,6 +461,8 @@ def _near_miss_identifier(
|
|
|
415
461
|
continue
|
|
416
462
|
if _tuples_match_ignoring_plural(each_identifier, candidate_tuple):
|
|
417
463
|
continue
|
|
464
|
+
if not _diverges_only_in_final_token(candidate_tuple, each_identifier):
|
|
465
|
+
continue
|
|
418
466
|
diverging_words = [
|
|
419
467
|
each_word
|
|
420
468
|
for each_word, each_token in zip(candidate_tuple, each_identifier)
|
|
@@ -32,7 +32,7 @@ pytest suite for the scripts and constants in `_shared/pr-loop/scripts/`. Each t
|
|
|
32
32
|
|
|
33
33
|
## Fixtures
|
|
34
34
|
|
|
35
|
-
`fixtures/
|
|
35
|
+
`fixtures/copilot_internal_user_example.json` — a captured `gh api
|
|
36
36
|
copilot_internal/user` response driving `test_copilot_quota.py`.
|
|
37
37
|
|
|
38
38
|
## Running
|
|
@@ -336,7 +336,7 @@ def test_is_trust_entry_rejects_cross_project_path_suffix_collision() -> None:
|
|
|
336
336
|
short_project_path = "/projects/foo"
|
|
337
337
|
trust_prefix = "Trusted local workspace:"
|
|
338
338
|
longer_unrelated_path_entry = (
|
|
339
|
-
"Trusted local workspace: /Users/
|
|
339
|
+
"Trusted local workspace: /Users/example/projects/foo/.claude/** unrelated path"
|
|
340
340
|
)
|
|
341
341
|
assert (
|
|
342
342
|
common_module.is_trust_entry_for_project(
|
|
@@ -337,11 +337,11 @@ def test_main_staged_mode_passes_when_only_terminology_findings_exist(
|
|
|
337
337
|
commit_all_files(temporary_git_repository, "initial")
|
|
338
338
|
write_file(
|
|
339
339
|
temporary_git_repository / "quota.py",
|
|
340
|
-
"first_count = 1\
|
|
340
|
+
"first_count = 1\npremium_request_interactions = 1\n",
|
|
341
341
|
)
|
|
342
342
|
write_file(
|
|
343
343
|
temporary_git_repository / "README.md",
|
|
344
|
-
"The premium-request
|
|
344
|
+
"The premium-request-budget field gates the run.\n",
|
|
345
345
|
)
|
|
346
346
|
stage_file(temporary_git_repository, "quota.py")
|
|
347
347
|
stage_file(temporary_git_repository, "README.md")
|
|
@@ -2043,6 +2043,88 @@ def test_run_staged_test_files_zero_when_staged_conftest_collects_no_tests(
|
|
|
2043
2043
|
assert gate_module.run_staged_test_files(temporary_git_repository) == 0
|
|
2044
2044
|
|
|
2045
2045
|
|
|
2046
|
+
def test_staged_pytest_skipped_below_minimum_python(
|
|
2047
|
+
temporary_git_repository: Path,
|
|
2048
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
2049
|
+
capsys: pytest.CaptureFixture[str],
|
|
2050
|
+
) -> None:
|
|
2051
|
+
"""Below the minimum Python, the staged pytest step is skipped, not run.
|
|
2052
|
+
|
|
2053
|
+
A staged test that would fail returns a zero exit code because the older
|
|
2054
|
+
interpreter cannot run its newer-API calls, so the environment gap does not
|
|
2055
|
+
block the commit.
|
|
2056
|
+
"""
|
|
2057
|
+
write_file(
|
|
2058
|
+
temporary_git_repository / "test_needs_new_python.py",
|
|
2059
|
+
"def test_intentionally_fails() -> None:\n assert False\n",
|
|
2060
|
+
)
|
|
2061
|
+
stage_file(temporary_git_repository, "test_needs_new_python.py")
|
|
2062
|
+
monkeypatch.setattr(gate_module, "MINIMUM_STAGED_PYTEST_PYTHON_MAJOR", 99)
|
|
2063
|
+
|
|
2064
|
+
exit_code = gate_module._staged_pytest_exit_code_for_current_python(
|
|
2065
|
+
temporary_git_repository
|
|
2066
|
+
)
|
|
2067
|
+
|
|
2068
|
+
captured = capsys.readouterr()
|
|
2069
|
+
assert exit_code == 0
|
|
2070
|
+
assert "skipping the staged pytest step" in captured.err
|
|
2071
|
+
|
|
2072
|
+
|
|
2073
|
+
def test_staged_pytest_runs_at_or_above_minimum_python(
|
|
2074
|
+
temporary_git_repository: Path,
|
|
2075
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
2076
|
+
) -> None:
|
|
2077
|
+
"""At or above the minimum Python, the staged pytest step runs and a failing
|
|
2078
|
+
staged test blocks with a non-zero exit code.
|
|
2079
|
+
"""
|
|
2080
|
+
write_file(
|
|
2081
|
+
temporary_git_repository / "test_fails.py",
|
|
2082
|
+
"def test_intentionally_fails() -> None:\n assert False\n",
|
|
2083
|
+
)
|
|
2084
|
+
stage_file(temporary_git_repository, "test_fails.py")
|
|
2085
|
+
monkeypatch.setattr(gate_module, "MINIMUM_STAGED_PYTEST_PYTHON_MAJOR", 2)
|
|
2086
|
+
|
|
2087
|
+
exit_code = gate_module._staged_pytest_exit_code_for_current_python(
|
|
2088
|
+
temporary_git_repository
|
|
2089
|
+
)
|
|
2090
|
+
|
|
2091
|
+
assert exit_code != 0
|
|
2092
|
+
|
|
2093
|
+
|
|
2094
|
+
def test_main_staged_skips_pytest_but_runs_code_rules_below_minimum_python(
|
|
2095
|
+
temporary_git_repository: Path,
|
|
2096
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
2097
|
+
capsys: pytest.CaptureFixture[str],
|
|
2098
|
+
) -> None:
|
|
2099
|
+
"""The version guard skips only the staged pytest step. A staged code-rules
|
|
2100
|
+
violation still blocks the commit, so the two checks stay independent.
|
|
2101
|
+
"""
|
|
2102
|
+
write_file(temporary_git_repository / "module.py", "first_count = 1\n")
|
|
2103
|
+
commit_all_files(temporary_git_repository, "initial")
|
|
2104
|
+
write_file(
|
|
2105
|
+
temporary_git_repository / "module.py",
|
|
2106
|
+
"first_count = 1\n"
|
|
2107
|
+
"def compute_total(operand: int) -> int:\n"
|
|
2108
|
+
" result = operand + 1\n"
|
|
2109
|
+
" return result\n",
|
|
2110
|
+
)
|
|
2111
|
+
write_file(
|
|
2112
|
+
temporary_git_repository / "test_fails.py",
|
|
2113
|
+
"def test_intentionally_fails() -> None:\n assert False\n",
|
|
2114
|
+
)
|
|
2115
|
+
stage_file(temporary_git_repository, "module.py")
|
|
2116
|
+
stage_file(temporary_git_repository, "test_fails.py")
|
|
2117
|
+
monkeypatch.setattr(gate_module, "MINIMUM_STAGED_PYTEST_PYTHON_MAJOR", 99)
|
|
2118
|
+
monkeypatch.chdir(temporary_git_repository)
|
|
2119
|
+
|
|
2120
|
+
exit_code = gate_module.main(["--staged"])
|
|
2121
|
+
|
|
2122
|
+
captured = capsys.readouterr()
|
|
2123
|
+
assert exit_code == 1
|
|
2124
|
+
assert "skipping the staged pytest step" in captured.err
|
|
2125
|
+
assert "result" in captured.err
|
|
2126
|
+
|
|
2127
|
+
|
|
2046
2128
|
def test_hunk_header_pattern_captures_new_start_and_count() -> None:
|
|
2047
2129
|
header_match = gate_module.hunk_header_pattern().match("@@ -12,3 +45,6 @@ def scope")
|
|
2048
2130
|
assert header_match is not None
|
|
@@ -2578,3 +2660,65 @@ def test_run_staged_test_files_fails_when_one_group_fails(
|
|
|
2578
2660
|
assert exit_code != 0
|
|
2579
2661
|
captured_error = capsys.readouterr().err
|
|
2580
2662
|
assert "failing_skill" in captured_error
|
|
2663
|
+
|
|
2664
|
+
|
|
2665
|
+
def test_resolve_gate_python_executable_prefers_env_var_over_venv(
|
|
2666
|
+
tmp_path: Path,
|
|
2667
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
2668
|
+
) -> None:
|
|
2669
|
+
relative_segments = (
|
|
2670
|
+
gate_module.ALL_WINDOWS_VENV_PYTHON_RELATIVE_PATH_SEGMENTS
|
|
2671
|
+
if os.name == "nt"
|
|
2672
|
+
else gate_module.ALL_POSIX_VENV_PYTHON_RELATIVE_PATH_SEGMENTS
|
|
2673
|
+
)
|
|
2674
|
+
venv_python = tmp_path.joinpath(".venv", *relative_segments)
|
|
2675
|
+
venv_python.parent.mkdir(parents=True)
|
|
2676
|
+
venv_python.write_text("", encoding="utf-8")
|
|
2677
|
+
monkeypatch.setenv("CODE_RULES_GATE_PYTHON", "/configured/python")
|
|
2678
|
+
|
|
2679
|
+
resolved = gate_module._resolve_gate_python_executable(tmp_path)
|
|
2680
|
+
|
|
2681
|
+
assert resolved == "/configured/python"
|
|
2682
|
+
|
|
2683
|
+
|
|
2684
|
+
def test_resolve_gate_python_executable_falls_back_to_project_venv(
|
|
2685
|
+
tmp_path: Path,
|
|
2686
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
2687
|
+
) -> None:
|
|
2688
|
+
monkeypatch.delenv("CODE_RULES_GATE_PYTHON", raising=False)
|
|
2689
|
+
relative_segments = (
|
|
2690
|
+
gate_module.ALL_WINDOWS_VENV_PYTHON_RELATIVE_PATH_SEGMENTS
|
|
2691
|
+
if os.name == "nt"
|
|
2692
|
+
else gate_module.ALL_POSIX_VENV_PYTHON_RELATIVE_PATH_SEGMENTS
|
|
2693
|
+
)
|
|
2694
|
+
venv_python = tmp_path.joinpath(".venv", *relative_segments)
|
|
2695
|
+
venv_python.parent.mkdir(parents=True)
|
|
2696
|
+
venv_python.write_text("", encoding="utf-8")
|
|
2697
|
+
|
|
2698
|
+
resolved = gate_module._resolve_gate_python_executable(tmp_path)
|
|
2699
|
+
|
|
2700
|
+
assert resolved == str(venv_python)
|
|
2701
|
+
|
|
2702
|
+
|
|
2703
|
+
def test_resolve_gate_python_executable_falls_back_to_sys_executable(
|
|
2704
|
+
tmp_path: Path,
|
|
2705
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
2706
|
+
) -> None:
|
|
2707
|
+
monkeypatch.delenv("CODE_RULES_GATE_PYTHON", raising=False)
|
|
2708
|
+
|
|
2709
|
+
resolved = gate_module._resolve_gate_python_executable(tmp_path)
|
|
2710
|
+
|
|
2711
|
+
assert resolved == sys.executable
|
|
2712
|
+
|
|
2713
|
+
|
|
2714
|
+
def test_staged_pytest_environment_prepends_configured_pythonpath(
|
|
2715
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
2716
|
+
) -> None:
|
|
2717
|
+
monkeypatch.setenv("CODE_RULES_GATE_PYTHONPATH", "/configured/site-packages")
|
|
2718
|
+
monkeypatch.setenv("PYTHONPATH", "/already/on/path")
|
|
2719
|
+
|
|
2720
|
+
environment = gate_module._staged_pytest_environment()
|
|
2721
|
+
|
|
2722
|
+
assert environment["PYTHONPATH"] == os.pathsep.join(
|
|
2723
|
+
["/configured/site-packages", "/already/on/path"]
|
|
2724
|
+
)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Every case drives the production ``main`` / ``evaluate_copilot_quota`` path with
|
|
4
4
|
only the ``gh`` subprocess boundary (``_run_gh``) stubbed, fed a captured
|
|
5
|
-
``copilot_internal/user`` JSON fixture in the
|
|
5
|
+
``copilot_internal/user`` JSON fixture in the example-account shape.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import importlib.util
|
|
@@ -30,7 +30,7 @@ def _load_copilot_quota_module() -> ModuleType:
|
|
|
30
30
|
|
|
31
31
|
copilot_quota = _load_copilot_quota_module()
|
|
32
32
|
|
|
33
|
-
FIXTURE_PATH = Path(__file__).parent / "fixtures" / "
|
|
33
|
+
FIXTURE_PATH = Path(__file__).parent / "fixtures" / "copilot_internal_user_example.json"
|
|
34
34
|
AVAILABLE_USER_JSON = FIXTURE_PATH.read_text(encoding="utf-8")
|
|
35
35
|
FAKE_TOKEN_RESULT = (0, "ghp_faketoken_value\n")
|
|
36
36
|
|
|
@@ -75,7 +75,7 @@ def _isolate_account_sources(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) ->
|
|
|
75
75
|
def test_main_runs_copilot_when_premium_quota_available(
|
|
76
76
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
77
77
|
) -> None:
|
|
78
|
-
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "
|
|
78
|
+
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
|
|
79
79
|
monkeypatch.setattr(
|
|
80
80
|
copilot_quota,
|
|
81
81
|
"_run_gh",
|
|
@@ -91,7 +91,7 @@ def test_main_runs_copilot_when_premium_quota_available(
|
|
|
91
91
|
def test_main_exits_out_of_quota_when_premium_exhausted(
|
|
92
92
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
93
93
|
) -> None:
|
|
94
|
-
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "
|
|
94
|
+
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
|
|
95
95
|
monkeypatch.setattr(
|
|
96
96
|
copilot_quota,
|
|
97
97
|
"_run_gh",
|
|
@@ -106,7 +106,7 @@ def test_main_exits_out_of_quota_when_premium_exhausted(
|
|
|
106
106
|
def test_main_exits_api_down_on_non_json_response(
|
|
107
107
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
108
108
|
) -> None:
|
|
109
|
-
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "
|
|
109
|
+
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
|
|
110
110
|
monkeypatch.setattr(
|
|
111
111
|
copilot_quota,
|
|
112
112
|
"_run_gh",
|
|
@@ -121,7 +121,7 @@ def test_main_exits_api_down_on_non_json_response(
|
|
|
121
121
|
def test_main_exits_api_down_on_missing_premium_snapshot(
|
|
122
122
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
123
123
|
) -> None:
|
|
124
|
-
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "
|
|
124
|
+
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
|
|
125
125
|
monkeypatch.setattr(
|
|
126
126
|
copilot_quota,
|
|
127
127
|
"_run_gh",
|
|
@@ -136,7 +136,7 @@ def test_main_exits_api_down_on_missing_premium_snapshot(
|
|
|
136
136
|
def test_main_exits_api_down_when_gh_token_unresolved(
|
|
137
137
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
138
138
|
) -> None:
|
|
139
|
-
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "
|
|
139
|
+
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
|
|
140
140
|
monkeypatch.setattr(
|
|
141
141
|
copilot_quota,
|
|
142
142
|
"_run_gh",
|
|
@@ -180,7 +180,7 @@ def test_every_gh_backed_skip_writes_a_log_line_naming_the_scenario(
|
|
|
180
180
|
expected_exit_code: int,
|
|
181
181
|
expected_scenario: str,
|
|
182
182
|
) -> None:
|
|
183
|
-
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "
|
|
183
|
+
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
|
|
184
184
|
monkeypatch.setattr(
|
|
185
185
|
copilot_quota, "_run_gh", _gh_stub(FAKE_TOKEN_RESULT, api_result)
|
|
186
186
|
)
|
|
@@ -33,7 +33,7 @@ def _load_module(module_name: str) -> ModuleType:
|
|
|
33
33
|
copilot_quota = _load_module("copilot_quota")
|
|
34
34
|
reviewer_availability = _load_module("reviewer_availability")
|
|
35
35
|
|
|
36
|
-
FIXTURE_PATH = Path(__file__).parent / "fixtures" / "
|
|
36
|
+
FIXTURE_PATH = Path(__file__).parent / "fixtures" / "copilot_internal_user_example.json"
|
|
37
37
|
AVAILABLE_USER_JSON = FIXTURE_PATH.read_text(encoding="utf-8")
|
|
38
38
|
FAKE_TOKEN_RESULT = (0, "ghp_faketoken_value\n")
|
|
39
39
|
|
|
@@ -67,6 +67,7 @@ def _isolate_reviewer_environment(
|
|
|
67
67
|
) -> None:
|
|
68
68
|
monkeypatch.delenv("COPILOT_QUOTA_ACCOUNT", raising=False)
|
|
69
69
|
monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
|
|
70
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
|
|
70
71
|
monkeypatch.setattr(
|
|
71
72
|
reviewer_availability, "COPILOT_QUOTA_DEFAULT_ENV_FILE_PATH", tmp_path / ".env"
|
|
72
73
|
)
|
|
@@ -75,7 +76,7 @@ def _isolate_reviewer_environment(
|
|
|
75
76
|
def test_main_reports_copilot_available_when_quota_confirmed_available(
|
|
76
77
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
77
78
|
) -> None:
|
|
78
|
-
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "
|
|
79
|
+
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
|
|
79
80
|
monkeypatch.setattr(
|
|
80
81
|
copilot_quota, "_run_gh", _gh_stub(FAKE_TOKEN_RESULT, (0, AVAILABLE_USER_JSON))
|
|
81
82
|
)
|
|
@@ -106,7 +107,7 @@ def test_main_reports_copilot_down_when_opted_out_via_env(
|
|
|
106
107
|
def test_main_reports_copilot_down_when_out_of_quota(
|
|
107
108
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
108
109
|
) -> None:
|
|
109
|
-
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "
|
|
110
|
+
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
|
|
110
111
|
monkeypatch.setattr(
|
|
111
112
|
copilot_quota,
|
|
112
113
|
"_run_gh",
|
|
@@ -121,7 +122,7 @@ def test_main_reports_copilot_down_when_out_of_quota(
|
|
|
121
122
|
def test_main_reports_copilot_down_when_quota_api_is_down(
|
|
122
123
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
123
124
|
) -> None:
|
|
124
|
-
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "
|
|
125
|
+
monkeypatch.setenv("COPILOT_QUOTA_ACCOUNT", "example-account")
|
|
125
126
|
monkeypatch.setattr(
|
|
126
127
|
copilot_quota, "_run_gh", _gh_stub(FAKE_TOKEN_RESULT, (0, "not json at all"))
|
|
127
128
|
)
|
|
@@ -140,9 +141,20 @@ def test_main_reports_copilot_down_when_no_account_configured(
|
|
|
140
141
|
assert "scenario C" in captured.err
|
|
141
142
|
|
|
142
143
|
|
|
143
|
-
def
|
|
144
|
+
def test_main_reports_bugbot_down_when_no_env_vars_set(
|
|
144
145
|
capsys: pytest.CaptureFixture[str],
|
|
145
146
|
) -> None:
|
|
147
|
+
exit_code = reviewer_availability.main(["--reviewer", "bugbot"])
|
|
148
|
+
captured = capsys.readouterr()
|
|
149
|
+
assert exit_code == reviewer_availability.EXIT_CODE_REVIEWER_DOWN
|
|
150
|
+
assert "CLAUDE_REVIEWS_ENABLED" in captured.err
|
|
151
|
+
assert "CLAUDE_REVIEWS_DISABLED" not in captured.err
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_main_reports_bugbot_available_when_enabled_via_env(
|
|
155
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
156
|
+
) -> None:
|
|
157
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
|
|
146
158
|
exit_code = reviewer_availability.main(["--reviewer", "bugbot"])
|
|
147
159
|
captured = capsys.readouterr()
|
|
148
160
|
assert exit_code == reviewer_availability.EXIT_CODE_REVIEWER_AVAILABLE
|
|
@@ -153,6 +165,7 @@ def test_main_reports_bugbot_down_when_opted_out_via_env(
|
|
|
153
165
|
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
154
166
|
) -> None:
|
|
155
167
|
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
|
|
168
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
|
|
156
169
|
exit_code = reviewer_availability.main(["--reviewer", "bugbot"])
|
|
157
170
|
captured = capsys.readouterr()
|
|
158
171
|
assert exit_code == reviewer_availability.EXIT_CODE_REVIEWER_DOWN
|
|
@@ -43,20 +43,46 @@ def test_is_bugbot_disabled_via_env_returns_true_when_env_lists_bugbot(
|
|
|
43
43
|
assert reviews_disabled.is_bugbot_disabled_via_env() is True
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def
|
|
46
|
+
def test_is_bugbot_disabled_via_env_returns_true_when_no_env_vars_set(
|
|
47
47
|
monkeypatch: pytest.MonkeyPatch,
|
|
48
48
|
) -> None:
|
|
49
49
|
monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
|
|
50
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
|
|
51
|
+
assert reviews_disabled.is_bugbot_disabled_via_env() is True
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_is_bugbot_disabled_via_env_returns_false_when_enabled_lists_bugbot(
|
|
55
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
56
|
+
) -> None:
|
|
57
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
|
|
58
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
|
|
50
59
|
assert reviews_disabled.is_bugbot_disabled_via_env() is False
|
|
51
60
|
|
|
52
61
|
|
|
53
|
-
def
|
|
62
|
+
def test_is_bugbot_disabled_via_env_enabled_parse_is_case_and_space_tolerant(
|
|
54
63
|
monkeypatch: pytest.MonkeyPatch,
|
|
55
64
|
) -> None:
|
|
56
|
-
monkeypatch.
|
|
65
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
|
|
66
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", " BugBot ")
|
|
57
67
|
assert reviews_disabled.is_bugbot_disabled_via_env() is False
|
|
58
68
|
|
|
59
69
|
|
|
70
|
+
def test_is_bugbot_disabled_via_env_true_when_disabled_overrides_enabled(
|
|
71
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
72
|
+
) -> None:
|
|
73
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
|
|
74
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
|
|
75
|
+
assert reviews_disabled.is_bugbot_disabled_via_env() is True
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_is_bugbot_disabled_via_env_true_when_bugteam_listed_and_not_enabled(
|
|
79
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
80
|
+
) -> None:
|
|
81
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugteam")
|
|
82
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
|
|
83
|
+
assert reviews_disabled.is_bugbot_disabled_via_env() is True
|
|
84
|
+
|
|
85
|
+
|
|
60
86
|
def test_is_bugbot_disabled_via_env_true_when_both_tokens_listed_mixed_case(
|
|
61
87
|
monkeypatch: pytest.MonkeyPatch,
|
|
62
88
|
) -> None:
|
|
@@ -65,6 +91,29 @@ def test_is_bugbot_disabled_via_env_true_when_both_tokens_listed_mixed_case(
|
|
|
65
91
|
assert reviews_disabled.is_bugteam_disabled_via_env() is True
|
|
66
92
|
|
|
67
93
|
|
|
94
|
+
def test_is_bugbot_opted_out_via_env_returns_true_when_disabled_lists_bugbot(
|
|
95
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
96
|
+
) -> None:
|
|
97
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
|
|
98
|
+
assert reviews_disabled.is_bugbot_opted_out_via_env() is True
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_is_bugbot_opted_out_via_env_returns_false_when_only_enabled_lists_bugbot(
|
|
102
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
103
|
+
) -> None:
|
|
104
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
|
|
105
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
|
|
106
|
+
assert reviews_disabled.is_bugbot_opted_out_via_env() is False
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_is_bugbot_opted_out_via_env_returns_false_when_no_env_vars_set(
|
|
110
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
111
|
+
) -> None:
|
|
112
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
|
|
113
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
|
|
114
|
+
assert reviews_disabled.is_bugbot_opted_out_via_env() is False
|
|
115
|
+
|
|
116
|
+
|
|
68
117
|
def test_is_copilot_disabled_via_env_returns_true_when_env_lists_copilot(
|
|
69
118
|
monkeypatch: pytest.MonkeyPatch,
|
|
70
119
|
) -> None:
|
|
@@ -101,17 +150,19 @@ def test_cli_main_returns_zero_when_named_reviewer_disabled(
|
|
|
101
150
|
assert reviews_disabled.main(["--reviewer", "bugbot"]) == 0
|
|
102
151
|
|
|
103
152
|
|
|
104
|
-
def
|
|
153
|
+
def test_cli_main_returns_one_when_bugbot_enabled_via_env(
|
|
105
154
|
monkeypatch: pytest.MonkeyPatch,
|
|
106
155
|
) -> None:
|
|
107
156
|
monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
|
|
157
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
|
|
108
158
|
assert reviews_disabled.main(["--reviewer", "bugbot"]) == 1
|
|
109
159
|
|
|
110
160
|
|
|
111
|
-
def
|
|
161
|
+
def test_cli_main_returns_one_for_bugbot_when_enabled_despite_bugteam_optout(
|
|
112
162
|
monkeypatch: pytest.MonkeyPatch,
|
|
113
163
|
) -> None:
|
|
114
164
|
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugteam")
|
|
165
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
|
|
115
166
|
assert reviews_disabled.main(["--reviewer", "bugbot"]) == 1
|
|
116
167
|
|
|
117
168
|
|