argus-code-review 0.3.3__tar.gz → 0.3.4__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.
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/CHANGELOG.md +11 -1
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/PKG-INFO +1 -1
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/gemini_runner.py +88 -57
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/helpers.py +45 -8
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/pipeline_models.py +8 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-cross-cutting.md +5 -6
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-lite.md +1 -1
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/runners.py +16 -36
- argus_code_review-0.3.4/tests/test_failure_diagnostics.py +129 -0
- argus_code_review-0.3.4/tests/test_gemini_http_retry.py +136 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_gemini_runner.py +18 -11
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/.gitignore +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/LICENSE +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/README.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/__init__.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/bench.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/bench_default.toml +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/cli.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/config.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/coverage.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/dotenv_utils.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/gemini_cache.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/github_client.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/graph.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/llm/models.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/llm/output_models.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/llm/pricing.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/llm/usage.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/models.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/openai_client.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/openai_runner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/__init__.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/actions_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/engine.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/eslint_bundle/.gitignore +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/eslint_bundle/README.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/eslint_bundle/eslint.config.js +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/eslint_bundle/package-lock.json +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/eslint_bundle/package.json +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/js_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/migration_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/rules/README.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/sarif.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/scanner_utils.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/secrets_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/shadow.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/terraform_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/precheck/workflow_lint_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/__init__.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-blocking-validator.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-coverage-check.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-feedback-verifier.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-planner.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-preflight-router.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-prior-art.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-scope.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-specialist-deployment.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-specialist-frontend.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-specialist-infra.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-specialist-llm-patterns.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-specialist-observability.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-specialist-orchestration.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-specialist-security.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-specialist-slackbot.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-specialist-sql.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-subagent.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-tests-and-docs.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-writer.md +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts_runtime.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/repo_provision.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/review_tools.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/scope.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/storage/__init__.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/storage/http.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/storage/models.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/storage/precheck.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/storage/resolver.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/storage/session.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/storage/sql.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/storage/sqlite.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/pyproject.toml +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/schema/008_add_code_reviews.sql +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/schema/009_add_reviewer_version.sql +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/schema/010_add_review_patterns.sql +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/schema/011_add_review_progress_columns.sql +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/schema/015_create_agent_runs.sql +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/schema/016_add_agent_runs_failure_reason.sql +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/schema/017_add_precheck_rules.sql +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/schema/018_widen_agent_runs_failure_reason.sql +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/__init__.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/conftest.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/golden/review_response.schema.json +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/storage/__init__.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/storage/test_backend_contract.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/storage/test_http.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/storage/test_models.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/storage/test_resolver.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/storage/test_session.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/storage/test_sql.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/storage/test_sqlite_backend.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_actions_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_argus_review_local.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_bench.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_bench_config_guard.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_catchup_gate.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_cli_args.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_cli_output_contract.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_cli_post_review.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_cli_preflight.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_cli_prompts.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_config.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_conftest.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_dotenv_utils.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_gemini_cache.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_github_client_checks_signal.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_github_client_write.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_graph_fetch_diff.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_graph_get_llm_temperature.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_graph_http_guards.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_graph_precheck.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_graph_preflight_image_bump.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_graph_progress.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_graph_storage_resolution.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_graph_timeout_surfacing.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_js_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_llm_models.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_llm_models_override.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_llm_pricing.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_migration_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_models.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_multi_round.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_openai_client.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_openai_runner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_output_models.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_packaging.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_plan_review.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_precheck_engine.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_precheck_engine_integration.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_precheck_sarif.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_precheck_shadow.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_precheck_shadow_integration.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_prompts.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_repo_provision.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_review_patterns_integration.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_review_tools.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_runners_context7.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_runners_context_usage.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_runners_helpers.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_runners_new.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_runners_turn_budget.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_scanner_utils.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_scope.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_scope_gate.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_sdk_bundled_cli_version.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_secrets_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_specialist_validation.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_stage_costs.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_storage_precheck.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_structured_output_methods.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_terraform_scanner.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_terraform_scanner_integration.py +0 -0
- {argus_code_review-0.3.3 → argus_code_review-0.3.4}/tests/test_workflow_lint_scanner.py +0 -0
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.4] - 2026-10-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Gemini requests now retry HTTP 500/502/503/504 in place at every turn (up to three attempts with exponential jitter), retaining the conversation and without replaying tools. The outer session timeout still bounds the total work. The quota-aware whole-session turn-0 retry remains for 429s and transport errors, but a 5xx the SDK has already retried (three attempts) is not restarted, so a persistent 5xx costs at most three requests per turn; 429s are excluded from the SDK's status-only retry policy so daily-quota errors still fail fast.
|
|
15
|
+
- Failed Gemini sessions preserve cost and tool accounting from completed turns, and include the failed turn index in their short failure detail.
|
|
16
|
+
- Reviewer failure details now reach degraded-coverage reports instead of disappearing after the runner. Only canonical provider codes/statuses, standard HTTP reason phrases (such as `502 Bad Gateway`) and httpx transport error names are rendered; raw provider messages are never included. The cause precedes the reviewer-area name so it remains visible to consumers that truncate descriptions to 200 characters. Existing failure-reason values and database constraints are unchanged.
|
|
17
|
+
- The model-registry check in the cross-cutting and lite prompts now applies to the repository under review instead of naming one specific package's constants. A package that has no registry of its own is no longer told it must use someone else's, and a legitimately overridden model value is not flagged. The em-dash character rule, which was one project's prose convention rather than a general review criterion, is removed from the shared prompt.
|
|
18
|
+
|
|
10
19
|
## [0.3.3] - 2026-10-22
|
|
11
20
|
|
|
12
21
|
### Changed
|
|
@@ -448,7 +457,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
448
457
|
packaged set.
|
|
449
458
|
- `argus --version`, `argus prompts list`, and `argus prompts export`.
|
|
450
459
|
|
|
451
|
-
[Unreleased]: https://github.com/redesignhealth/argus-review/compare/v0.3.
|
|
460
|
+
[Unreleased]: https://github.com/redesignhealth/argus-review/compare/v0.3.4...HEAD
|
|
461
|
+
[0.3.4]: https://github.com/redesignhealth/argus-review/compare/v0.3.3...v0.3.4
|
|
452
462
|
[0.3.3]: https://github.com/redesignhealth/argus-review/compare/v0.3.2...v0.3.3
|
|
453
463
|
[0.3.2]: https://github.com/redesignhealth/argus-review/compare/v0.3.1...v0.3.2
|
|
454
464
|
[0.3.1]: https://github.com/redesignhealth/argus-review/compare/v0.3.0...v0.3.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: argus-code-review
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
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
|
|
@@ -147,7 +147,7 @@ import logging
|
|
|
147
147
|
import os
|
|
148
148
|
import random
|
|
149
149
|
import re
|
|
150
|
-
from dataclasses import dataclass
|
|
150
|
+
from dataclasses import dataclass, field
|
|
151
151
|
from datetime import datetime, timezone
|
|
152
152
|
from typing import Any
|
|
153
153
|
|
|
@@ -219,7 +219,9 @@ _HTTP_TIMEOUT_FRACTION = 0.8
|
|
|
219
219
|
# DEADLINE_EXCEEDED/INTERNAL, or an httpx timeout/connect/read/remote-protocol
|
|
220
220
|
# error) before the model returned ANY response (i.e. on turn 0), so the retry
|
|
221
221
|
# restarts the session cleanly from the beginning. Later failures are not
|
|
222
|
-
#
|
|
222
|
+
# restarted; the SDK retries only individual 5xx requests (see
|
|
223
|
+
# `_HTTP_RETRY_ATTEMPTS`), and a 5xx it already retried is not restarted again,
|
|
224
|
+
# so the restart covers 429 and transport errors only. The cooldown depends on the error kind (see `_retry_decision`):
|
|
223
225
|
# 45-60 s jitter for 429/RESOURCE_EXHAUSTED (per-minute quota windows), 5-10 s
|
|
224
226
|
# for 5xx/network errors; a server Retry-After/retryDelay >= 5 s is honored;
|
|
225
227
|
# never more than 60 s. Daily-quota 429s are not retried. Overridable via
|
|
@@ -249,6 +251,14 @@ _TRANSIENT_HTTPX = (
|
|
|
249
251
|
httpx.ReadError,
|
|
250
252
|
httpx.RemoteProtocolError,
|
|
251
253
|
)
|
|
254
|
+
|
|
255
|
+
# SDK-level per-request retry of 5xx at any turn; the request is re-sent
|
|
256
|
+
# unchanged, so no tools are replayed. 429s stay in _retry_decision because the
|
|
257
|
+
# SDK cannot tell daily quota from per-minute limits or honor Retry-After.
|
|
258
|
+
_HTTP_RETRY_ATTEMPTS = 3 # including the initial call
|
|
259
|
+
_HTTP_RETRY_STATUS_CODES = (500, 502, 503, 504)
|
|
260
|
+
_HTTP_RETRY_INITIAL_DELAY_S = 2.0
|
|
261
|
+
_HTTP_RETRY_MAX_DELAY_S = 20.0
|
|
252
262
|
_DETAIL_MAX_CHARS = 300
|
|
253
263
|
_SECRET_RE = re.compile(
|
|
254
264
|
r"(AIza[0-9A-Za-z_\-]{20,}|(?i:(?:api[_-]?key|key|token)=)[^&\s\"']+|"
|
|
@@ -271,8 +281,38 @@ class _Progress:
|
|
|
271
281
|
"""Mutable per-session state shared between attempts and the failure handler."""
|
|
272
282
|
|
|
273
283
|
responses: int = 0 # successful generate_content responses received
|
|
284
|
+
last_turn: int | None = None # 0-based index of the turn that last ran
|
|
274
285
|
retried: bool = False
|
|
275
286
|
retry_wait_s: float = 0.0
|
|
287
|
+
# Usage accrued so far, so a session that dies mid-flight still reports it.
|
|
288
|
+
prompt_tokens: int = 0
|
|
289
|
+
cached_tokens: int = 0
|
|
290
|
+
candidates_tokens: int = 0
|
|
291
|
+
thoughts_tokens: int = 0
|
|
292
|
+
tool_use_prompt_tokens: int = 0
|
|
293
|
+
tool_calls: list[str] = field(default_factory=list)
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def input_tokens(self) -> int:
|
|
297
|
+
# Gemini's prompt_token_count already includes the cached portion, so
|
|
298
|
+
# subtract it to avoid billing it at both input and cache-read rates.
|
|
299
|
+
# Tool-result tokens are billed as input.
|
|
300
|
+
return max(self.prompt_tokens - self.cached_tokens, 0) + self.tool_use_prompt_tokens
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def output_tokens(self) -> int:
|
|
304
|
+
# Thinking tokens are a separate additive bucket billed as output.
|
|
305
|
+
return self.candidates_tokens + self.thoughts_tokens
|
|
306
|
+
|
|
307
|
+
def cost_usd(self, model: str) -> float:
|
|
308
|
+
if self.responses == 0:
|
|
309
|
+
return 0.0
|
|
310
|
+
return estimate_cost_usd(
|
|
311
|
+
model=model,
|
|
312
|
+
input_tokens=self.input_tokens,
|
|
313
|
+
output_tokens=self.output_tokens,
|
|
314
|
+
cached_input_tokens=self.cached_tokens,
|
|
315
|
+
)
|
|
276
316
|
|
|
277
317
|
|
|
278
318
|
def _scrub(text: str) -> str:
|
|
@@ -346,6 +386,8 @@ def _retry_decision(exc: BaseException) -> tuple[str, float] | None:
|
|
|
346
386
|
if not _is_transient_error(exc):
|
|
347
387
|
return None
|
|
348
388
|
code, status = _error_code_status(exc)
|
|
389
|
+
if isinstance(exc, genai_errors.ServerError) and code in _HTTP_RETRY_STATUS_CODES:
|
|
390
|
+
return None # the SDK already retried this request; a restart would repeat it
|
|
349
391
|
server_delay = _retry_after_seconds(exc)
|
|
350
392
|
if code == 429 or (status or "").upper() == "RESOURCE_EXHAUSTED":
|
|
351
393
|
if _is_daily_quota(exc, server_delay):
|
|
@@ -767,7 +809,7 @@ async def _run_turns(
|
|
|
767
809
|
repo_root: str,
|
|
768
810
|
timeout_s: float,
|
|
769
811
|
started_at: datetime,
|
|
770
|
-
progress: _Progress
|
|
812
|
+
progress: _Progress,
|
|
771
813
|
) -> SessionResult:
|
|
772
814
|
"""The actual tool-calling loop. Returns ``failure_reason="turn_budget_exhausted"``
|
|
773
815
|
when the loop ran out of turns without a ``finish_review`` call, else
|
|
@@ -834,6 +876,16 @@ async def _run_turns(
|
|
|
834
876
|
label=label,
|
|
835
877
|
)
|
|
836
878
|
|
|
879
|
+
# Retry only model calls, not detached synchronous cache creation.
|
|
880
|
+
request_http_options = types.HttpOptions(
|
|
881
|
+
retry_options=types.HttpRetryOptions(
|
|
882
|
+
attempts=_HTTP_RETRY_ATTEMPTS,
|
|
883
|
+
initial_delay=_HTTP_RETRY_INITIAL_DELAY_S,
|
|
884
|
+
max_delay=_HTTP_RETRY_MAX_DELAY_S,
|
|
885
|
+
http_status_codes=list(_HTTP_RETRY_STATUS_CODES),
|
|
886
|
+
)
|
|
887
|
+
)
|
|
888
|
+
|
|
837
889
|
def _build_config(cache_name: str | None) -> types.GenerateContentConfig:
|
|
838
890
|
# Gemini constraint (confirmed against the live API, not just
|
|
839
891
|
# documentation): a request using `cached_content=` must NOT
|
|
@@ -847,11 +899,14 @@ async def _run_turns(
|
|
|
847
899
|
# `CreateCachedContentConfig.tool_config`), so the cached
|
|
848
900
|
# branch here sends `cached_content` alone.
|
|
849
901
|
if cache_name:
|
|
850
|
-
return types.GenerateContentConfig(
|
|
902
|
+
return types.GenerateContentConfig(
|
|
903
|
+
cached_content=cache_name, http_options=request_http_options
|
|
904
|
+
)
|
|
851
905
|
return types.GenerateContentConfig(
|
|
852
906
|
system_instruction=system_prompt,
|
|
853
907
|
tools=gemini_tools,
|
|
854
908
|
tool_config=_FORCE_TOOL_CONFIG,
|
|
909
|
+
http_options=request_http_options,
|
|
855
910
|
)
|
|
856
911
|
|
|
857
912
|
config = _build_config(cached_content_name)
|
|
@@ -860,19 +915,14 @@ async def _run_turns(
|
|
|
860
915
|
types.Content(role="user", parts=[types.Part.from_text(text=user_message)])
|
|
861
916
|
]
|
|
862
917
|
|
|
863
|
-
tool_calls: list[str] = []
|
|
864
918
|
files_explored: list[str] = []
|
|
865
919
|
exhausted = False
|
|
866
|
-
usage_prompt_total = 0
|
|
867
|
-
usage_candidates_total = 0
|
|
868
|
-
usage_cached_total = 0
|
|
869
|
-
usage_thoughts_total = 0
|
|
870
|
-
usage_tool_use_prompt_total = 0
|
|
871
920
|
|
|
872
921
|
_mid_budget_nudge_turn = _compute_mid_budget_nudge_turn(_MAX_TURNS_GEMINI)
|
|
873
922
|
|
|
874
923
|
with review_tools.review_session(repo_root) as findings_sink:
|
|
875
924
|
for _turn in range(_MAX_TURNS_GEMINI):
|
|
925
|
+
progress.last_turn = _turn
|
|
876
926
|
if (
|
|
877
927
|
_turn == _MAX_TURNS_GEMINI - _NUDGE_TURNS_BEFORE_BUDGET
|
|
878
928
|
and contents[-1].parts is not None
|
|
@@ -928,23 +978,16 @@ async def _run_turns(
|
|
|
928
978
|
model=model, contents=contents, config=config
|
|
929
979
|
)
|
|
930
980
|
|
|
931
|
-
|
|
932
|
-
progress.responses += 1
|
|
981
|
+
progress.responses += 1
|
|
933
982
|
usage = response.usage_metadata
|
|
934
983
|
if usage is not None:
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
# GenerateContentResponseUsageMetadata's own docstring
|
|
943
|
-
# (total_token_count = prompt + candidates +
|
|
944
|
-
# tool_use_prompt + thoughts) -- NOT already folded
|
|
945
|
-
# into prompt_token_count/candidates_token_count.
|
|
946
|
-
usage_thoughts_total += usage.thoughts_token_count or 0
|
|
947
|
-
usage_tool_use_prompt_total += usage.tool_use_prompt_token_count or 0
|
|
984
|
+
# thoughts and tool-use prompt tokens are billable, separate
|
|
985
|
+
# additive buckets (not folded into prompt/candidates counts).
|
|
986
|
+
progress.prompt_tokens += usage.prompt_token_count or 0
|
|
987
|
+
progress.candidates_tokens += usage.candidates_token_count or 0
|
|
988
|
+
progress.cached_tokens += usage.cached_content_token_count or 0
|
|
989
|
+
progress.thoughts_tokens += usage.thoughts_token_count or 0
|
|
990
|
+
progress.tool_use_prompt_tokens += usage.tool_use_prompt_token_count or 0
|
|
948
991
|
|
|
949
992
|
function_calls = response.function_calls or []
|
|
950
993
|
if not function_calls:
|
|
@@ -998,7 +1041,7 @@ async def _run_turns(
|
|
|
998
1041
|
for call in function_calls:
|
|
999
1042
|
name = call.name or ""
|
|
1000
1043
|
args = dict(call.args or {})
|
|
1001
|
-
tool_calls.append(name)
|
|
1044
|
+
progress.tool_calls.append(name)
|
|
1002
1045
|
output, is_error = await asyncio.to_thread(_execute_tool_call, name, args)
|
|
1003
1046
|
if is_error and name != "finish_review":
|
|
1004
1047
|
logger.warning("Tool call failed [%s]: %s", name, output[:200])
|
|
@@ -1030,35 +1073,19 @@ async def _run_turns(
|
|
|
1030
1073
|
result_text = _build_result_text(findings_sink, files_explored)
|
|
1031
1074
|
|
|
1032
1075
|
finished_at = datetime.now(timezone.utc)
|
|
1033
|
-
unique_tool_names = sorted(set(tool_calls))
|
|
1034
|
-
|
|
1035
|
-
# Gemini's prompt_token_count already INCLUDES the cached portion
|
|
1036
|
-
# (unlike Anthropic's fully-separate input/cache-read counters,
|
|
1037
|
-
# which is the convention estimate_cost_usd's cached_input_tokens
|
|
1038
|
-
# documents) -- so the cached amount must be subtracted out of the
|
|
1039
|
-
# uncached input count here, or it would be billed at both the full
|
|
1040
|
-
# input rate AND the cache-read rate. tool_use_prompt tokens are
|
|
1041
|
-
# billed at the input rate (added in); thoughts tokens are billed
|
|
1042
|
-
# at the output rate (added in) -- see the accumulation loop above.
|
|
1043
|
-
input_tokens = max(usage_prompt_total - usage_cached_total, 0) + usage_tool_use_prompt_total
|
|
1044
|
-
output_tokens = usage_candidates_total + usage_thoughts_total
|
|
1045
|
-
cost_usd = estimate_cost_usd(
|
|
1046
|
-
model=model,
|
|
1047
|
-
input_tokens=input_tokens,
|
|
1048
|
-
output_tokens=output_tokens,
|
|
1049
|
-
cached_input_tokens=usage_cached_total,
|
|
1050
|
-
)
|
|
1076
|
+
unique_tool_names = sorted(set(progress.tool_calls))
|
|
1077
|
+
cost_usd = progress.cost_usd(model)
|
|
1051
1078
|
|
|
1052
|
-
tools_str = f" (tools: {', '.join(unique_tool_names)})" if tool_calls else ""
|
|
1079
|
+
tools_str = f" (tools: {', '.join(unique_tool_names)})" if progress.tool_calls else ""
|
|
1053
1080
|
logger.info(
|
|
1054
1081
|
"Gemini agent done [%s]: %d tool calls%s, cost=$%.4f input_tokens=%d cached_tokens=%d output_tokens=%d",
|
|
1055
1082
|
label or "unlabeled",
|
|
1056
|
-
len(tool_calls),
|
|
1083
|
+
len(progress.tool_calls),
|
|
1057
1084
|
tools_str,
|
|
1058
1085
|
cost_usd,
|
|
1059
|
-
input_tokens,
|
|
1060
|
-
|
|
1061
|
-
output_tokens,
|
|
1086
|
+
progress.input_tokens,
|
|
1087
|
+
progress.cached_tokens,
|
|
1088
|
+
progress.output_tokens,
|
|
1062
1089
|
)
|
|
1063
1090
|
|
|
1064
1091
|
return SessionResult(
|
|
@@ -1067,7 +1094,7 @@ async def _run_turns(
|
|
|
1067
1094
|
duration_seconds=(finished_at - started_at).total_seconds(),
|
|
1068
1095
|
started_at=started_at,
|
|
1069
1096
|
finished_at=finished_at,
|
|
1070
|
-
tool_call_count=len(tool_calls),
|
|
1097
|
+
tool_call_count=len(progress.tool_calls),
|
|
1071
1098
|
tool_names=unique_tool_names,
|
|
1072
1099
|
# No Context7 MCP integration in scope for this runner (see module
|
|
1073
1100
|
# docstring) -- always 0, never derived from tool_calls.
|
|
@@ -1207,19 +1234,21 @@ async def run_session_gemini(
|
|
|
1207
1234
|
except (TimeoutError, httpx.TimeoutException) as exc:
|
|
1208
1235
|
finished_at = datetime.now(timezone.utc)
|
|
1209
1236
|
logger.warning(
|
|
1210
|
-
"Gemini session [%s] timed out after %ss (%s)",
|
|
1237
|
+
"Gemini session [%s] timed out after %ss (%s), %d turn(s) and %d tool call(s) done",
|
|
1211
1238
|
label or "unlabeled",
|
|
1212
1239
|
effective_timeout_s,
|
|
1213
1240
|
type(exc).__name__,
|
|
1241
|
+
progress.responses,
|
|
1242
|
+
len(progress.tool_calls),
|
|
1214
1243
|
)
|
|
1215
1244
|
return SessionResult(
|
|
1216
1245
|
result_text="",
|
|
1217
|
-
cost_usd=
|
|
1246
|
+
cost_usd=progress.cost_usd(model),
|
|
1218
1247
|
duration_seconds=(finished_at - started_at).total_seconds(),
|
|
1219
1248
|
started_at=started_at,
|
|
1220
1249
|
finished_at=finished_at,
|
|
1221
|
-
tool_call_count=
|
|
1222
|
-
tool_names=
|
|
1250
|
+
tool_call_count=len(progress.tool_calls),
|
|
1251
|
+
tool_names=sorted(set(progress.tool_calls)),
|
|
1223
1252
|
context7_call_count=0,
|
|
1224
1253
|
model=model,
|
|
1225
1254
|
failure_reason="timeout",
|
|
@@ -1259,6 +1288,8 @@ async def run_session_gemini(
|
|
|
1259
1288
|
# subprocess "worker" the way the Claude Agent SDK path does.
|
|
1260
1289
|
finished_at = datetime.now(timezone.utc)
|
|
1261
1290
|
detail = _failure_detail(exc)
|
|
1291
|
+
if progress.last_turn is not None:
|
|
1292
|
+
detail = f"{detail}, turn {progress.last_turn}"
|
|
1262
1293
|
logger.warning(
|
|
1263
1294
|
"Gemini session [%s] failed after %.1fs (%s, %s%s): %s",
|
|
1264
1295
|
label or "unlabeled",
|
|
@@ -1270,12 +1301,12 @@ async def run_session_gemini(
|
|
|
1270
1301
|
)
|
|
1271
1302
|
return SessionResult(
|
|
1272
1303
|
result_text="",
|
|
1273
|
-
cost_usd=
|
|
1304
|
+
cost_usd=progress.cost_usd(model),
|
|
1274
1305
|
duration_seconds=(finished_at - started_at).total_seconds(),
|
|
1275
1306
|
started_at=started_at,
|
|
1276
1307
|
finished_at=finished_at,
|
|
1277
|
-
tool_call_count=
|
|
1278
|
-
tool_names=
|
|
1308
|
+
tool_call_count=len(progress.tool_calls),
|
|
1309
|
+
tool_names=sorted(set(progress.tool_calls)),
|
|
1279
1310
|
context7_call_count=0,
|
|
1280
1311
|
model=model,
|
|
1281
1312
|
failure_reason="worker_crashed",
|
|
@@ -11,6 +11,8 @@ import re
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
14
16
|
from argus.models import Finding, ReviewResponse, RiskLevel, Severity, Verdict
|
|
15
17
|
from argus.pipeline_models import RawFinding, SystemReviewResult
|
|
16
18
|
|
|
@@ -145,6 +147,31 @@ def collect_reviewed_files(results: list[SystemReviewResult]) -> set[str]:
|
|
|
145
147
|
return reviewed
|
|
146
148
|
|
|
147
149
|
|
|
150
|
+
def _concrete_subclass_names(base: type[BaseException]) -> set[str]:
|
|
151
|
+
names = {base.__name__}
|
|
152
|
+
for sub in base.__subclasses__():
|
|
153
|
+
names |= _concrete_subclass_names(sub)
|
|
154
|
+
return names
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Only canonical provider codes/statuses, HTTP reason phrases and transport
|
|
158
|
+
# exception names may be rendered into reports, never arbitrary exception text
|
|
159
|
+
# or payloads. Transport names come from httpx itself so they cannot drift.
|
|
160
|
+
_TRANSPORT_ERROR_NAMES = sorted(
|
|
161
|
+
_concrete_subclass_names(httpx.TransportError) | {"HTTPStatusError"}
|
|
162
|
+
)
|
|
163
|
+
_CANONICAL_FAILURE_DETAIL_RE = re.compile(
|
|
164
|
+
r"(?:[1-5][0-9]{2}(?: (?:INTERNAL|UNAVAILABLE|RESOURCE_EXHAUSTED|"
|
|
165
|
+
r"DEADLINE_EXCEEDED|UNAUTHENTICATED|PERMISSION_DENIED|INVALID_ARGUMENT|"
|
|
166
|
+
r"NOT_FOUND|FAILED_PRECONDITION|UNKNOWN|CANCELLED|ABORTED|OUT_OF_RANGE|"
|
|
167
|
+
r"UNIMPLEMENTED|DATA_LOSS|"
|
|
168
|
+
# HTTP reason phrase (non-JSON error body), e.g. "Bad Gateway"; <= 40 chars.
|
|
169
|
+
r"(?=[A-Za-z ]{1,40}(?![A-Za-z ]))[A-Z][a-z]+(?: [A-Z][a-z]+)*))?|"
|
|
170
|
+
r"(?:" + "|".join(_TRANSPORT_ERROR_NAMES) + r"|ServerError|ClientError|APIError))"
|
|
171
|
+
r"(?: \(daily quota\))?(?:, turn [0-9]+)?"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
148
175
|
def failed_reviewer_labels(results: list[SystemReviewResult]) -> list[tuple[str, str]]:
|
|
149
176
|
"""Return ``(system_group, failure_reason)`` pairs for reviewers that did
|
|
150
177
|
not complete normally, in the order they appear in ``results``.
|
|
@@ -159,10 +186,13 @@ def failed_reviewer_labels(results: list[SystemReviewResult]) -> list[tuple[str,
|
|
|
159
186
|
"""
|
|
160
187
|
labels: list[tuple[str, str]] = []
|
|
161
188
|
for result in results:
|
|
162
|
-
reason = result.failure_reason
|
|
189
|
+
reason: str | None = result.failure_reason
|
|
163
190
|
if reason is None and result.timed_out:
|
|
164
191
|
reason = "timeout"
|
|
165
192
|
if reason is not None:
|
|
193
|
+
detail = result.failure_detail
|
|
194
|
+
if detail and _CANONICAL_FAILURE_DETAIL_RE.fullmatch(detail):
|
|
195
|
+
reason = f"{reason}: {detail}"
|
|
166
196
|
labels.append((result.system_group, reason))
|
|
167
197
|
return labels
|
|
168
198
|
|
|
@@ -272,15 +302,16 @@ def build_degraded_coverage_findings(
|
|
|
272
302
|
"""
|
|
273
303
|
findings: list[Finding] = []
|
|
274
304
|
for label, reason in failed_labels:
|
|
305
|
+
reason_code, _, detail = reason.partition(": ")
|
|
275
306
|
is_precheck = label.startswith("precheck:")
|
|
276
307
|
subject = "Precheck scanner" if is_precheck else "Reviewer session"
|
|
277
|
-
is_missing =
|
|
308
|
+
is_missing = reason_code == _SCANNER_NOT_INSTALLED_REASON
|
|
278
309
|
# Distinct wording from a crash/timeout: the reviewer was alive and
|
|
279
310
|
# exploring, it just never reached finish_review.
|
|
280
|
-
is_exhausted =
|
|
311
|
+
is_exhausted = reason_code == "turn_budget_exhausted"
|
|
281
312
|
if is_missing:
|
|
282
313
|
reason_desc = "is not installed"
|
|
283
|
-
elif
|
|
314
|
+
elif reason_code == "timeout":
|
|
284
315
|
reason_desc = "timed out"
|
|
285
316
|
elif is_exhausted:
|
|
286
317
|
reason_desc = "ran out of turns before finishing"
|
|
@@ -302,16 +333,22 @@ def build_degraded_coverage_findings(
|
|
|
302
333
|
f"Re-run the review or manually inspect the changes in '{label}' "
|
|
303
334
|
f"to ensure potential issues were not missed due to {reason}."
|
|
304
335
|
)
|
|
336
|
+
if detail:
|
|
337
|
+
# Cause first: API/MCP summaries may clip at 200 chars, and the
|
|
338
|
+
# area name is unbounded model output.
|
|
339
|
+
description = f"{subject} {reason_desc}. Area: '{label}'. Coverage is degraded."
|
|
340
|
+
else:
|
|
341
|
+
description = (
|
|
342
|
+
f"{subject} '{label}' {reason_desc} and produced no findings. "
|
|
343
|
+
"Coverage for this area is degraded."
|
|
344
|
+
)
|
|
305
345
|
findings.append(
|
|
306
346
|
Finding(
|
|
307
347
|
severity=Severity.SUGGESTION,
|
|
308
348
|
category="coverage-gap",
|
|
309
349
|
file=None,
|
|
310
350
|
line=None,
|
|
311
|
-
description=
|
|
312
|
-
f"{subject} '{label}' {reason_desc} and produced no findings. "
|
|
313
|
-
"Coverage for this area is degraded."
|
|
314
|
-
),
|
|
351
|
+
description=description,
|
|
315
352
|
suggestion=suggestion,
|
|
316
353
|
)
|
|
317
354
|
)
|
|
@@ -168,6 +168,14 @@ class SystemReviewResult(BaseModel):
|
|
|
168
168
|
default=False,
|
|
169
169
|
description="True when failure_reason == 'timeout'; kept for back-compat.",
|
|
170
170
|
)
|
|
171
|
+
failure_detail: str | None = Field(
|
|
172
|
+
default=None,
|
|
173
|
+
description=(
|
|
174
|
+
"Short, key-free cause for failure_reason (e.g. '503 UNAVAILABLE, turn 45'), "
|
|
175
|
+
"copied from the runner's SessionResult. Report/degraded-coverage text only -- "
|
|
176
|
+
"not persisted to agent_runs and never parsed for control flow."
|
|
177
|
+
),
|
|
178
|
+
)
|
|
171
179
|
|
|
172
180
|
@model_validator(mode="before")
|
|
173
181
|
@classmethod
|
{argus_code_review-0.3.3 → argus_code_review-0.3.4}/argus/prompts/pr-review-cross-cutting.md
RENAMED
|
@@ -87,12 +87,11 @@ Principle: if an LLM/AI pipeline builds execution, retry, state-tracking, or par
|
|
|
87
87
|
Architecture-mandate violations (an adopted design doc names a framework for a concern and the code uses a different approach for that same concern) are standing conformance debt, not a one-time "was this broken before the PR" question. **Causal-scope-filter override**: this overrides the causal scope-check test used elsewhere in this prompt -- if this diff modifies or extends a pipeline that is already non-compliant, the finding is still in scope. Only skip when the diff does not touch the non-compliant pipeline's code at all.
|
|
88
88
|
|
|
89
89
|
### Model Registry Compliance (BLOCKING)
|
|
90
|
-
-
|
|
91
|
-
- Flag as **BLOCKING** any new hardcoded model
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- Confirm the lines ADDED by this diff contain no em-dash characters (U+2014). Flag every added line that contains one. Scope this to diff-added lines only (prose in pre-existing code or docs is out of scope).
|
|
90
|
+
- Judge this against the repository under review, never against a fixed package name. If the repository has a central model registry (a module that exports model aliases or constants for call sites to import -- commonly something like `ALIAS_MAP`, `MODELS`, or an `llm/models.py`), all LLM model identifiers MUST come from it.
|
|
91
|
+
- Flag as **BLOCKING** any new hardcoded model string the diff introduces in model-SELECTION code -- e.g. a literal `"gpt-5.4-mini"`, `"claude-sonnet-5"`, `"claude-haiku-4-5"`, or deprecated names like `"gpt-4o"`, `"o3"`, `"claude-3-*"`. This targets the call site choosing which model to use; it does not apply to a value passed through a CLI/env override (e.g. a `--*-model` flag) or shown in docs or tests demonstrating that override.
|
|
92
|
+
- Only register an alias that has an actual call site; an unused registry entry is dead weight, not a defect worth blocking on.
|
|
93
|
+
- Short-lived eval or preview pins belong in the registry's own experimental section (with a comment naming a tracking issue and an expected removal date) or behind an inline `# model-registry: allow -- <reason>` comment.
|
|
94
|
+
- If the repository has no such registry, do not demand one. Report hardcoded provider model identifiers as a SUGGESTION only when the repository's own conventions clearly place configuration values outside code.
|
|
96
95
|
|
|
97
96
|
## Output
|
|
98
97
|
|
|
@@ -21,7 +21,7 @@ Review only the changed code for these issues:
|
|
|
21
21
|
- Blocking I/O calls in async functions (e.g. `requests` instead of `httpx`)
|
|
22
22
|
|
|
23
23
|
### Model Registry
|
|
24
|
-
- Hardcoded LLM model strings (e.g. `"claude-sonnet-5"`, `"gpt-5.4-mini"`) —
|
|
24
|
+
- Hardcoded LLM model strings in model-selection code (e.g. `"claude-sonnet-5"`, `"gpt-5.4-mini"`) — when the repository has a central registry of model aliases or constants, call sites must use it. A value passed through a CLI/env override (e.g. a `--*-model` flag), or shown in docs or tests demonstrating that override, is legitimate.
|
|
25
25
|
|
|
26
26
|
## Out of Scope
|
|
27
27
|
Do NOT flag: security deep-dives, performance analysis, test coverage gaps, architectural concerns, code style preferences, or cross-file integration issues. Those require a full Argus review.
|
|
@@ -734,6 +734,18 @@ _SPECIALIST_PROMPT_MAP: dict[SpecialistName, str] = {
|
|
|
734
734
|
# ---------------------------------------------------------------------------
|
|
735
735
|
|
|
736
736
|
|
|
737
|
+
def _apply_session_failure(result: SystemReviewResult, session: SessionResult) -> None:
|
|
738
|
+
"""Copy a session's failure state onto its parsed reviewer result.
|
|
739
|
+
|
|
740
|
+
Goes through the sync helper because a bare attribute assignment would
|
|
741
|
+
bypass ``SystemReviewResult``'s construction-time ``mode="before"`` validator.
|
|
742
|
+
"""
|
|
743
|
+
result.timed_out, result.failure_reason = _sync_timed_out_and_failure_reason(
|
|
744
|
+
session.timed_out, session.failure_reason
|
|
745
|
+
)
|
|
746
|
+
result.failure_detail = session.failure_detail
|
|
747
|
+
|
|
748
|
+
|
|
737
749
|
def _build_agent_run(
|
|
738
750
|
*,
|
|
739
751
|
session: SessionResult,
|
|
@@ -856,15 +868,7 @@ async def run_system_reviewer(
|
|
|
856
868
|
)
|
|
857
869
|
result = _parse_review_result(session.result_text, group.name)
|
|
858
870
|
result.cost_usd = session.cost_usd
|
|
859
|
-
|
|
860
|
-
# assignment) so `result.timed_out` stays correctly synced even before
|
|
861
|
-
# this object is next reconstructed via model_validate() -- a plain
|
|
862
|
-
# `result.failure_reason = session.failure_reason` bypasses
|
|
863
|
-
# SystemReviewResult's `mode="before"` validator entirely, since that
|
|
864
|
-
# validator only fires at construction time.
|
|
865
|
-
result.timed_out, result.failure_reason = _sync_timed_out_and_failure_reason(
|
|
866
|
-
session.timed_out, session.failure_reason
|
|
867
|
-
)
|
|
871
|
+
_apply_session_failure(result, session)
|
|
868
872
|
agent_run = _build_agent_run(
|
|
869
873
|
session=session,
|
|
870
874
|
agent_name=f"system:{group.name}",
|
|
@@ -954,15 +958,7 @@ async def run_specialist_reviewer(
|
|
|
954
958
|
)
|
|
955
959
|
result = _parse_review_result(session.result_text, f"{group.name}::{specialist}")
|
|
956
960
|
result.cost_usd = session.cost_usd
|
|
957
|
-
|
|
958
|
-
# assignment) so `result.timed_out` stays correctly synced even before
|
|
959
|
-
# this object is next reconstructed via model_validate() -- a plain
|
|
960
|
-
# `result.failure_reason = session.failure_reason` bypasses
|
|
961
|
-
# SystemReviewResult's `mode="before"` validator entirely, since that
|
|
962
|
-
# validator only fires at construction time.
|
|
963
|
-
result.timed_out, result.failure_reason = _sync_timed_out_and_failure_reason(
|
|
964
|
-
session.timed_out, session.failure_reason
|
|
965
|
-
)
|
|
961
|
+
_apply_session_failure(result, session)
|
|
966
962
|
agent_run = _build_agent_run(
|
|
967
963
|
session=session,
|
|
968
964
|
agent_name=f"specialist:{group.name}::{specialist}",
|
|
@@ -1043,15 +1039,7 @@ async def run_cross_cutting_reviewer(
|
|
|
1043
1039
|
)
|
|
1044
1040
|
result = _parse_review_result(session.result_text, "cross-cutting")
|
|
1045
1041
|
result.cost_usd = session.cost_usd
|
|
1046
|
-
|
|
1047
|
-
# assignment) so `result.timed_out` stays correctly synced even before
|
|
1048
|
-
# this object is next reconstructed via model_validate() -- a plain
|
|
1049
|
-
# `result.failure_reason = session.failure_reason` bypasses
|
|
1050
|
-
# SystemReviewResult's `mode="before"` validator entirely, since that
|
|
1051
|
-
# validator only fires at construction time.
|
|
1052
|
-
result.timed_out, result.failure_reason = _sync_timed_out_and_failure_reason(
|
|
1053
|
-
session.timed_out, session.failure_reason
|
|
1054
|
-
)
|
|
1042
|
+
_apply_session_failure(result, session)
|
|
1055
1043
|
agent_run = _build_agent_run(
|
|
1056
1044
|
session=session,
|
|
1057
1045
|
agent_name="cross-cutting",
|
|
@@ -1113,15 +1101,7 @@ async def run_tests_and_docs_reviewer(
|
|
|
1113
1101
|
)
|
|
1114
1102
|
result = _parse_review_result(session.result_text, "tests-and-docs")
|
|
1115
1103
|
result.cost_usd = session.cost_usd
|
|
1116
|
-
|
|
1117
|
-
# assignment) so `result.timed_out` stays correctly synced even before
|
|
1118
|
-
# this object is next reconstructed via model_validate() -- a plain
|
|
1119
|
-
# `result.failure_reason = session.failure_reason` bypasses
|
|
1120
|
-
# SystemReviewResult's `mode="before"` validator entirely, since that
|
|
1121
|
-
# validator only fires at construction time.
|
|
1122
|
-
result.timed_out, result.failure_reason = _sync_timed_out_and_failure_reason(
|
|
1123
|
-
session.timed_out, session.failure_reason
|
|
1124
|
-
)
|
|
1104
|
+
_apply_session_failure(result, session)
|
|
1125
1105
|
agent_run = _build_agent_run(
|
|
1126
1106
|
session=session,
|
|
1127
1107
|
agent_name="tests-and-docs",
|