argus-code-review 0.2.0__tar.gz → 0.2.2__tar.gz

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.
Files changed (148) hide show
  1. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/.gitignore +1 -0
  2. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/CHANGELOG.md +31 -1
  3. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/PKG-INFO +3 -1
  4. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/README.md +2 -0
  5. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/config.py +5 -0
  6. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/gemini_runner.py +15 -1
  7. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/llm/models.py +6 -1
  8. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-specialist-llm-patterns.md +1 -1
  9. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_config.py +16 -0
  10. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_gemini_runner.py +119 -0
  11. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_llm_models.py +15 -0
  12. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/LICENSE +0 -0
  13. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/__init__.py +0 -0
  14. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/bench.py +0 -0
  15. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/bench_default.toml +0 -0
  16. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/cli.py +0 -0
  17. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/coverage.py +0 -0
  18. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/dotenv_utils.py +0 -0
  19. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/gemini_cache.py +0 -0
  20. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/github_client.py +0 -0
  21. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/graph.py +0 -0
  22. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/helpers.py +0 -0
  23. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/llm/output_models.py +0 -0
  24. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/llm/pricing.py +0 -0
  25. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/models.py +0 -0
  26. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/openai_client.py +0 -0
  27. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/openai_runner.py +0 -0
  28. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/pipeline_models.py +0 -0
  29. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/__init__.py +0 -0
  30. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/actions_scanner.py +0 -0
  31. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/engine.py +0 -0
  32. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/eslint_bundle/.gitignore +0 -0
  33. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/eslint_bundle/README.md +0 -0
  34. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/eslint_bundle/eslint.config.js +0 -0
  35. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/eslint_bundle/package-lock.json +0 -0
  36. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/eslint_bundle/package.json +0 -0
  37. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/js_scanner.py +0 -0
  38. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/migration_scanner.py +0 -0
  39. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/rules/README.md +0 -0
  40. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/sarif.py +0 -0
  41. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/scanner_utils.py +0 -0
  42. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/secrets_scanner.py +0 -0
  43. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/shadow.py +0 -0
  44. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/terraform_scanner.py +0 -0
  45. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/precheck/workflow_lint_scanner.py +0 -0
  46. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/__init__.py +0 -0
  47. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-blocking-validator.md +0 -0
  48. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-coverage-check.md +0 -0
  49. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-cross-cutting.md +0 -0
  50. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-feedback-verifier.md +0 -0
  51. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-lite.md +0 -0
  52. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-planner.md +0 -0
  53. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-preflight-router.md +0 -0
  54. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-prior-art.md +0 -0
  55. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-specialist-deployment.md +0 -0
  56. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-specialist-frontend.md +0 -0
  57. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-specialist-infra.md +0 -0
  58. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-specialist-observability.md +0 -0
  59. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-specialist-orchestration.md +0 -0
  60. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-specialist-security.md +0 -0
  61. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-specialist-slackbot.md +0 -0
  62. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-specialist-sql.md +0 -0
  63. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-subagent.md +0 -0
  64. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-tests-and-docs.md +0 -0
  65. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts/pr-review-writer.md +0 -0
  66. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/prompts_runtime.py +0 -0
  67. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/repo_provision.py +0 -0
  68. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/review_tools.py +0 -0
  69. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/runners.py +0 -0
  70. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/storage/__init__.py +0 -0
  71. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/storage/http.py +0 -0
  72. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/storage/models.py +0 -0
  73. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/storage/precheck.py +0 -0
  74. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/storage/resolver.py +0 -0
  75. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/storage/session.py +0 -0
  76. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/storage/sql.py +0 -0
  77. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/argus/storage/sqlite.py +0 -0
  78. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/pyproject.toml +0 -0
  79. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/schema/008_add_code_reviews.sql +0 -0
  80. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/schema/009_add_reviewer_version.sql +0 -0
  81. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/schema/010_add_review_patterns.sql +0 -0
  82. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/schema/011_add_review_progress_columns.sql +0 -0
  83. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/schema/015_create_agent_runs.sql +0 -0
  84. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/schema/016_add_agent_runs_failure_reason.sql +0 -0
  85. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/schema/017_add_precheck_rules.sql +0 -0
  86. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/__init__.py +0 -0
  87. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/conftest.py +0 -0
  88. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/golden/review_response.schema.json +0 -0
  89. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/storage/__init__.py +0 -0
  90. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/storage/test_backend_contract.py +0 -0
  91. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/storage/test_http.py +0 -0
  92. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/storage/test_models.py +0 -0
  93. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/storage/test_resolver.py +0 -0
  94. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/storage/test_session.py +0 -0
  95. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/storage/test_sql.py +0 -0
  96. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/storage/test_sqlite_backend.py +0 -0
  97. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_actions_scanner.py +0 -0
  98. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_argus_review_local.py +0 -0
  99. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_bench.py +0 -0
  100. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_catchup_gate.py +0 -0
  101. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_cli_args.py +0 -0
  102. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_cli_output_contract.py +0 -0
  103. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_cli_post_review.py +0 -0
  104. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_cli_preflight.py +0 -0
  105. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_cli_prompts.py +0 -0
  106. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_conftest.py +0 -0
  107. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_gemini_cache.py +0 -0
  108. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_github_client_checks_signal.py +0 -0
  109. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_github_client_write.py +0 -0
  110. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_graph_fetch_diff.py +0 -0
  111. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_graph_get_llm_temperature.py +0 -0
  112. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_graph_http_guards.py +0 -0
  113. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_graph_precheck.py +0 -0
  114. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_graph_preflight_image_bump.py +0 -0
  115. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_graph_progress.py +0 -0
  116. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_graph_storage_resolution.py +0 -0
  117. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_graph_timeout_surfacing.py +0 -0
  118. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_js_scanner.py +0 -0
  119. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_lite_review_cost.py +0 -0
  120. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_llm_models_override.py +0 -0
  121. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_llm_pricing.py +0 -0
  122. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_migration_scanner.py +0 -0
  123. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_models.py +0 -0
  124. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_multi_round.py +0 -0
  125. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_openai_client.py +0 -0
  126. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_openai_runner.py +0 -0
  127. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_packaging.py +0 -0
  128. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_plan_review.py +0 -0
  129. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_precheck_engine.py +0 -0
  130. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_precheck_engine_integration.py +0 -0
  131. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_precheck_sarif.py +0 -0
  132. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_precheck_shadow.py +0 -0
  133. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_precheck_shadow_integration.py +0 -0
  134. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_prompts.py +0 -0
  135. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_repo_provision.py +0 -0
  136. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_review_patterns_integration.py +0 -0
  137. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_review_tools.py +0 -0
  138. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_runners_context7.py +0 -0
  139. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_runners_context_usage.py +0 -0
  140. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_runners_helpers.py +0 -0
  141. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_runners_new.py +0 -0
  142. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_scanner_utils.py +0 -0
  143. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_secrets_scanner.py +0 -0
  144. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_specialist_validation.py +0 -0
  145. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_storage_precheck.py +0 -0
  146. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_terraform_scanner.py +0 -0
  147. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_terraform_scanner_integration.py +0 -0
  148. {argus_code_review-0.2.0 → argus_code_review-0.2.2}/tests/test_workflow_lint_scanner.py +0 -0
@@ -12,3 +12,4 @@ build/
12
12
  .env.local
13
13
  .env.*.local
14
14
  !.env.example
15
+ .argus/
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2026-09-11
11
+
12
+ ### Added
13
+
14
+ - `docs/BENCH.md`, documenting the reviewer bench's config-driven platform,
15
+ model, and caching selection for leaf-reviewer roles, including the override
16
+ chain, valid values, platform credentials, and a worked Gemini configuration.
17
+ README and `docs/BUILDING_A_REVIEW_LOOP.md` now point to the new guide.
18
+
19
+ ### Fixed
20
+
21
+ - Updated the `gemini-mini` alias from the superseded `gemini-3-flash-preview`
22
+ model to the stable `gemini-3.8-flash` release. The approved-model policy
23
+ table's Google default now matches, and a regression test pins the alias to
24
+ the approved value.
25
+
26
+ ## [0.2.1] - 2026-09-10
27
+
28
+ ### Added
29
+
30
+ - `GOOGLE_BASE_URL` proxy support for the Gemini runner (`argus/gemini_runner.py`),
31
+ mirroring `OPENAI_BASE_URL`: closes a gap where the Gemini platform could only
32
+ be used with a raw, unproxied `GOOGLE_API_KEY` talking directly to
33
+ `generativelanguage.googleapis.com`, unlike the OpenAI (`OPENAI_BASE_URL`) and
34
+ Claude (`ANTHROPIC_BASE_URL`) paths that the `argus-review-loop` skill relies
35
+ on to route through a short-lived, PR-scoped credential proxy instead of a
36
+ standing API key.
37
+
10
38
  ## [0.2.0] - 2026-09-10
11
39
 
12
40
  ### Added
@@ -175,7 +203,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
175
203
  packaged set.
176
204
  - `argus --version`, `argus prompts list`, and `argus prompts export`.
177
205
 
178
- [Unreleased]: https://github.com/redesignhealth/argus-review/compare/v0.2.0...HEAD
206
+ [Unreleased]: https://github.com/redesignhealth/argus-review/compare/v0.2.2...HEAD
207
+ [0.2.2]: https://github.com/redesignhealth/argus-review/compare/v0.2.1...v0.2.2
208
+ [0.2.1]: https://github.com/redesignhealth/argus-review/compare/v0.2.0...v0.2.1
179
209
  [0.2.0]: https://github.com/redesignhealth/argus-review/compare/v0.1.5...v0.2.0
180
210
  [0.1.5]: https://github.com/redesignhealth/argus-review/compare/v0.1.4...v0.1.5
181
211
  [0.1.4]: https://github.com/redesignhealth/argus-review/compare/v0.1.3...v0.1.4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: argus-code-review
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Self-orchestrated PR review agent using LangGraph + Claude Agent SDK
5
5
  Project-URL: Repository, https://github.com/redesignhealth/argus-review
6
6
  Project-URL: Issues, https://github.com/redesignhealth/argus-review/issues
@@ -437,6 +437,8 @@ see the [write-up](https://www.redesignhealth.com/content/agentic-code-review-ha
437
437
  contract for self-hosters.
438
438
  - [`docs/BUILDING_A_REVIEW_LOOP.md`](docs/BUILDING_A_REVIEW_LOOP.md) — how to
439
439
  wrap Argus in a review → fix → re-review automation loop gated on CI.
440
+ - [`docs/BENCH.md`](docs/BENCH.md) — configure the static reviewer bench to
441
+ choose platforms and models for leaf reviewers.
440
442
  - [`docs/PRECHECKS.md`](docs/PRECHECKS.md) — the deterministic, non-LLM gate
441
443
  that runs before the LLM pipeline: CI-status routing signal and custom
442
444
  semgrep rules mined from Argus's own recurring findings.
@@ -385,6 +385,8 @@ see the [write-up](https://www.redesignhealth.com/content/agentic-code-review-ha
385
385
  contract for self-hosters.
386
386
  - [`docs/BUILDING_A_REVIEW_LOOP.md`](docs/BUILDING_A_REVIEW_LOOP.md) — how to
387
387
  wrap Argus in a review → fix → re-review automation loop gated on CI.
388
+ - [`docs/BENCH.md`](docs/BENCH.md) — configure the static reviewer bench to
389
+ choose platforms and models for leaf reviewers.
388
390
  - [`docs/PRECHECKS.md`](docs/PRECHECKS.md) — the deterministic, non-LLM gate
389
391
  that runs before the LLM pipeline: CI-status routing signal and custom
390
392
  semgrep rules mined from Argus's own recurring findings.
@@ -135,6 +135,10 @@ class Settings(BaseSettings):
135
135
  ``platform = "gemini"``. Only required if you actually
136
136
  opt a role into that platform (see ``argus.bench``'s
137
137
  override chain) -- the packaged default bench never does.
138
+ GOOGLE_BASE_URL: Optional override for the Gemini API endpoint (e.g. for
139
+ proxying). Mirrors ``OPENAI_BASE_URL`` above. Note that GOOGLE_API_KEY
140
+ will be forwarded to whatever endpoint is configured here; point only
141
+ at trusted proxy hosts.
138
142
  ARGUS_BENCH_FILE: Highest-priority bench-config override file. See
139
143
  ``argus.bench`` for the full override search chain.
140
144
  ARGUS_NO_BENCH_OVERRIDES: Set truthy to ignore every bench override
@@ -154,6 +158,7 @@ class Settings(BaseSettings):
154
158
  OPENAI_API_KEY: str
155
159
  OPENAI_BASE_URL: str | None = None
156
160
  GOOGLE_API_KEY: str | None = None
161
+ GOOGLE_BASE_URL: str | None = None
157
162
 
158
163
  ARGUS_DB_URL: str | None = None
159
164
  SUPABASE_DB_URL: str | None = None
@@ -607,7 +607,21 @@ async def _run_turns(
607
607
  # clause catches and translates into a clean `failure_reason="timeout"` result
608
608
  # -- rather than racing the outer deadline so closely that the raw
609
609
  # httpx exception could escape uncaught instead.
610
- http_options = types.HttpOptions(timeout=int(timeout_s * _HTTP_TIMEOUT_FRACTION * 1000))
610
+ #
611
+ # `base_url` is only set when truthy; `None` safely defaults to the
612
+ # standard Gemini endpoint (`generativelanguage.googleapis.com`) --
613
+ # mirrors `OPENAI_BASE_URL`'s exact same optional-proxy pattern in
614
+ # `argus.openai_runner`/`argus.openai_client`. This same `http_options`
615
+ # instance is reused as-is by `_maybe_activate_cache`'s own
616
+ # short-lived `genai.Client` (see that function's docstring for why it
617
+ # needs a separate client instance), so threading `base_url` through
618
+ # here covers both `genai.Client(...)` construction sites in this
619
+ # module without a second read of `settings.GOOGLE_BASE_URL`.
620
+ base_url = getattr(settings, "GOOGLE_BASE_URL", None)
621
+ http_options = types.HttpOptions(
622
+ timeout=int(timeout_s * _HTTP_TIMEOUT_FRACTION * 1000),
623
+ base_url=base_url if isinstance(base_url, str) and base_url else None,
624
+ )
611
625
  client = genai.Client(api_key=api_key, http_options=http_options)
612
626
  logger.info(
613
627
  "Gemini session started: %s model=%s caching=%s timeout=%ss",
@@ -105,7 +105,12 @@ ALIAS_MAP: Final[dict[str, str]] = {
105
105
  # TODO: upgrade to stable gemini-3.1-pro (non-preview) when GA;
106
106
  # re-eval by 2026-11-01.
107
107
  "gemini-frontier": "gemini-3.1-pro-preview",
108
- "gemini-mini": "gemini-3-flash-preview",
108
+ # gemini-3-flash-preview has been superseded by gemini-3.8-flash, the
109
+ # stable (non-preview) release -- same reasoning as gemini-frontier's
110
+ # move off a deprecated preview above: prefer the stable successor over
111
+ # a preview model once one exists. test_llm_models.py pins this exact
112
+ # value as a regression guard.
113
+ "gemini-mini": "gemini-3.8-flash",
109
114
  }
110
115
 
111
116
  # Short-lived pins for evals / preview models. Anything in here is a known
@@ -9,7 +9,7 @@ Only approved model families. Check model strings in code against this table:
9
9
  |----------|----------|---------|
10
10
  | Anthropic | claude-opus-4, claude-sonnet-4, claude-haiku-4 (use `CLAUDE_MINI`), claude-fable-5 (use `CLAUDE_FRONTIER`), claude-opus-5 (use `CLAUDE_OPUS`), claude-sonnet-4-6 (use `CLAUDE_DEFAULT`) families; claude-sonnet-5 (the previous `CLAUDE_DEFAULT` pin) remains valid as an explicit `ARGUS_SPECIALIST_MODEL`/`--specialist-model` override value, not as a new hardcoded default | claude-sonnet-4-6 |
11
11
  | OpenAI | gpt-5.4 family (gpt-5.4, gpt-5.4-mini) and gpt-5.6 family | gpt-5.6-luna |
12
- | Google | gemini-3 family | gemini-3-flash |
12
+ | Google | gemini-3 family | gemini-3.8-flash |
13
13
 
14
14
  Flag any use of: o3, o1, gpt-4 family, gpt-5-mini, claude-3/3.5 family, gemini-1.5/2.5 family.
15
15
 
@@ -149,6 +149,7 @@ def test_optional_fields_default_to_none(monkeypatch: pytest.MonkeyPatch) -> Non
149
149
  "ARGUS_CONTEXT7_LIBRARY_ID",
150
150
  "ARGUS_CONTEXT7_BASE_URL",
151
151
  "GOOGLE_API_KEY",
152
+ "GOOGLE_BASE_URL",
152
153
  "ARGUS_BENCH_FILE",
153
154
  "ARGUS_GEMINI_CACHE_DIR",
154
155
  ):
@@ -160,6 +161,7 @@ def test_optional_fields_default_to_none(monkeypatch: pytest.MonkeyPatch) -> Non
160
161
  assert settings.ARGUS_CONTEXT7_BASE_URL is None
161
162
  assert settings.ARGUS_PROMPTS_DIR is None
162
163
  assert settings.GOOGLE_API_KEY is None
164
+ assert settings.GOOGLE_BASE_URL is None
163
165
  assert settings.ARGUS_BENCH_FILE is None
164
166
  assert settings.ARGUS_GEMINI_CACHE_DIR is None
165
167
 
@@ -252,3 +254,17 @@ def test_openai_base_url_override(monkeypatch: pytest.MonkeyPatch) -> None:
252
254
  monkeypatch.setenv("OPENAI_BASE_URL", "https://proxy.example.com/v1")
253
255
  settings = get_settings()
254
256
  assert settings.OPENAI_BASE_URL == "https://proxy.example.com/v1"
257
+
258
+
259
+ def test_google_base_url_defaults_to_none(monkeypatch: pytest.MonkeyPatch) -> None:
260
+ _set_required(monkeypatch)
261
+ monkeypatch.delenv("GOOGLE_BASE_URL", raising=False)
262
+ settings = get_settings()
263
+ assert settings.GOOGLE_BASE_URL is None
264
+
265
+
266
+ def test_google_base_url_override(monkeypatch: pytest.MonkeyPatch) -> None:
267
+ _set_required(monkeypatch)
268
+ monkeypatch.setenv("GOOGLE_BASE_URL", "https://proxy.example.com/v1beta")
269
+ settings = get_settings()
270
+ assert settings.GOOGLE_BASE_URL == "https://proxy.example.com/v1beta"
@@ -1395,3 +1395,122 @@ class TestGeminiRedactionHooks:
1395
1395
  inputs = {"label": "test"}
1396
1396
  redacted = _redact_gemini_inputs(inputs)
1397
1397
  assert redacted == {"label": "test"}
1398
+
1399
+
1400
+ # ---------------------------------------------------------------------------
1401
+ # GOOGLE_BASE_URL threading (mirrors OPENAI_BASE_URL in
1402
+ # tests/test_openai_runner.py / tests/test_openai_client.py): a proxy base
1403
+ # URL configured via settings must reach the underlying `genai.Client`'s
1404
+ # `http_options.base_url`, at every `genai.Client(...)` construction site in
1405
+ # this module -- the session's own client (`_run_turns`) AND the dedicated,
1406
+ # short-lived client `_maybe_activate_cache`'s `_create_fn` builds for
1407
+ # `caches.create()`.
1408
+ # ---------------------------------------------------------------------------
1409
+
1410
+
1411
+ class TestGoogleBaseUrlOverride:
1412
+ async def test_settings_google_base_url_reaches_session_client_http_options(self) -> None:
1413
+ """GOOGLE_BASE_URL on settings is threaded through to the session's
1414
+ own `genai.Client`'s `http_options.base_url`."""
1415
+ entry = _make_entry(caching="off")
1416
+ settings = _make_settings()
1417
+ settings.GOOGLE_BASE_URL = "https://proxy.example.com/v1beta"
1418
+ response = _make_response(calls=[("finish_review", {"files_explored": []})])
1419
+ fake_client = _make_fake_client([response])
1420
+
1421
+ with patch("argus.gemini_runner.genai.Client", return_value=fake_client) as mock_ctor:
1422
+ result = await run_session_gemini(
1423
+ entry=entry,
1424
+ system_prompt="sys",
1425
+ user_message="msg",
1426
+ settings=settings,
1427
+ repo_root="/tmp/does-not-need-to-exist",
1428
+ )
1429
+
1430
+ assert result.failure_reason is None
1431
+ mock_ctor.assert_called_once()
1432
+ http_options = mock_ctor.call_args.kwargs["http_options"]
1433
+ assert http_options.base_url == "https://proxy.example.com/v1beta"
1434
+
1435
+ async def test_no_google_base_url_defaults_to_sdk_default(self) -> None:
1436
+ """Without an override, `http_options.base_url` stays `None` -- the
1437
+ SDK's own default (`generativelanguage.googleapis.com`) applies,
1438
+ exactly like the OPENAI_BASE_URL/AsyncOpenAI pattern this mirrors."""
1439
+ entry = _make_entry(caching="off")
1440
+ settings = _make_settings() # GOOGLE_BASE_URL never set on this MagicMock
1441
+ response = _make_response(calls=[("finish_review", {"files_explored": []})])
1442
+ fake_client = _make_fake_client([response])
1443
+
1444
+ with patch("argus.gemini_runner.genai.Client", return_value=fake_client) as mock_ctor:
1445
+ result = await run_session_gemini(
1446
+ entry=entry,
1447
+ system_prompt="sys",
1448
+ user_message="msg",
1449
+ settings=settings,
1450
+ repo_root="/tmp/does-not-need-to-exist",
1451
+ )
1452
+
1453
+ assert result.failure_reason is None
1454
+ http_options = mock_ctor.call_args.kwargs["http_options"]
1455
+ assert http_options.base_url is None
1456
+
1457
+ async def test_google_base_url_composes_with_existing_timeout(self) -> None:
1458
+ """A `base_url` override must compose with the existing `timeout=`
1459
+ `HttpOptions` field -- both set together on the same instance --
1460
+ rather than one replacing the other."""
1461
+ from argus.gemini_runner import _HTTP_TIMEOUT_FRACTION
1462
+
1463
+ entry = _make_entry(caching="off")
1464
+ settings = _make_settings(session_timeout=300)
1465
+ settings.GOOGLE_BASE_URL = "https://proxy.example.com/v1beta"
1466
+ response = _make_response(calls=[("finish_review", {"files_explored": []})])
1467
+ fake_client = _make_fake_client([response])
1468
+
1469
+ with patch("argus.gemini_runner.genai.Client", return_value=fake_client) as mock_ctor:
1470
+ result = await run_session_gemini(
1471
+ entry=entry,
1472
+ system_prompt="sys",
1473
+ user_message="msg",
1474
+ settings=settings,
1475
+ repo_root="/tmp/does-not-need-to-exist",
1476
+ timeout_s=10.0,
1477
+ )
1478
+
1479
+ assert result.failure_reason is None
1480
+ http_options = mock_ctor.call_args.kwargs["http_options"]
1481
+ assert http_options.base_url == "https://proxy.example.com/v1beta"
1482
+ assert http_options.timeout == int(10.0 * _HTTP_TIMEOUT_FRACTION * 1000)
1483
+
1484
+ async def test_google_base_url_also_reaches_the_cache_creation_client(self) -> None:
1485
+ """`_maybe_activate_cache`'s `_create_fn` builds its own, separate
1486
+ `genai.Client` for `caches.create()` (see that function's docstring)
1487
+ -- it must carry the same `base_url` override as the session's own
1488
+ client, not silently fall back to the real API host."""
1489
+ entry = _make_entry(caching="on")
1490
+ settings = _make_settings()
1491
+ settings.GOOGLE_BASE_URL = "https://proxy.example.com/v1beta"
1492
+ response = _make_response(calls=[("finish_review", {"files_explored": []})])
1493
+ fake_client = _make_fake_client([response], cache_name="cachedContents/abc123")
1494
+
1495
+ constructed_http_options: list[types.HttpOptions] = []
1496
+
1497
+ def _new_client(*args: Any, **kwargs: Any) -> MagicMock:
1498
+ constructed_http_options.append(kwargs["http_options"])
1499
+ return fake_client
1500
+
1501
+ with patch("argus.gemini_runner.genai.Client", side_effect=_new_client):
1502
+ result = await run_session_gemini(
1503
+ entry=entry,
1504
+ system_prompt="sys",
1505
+ user_message="msg",
1506
+ settings=settings,
1507
+ repo_root="/tmp/does-not-need-to-exist",
1508
+ )
1509
+
1510
+ assert result.failure_reason is None
1511
+ # Two `genai.Client` constructions: the session's own, and the
1512
+ # dedicated cache-creation client -- both must carry the override.
1513
+ assert len(constructed_http_options) == 2
1514
+ assert all(
1515
+ ho.base_url == "https://proxy.example.com/v1beta" for ho in constructed_http_options
1516
+ )
@@ -14,6 +14,7 @@ from argus.llm.models import (
14
14
  CLAUDE_DEFAULT,
15
15
  EXPERIMENTAL_MODELS,
16
16
  GEMINI_FRONTIER,
17
+ GEMINI_MINI,
17
18
  GPT_FRONTIER,
18
19
  GPT_MINI,
19
20
  estimate_cost_usd,
@@ -163,6 +164,20 @@ class TestEstimateCostUsd:
163
164
  cost = estimate_cost_usd(GPT_MINI, input_tokens=1000, output_tokens=500)
164
165
  assert cost > 0.0
165
166
 
167
+ def test_gemini_mini_pinned_to_approved_model(self) -> None:
168
+ """Regression guard for gemini-mini, alongside gpt-frontier and
169
+ gpt-mini's guards above: gemini-3-flash-preview has been superseded by
170
+ gemini-3.8-flash, the stable (non-preview) release on the approved model
171
+ list (see pr-review-specialist-llm-patterns.md). Pin the alias to that
172
+ approved value so an accidental regression to a preview model or
173
+ unapproved string is caught here instead of at review time.
174
+
175
+ Also exercises estimate_cost_usd(GEMINI_MINI, ...) directly."""
176
+ assert ALIAS_MAP["gemini-mini"] == "gemini-3.8-flash"
177
+ assert GEMINI_MINI == "gemini-3.8-flash"
178
+ cost = estimate_cost_usd(GEMINI_MINI, input_tokens=1000, output_tokens=500)
179
+ assert cost > 0.0
180
+
166
181
 
167
182
  class TestResolveOverrides:
168
183
  def test_resolve_claude_default_honors_env_override(