argus-code-review 0.3.0__tar.gz → 0.3.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.
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/CHANGELOG.md +21 -1
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/PKG-INFO +4 -2
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/README.md +2 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/cli.py +19 -2
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/config.py +21 -1
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/dotenv_utils.py +20 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/graph.py +10 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/runners.py +12 -8
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/scope.py +7 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/pyproject.toml +15 -6
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_cli_args.py +23 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_cli_preflight.py +1 -0
- argus_code_review-0.3.2/tests/test_dotenv_utils.py +93 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_runners_turn_budget.py +5 -4
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_scope_gate.py +16 -0
- argus_code_review-0.3.2/tests/test_sdk_bundled_cli_version.py +30 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/.gitignore +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/LICENSE +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/__init__.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/bench.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/bench_default.toml +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/coverage.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/gemini_cache.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/gemini_runner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/github_client.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/helpers.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/llm/models.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/llm/output_models.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/llm/pricing.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/llm/usage.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/models.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/openai_client.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/openai_runner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/pipeline_models.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/__init__.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/actions_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/engine.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/eslint_bundle/.gitignore +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/eslint_bundle/README.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/eslint_bundle/eslint.config.js +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/eslint_bundle/package-lock.json +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/eslint_bundle/package.json +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/js_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/migration_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/rules/README.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/sarif.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/scanner_utils.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/secrets_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/shadow.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/terraform_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/workflow_lint_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/__init__.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-blocking-validator.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-coverage-check.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-cross-cutting.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-feedback-verifier.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-lite.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-planner.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-preflight-router.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-prior-art.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-scope.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-deployment.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-frontend.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-infra.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-llm-patterns.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-observability.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-orchestration.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-security.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-slackbot.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-sql.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-subagent.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-tests-and-docs.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-writer.md +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts_runtime.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/repo_provision.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/review_tools.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/storage/__init__.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/storage/http.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/storage/models.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/storage/precheck.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/storage/resolver.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/storage/session.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/storage/sql.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/storage/sqlite.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/008_add_code_reviews.sql +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/009_add_reviewer_version.sql +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/010_add_review_patterns.sql +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/011_add_review_progress_columns.sql +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/015_create_agent_runs.sql +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/016_add_agent_runs_failure_reason.sql +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/017_add_precheck_rules.sql +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/018_widen_agent_runs_failure_reason.sql +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/__init__.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/conftest.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/golden/review_response.schema.json +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/storage/__init__.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/storage/test_backend_contract.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/storage/test_http.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/storage/test_models.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/storage/test_resolver.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/storage/test_session.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/storage/test_sql.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/storage/test_sqlite_backend.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_actions_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_argus_review_local.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_bench.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_bench_config_guard.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_catchup_gate.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_cli_output_contract.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_cli_post_review.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_cli_prompts.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_config.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_conftest.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_gemini_cache.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_gemini_runner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_github_client_checks_signal.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_github_client_write.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_graph_fetch_diff.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_graph_get_llm_temperature.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_graph_http_guards.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_graph_precheck.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_graph_preflight_image_bump.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_graph_progress.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_graph_storage_resolution.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_graph_timeout_surfacing.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_js_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_llm_models.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_llm_models_override.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_llm_pricing.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_migration_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_models.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_multi_round.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_openai_client.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_openai_runner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_output_models.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_packaging.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_plan_review.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_precheck_engine.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_precheck_engine_integration.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_precheck_sarif.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_precheck_shadow.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_precheck_shadow_integration.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_prompts.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_repo_provision.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_review_patterns_integration.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_review_tools.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_runners_context7.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_runners_context_usage.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_runners_helpers.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_runners_new.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_scanner_utils.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_scope.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_secrets_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_specialist_validation.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_stage_costs.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_storage_precheck.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_structured_output_methods.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_terraform_scanner.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_terraform_scanner_integration.py +0 -0
- {argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_workflow_lint_scanner.py +0 -0
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.2] - 2026-10-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- CLI `--expect-head-sha <sha>`: the review fails before any LLM spend unless the resolved head commit equals this 40-character lowercase SHA, so a service that reviews a specific commit never silently reviews a newer one if the PR head moves (TECH-7789). Also available as `run_review(..., expect_head_sha=...)`.
|
|
15
|
+
- `ARGUS_NO_DOTENV` (truthy): stop all `.env` loading. It disables both `load_dotenv_early` and the `Settings` `env_file`, so a `.env` in the working directory cannot configure the process. For service workers that review untrusted checkouts, alongside the prompt-override and bench-override switches. Enforced on `Settings` itself, so every `Settings()` construction honors it.
|
|
16
|
+
|
|
17
|
+
## [0.3.1] - 2026-10-19
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- The cross-cutting reviewer no longer fails every round with `API Error: 400` (TECH-7686). Argus bundled Claude Code 2.1.139 (through `claude-agent-sdk` 0.1.81), and Anthropic's API rejects `claude-opus-5-5` from Claude Code older than 2.1.280 ("version 2.1.280 or newer is required", `claude_code_version_too_old`). That reviewer is the only one that runs opus through the SDK, so it returned no tokens and no findings. `claude-agent-sdk` is now pinned to 0.2.168, which bundles Claude Code 2.1.301. A new test asserts the bundled CLI is at least 2.1.280.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `claude-agent-sdk` 0.1.81 to 0.2.168. This adds `jsonschema>=4.20.0` as a transitive dependency and raises the `mcp` floor to 1.23.0; semgrep's exact `mcp==1.23.3` pin in the `prechecks` extra still satisfies it, with little headroom (see the comment in `pyproject.toml`).
|
|
26
|
+
- A test that exercised the SDK's private hook-conversion helper now calls `claude_agent_sdk.types._hooks_to_internal_format`, where that logic moved. It is a private API and should be rechecked on each SDK bump.
|
|
27
|
+
|
|
10
28
|
## [0.3.0] - 2026-10-13
|
|
11
29
|
|
|
12
30
|
### Added
|
|
@@ -423,7 +441,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
423
441
|
packaged set.
|
|
424
442
|
- `argus --version`, `argus prompts list`, and `argus prompts export`.
|
|
425
443
|
|
|
426
|
-
[Unreleased]: https://github.com/redesignhealth/argus-review/compare/v0.3.
|
|
444
|
+
[Unreleased]: https://github.com/redesignhealth/argus-review/compare/v0.3.1...HEAD
|
|
445
|
+
[0.3.2]: https://github.com/redesignhealth/argus-review/compare/v0.3.1...v0.3.2
|
|
446
|
+
[0.3.1]: https://github.com/redesignhealth/argus-review/compare/v0.3.0...v0.3.1
|
|
427
447
|
[0.3.0]: https://github.com/redesignhealth/argus-review/compare/v0.2.11...v0.3.0
|
|
428
448
|
[0.2.11]: https://github.com/redesignhealth/argus-review/compare/v0.2.10...v0.2.11
|
|
429
449
|
[0.2.10]: https://github.com/redesignhealth/argus-review/compare/v0.2.9...v0.2.10
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: argus-code-review
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
17
17
|
Requires-Python: <3.14,>=3.12
|
|
18
18
|
Requires-Dist: asyncpg>=0.29.0
|
|
19
|
-
Requires-Dist: claude-agent-sdk==0.
|
|
19
|
+
Requires-Dist: claude-agent-sdk==0.2.168
|
|
20
20
|
Requires-Dist: google-genai<2,>=1.55
|
|
21
21
|
Requires-Dist: httpx>=0.27.0
|
|
22
22
|
Requires-Dist: langchain-anthropic>=1.2.0
|
|
@@ -280,6 +280,8 @@ Argus reads configuration from environment variables, or from a `.env` file
|
|
|
280
280
|
in the working directory (add it to `.gitignore` — never commit it).
|
|
281
281
|
[`.env.example`](.env.example) lists every variable Argus reads, required
|
|
282
282
|
and optional, with comments — copy it to `.env` and fill in what you need.
|
|
283
|
+
Set `ARGUS_NO_DOTENV=1` in the real environment to ignore `.env` files
|
|
284
|
+
entirely (for service workers that run in a directory they do not control).
|
|
283
285
|
|
|
284
286
|
Three keys are required for a first run:
|
|
285
287
|
|
|
@@ -229,6 +229,8 @@ Argus reads configuration from environment variables, or from a `.env` file
|
|
|
229
229
|
in the working directory (add it to `.gitignore` — never commit it).
|
|
230
230
|
[`.env.example`](.env.example) lists every variable Argus reads, required
|
|
231
231
|
and optional, with comments — copy it to `.env` and fill in what you need.
|
|
232
|
+
Set `ARGUS_NO_DOTENV=1` in the real environment to ignore `.env` files
|
|
233
|
+
entirely (for service workers that run in a directory they do not control).
|
|
232
234
|
|
|
233
235
|
Three keys are required for a first run:
|
|
234
236
|
|
|
@@ -70,6 +70,8 @@ import time
|
|
|
70
70
|
from pathlib import Path
|
|
71
71
|
from typing import TYPE_CHECKING, Final
|
|
72
72
|
|
|
73
|
+
from argus.dotenv_utils import TRUTHY_STRINGS
|
|
74
|
+
|
|
73
75
|
if TYPE_CHECKING:
|
|
74
76
|
from argus.config import Settings
|
|
75
77
|
from argus.models import ReviewRequest, ReviewResponse
|
|
@@ -79,6 +81,7 @@ logging.basicConfig(
|
|
|
79
81
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
80
82
|
datefmt="%H:%M:%S",
|
|
81
83
|
)
|
|
84
|
+
|
|
82
85
|
logger = logging.getLogger("argus_review_local")
|
|
83
86
|
|
|
84
87
|
# Hard wall-clock backstop: if the entire review has not completed within this
|
|
@@ -229,6 +232,7 @@ async def run(
|
|
|
229
232
|
request: "ReviewRequest",
|
|
230
233
|
review_override_sha: str | None = None,
|
|
231
234
|
review_override_reason: str | None = None,
|
|
235
|
+
expect_head_sha: str | None = None,
|
|
232
236
|
) -> "ReviewResponse":
|
|
233
237
|
"""Run the review pipeline (storage backend resolved automatically:
|
|
234
238
|
Postgres if ``ARGUS_DB_URL`` is set, HTTP shim if the storage URLs are
|
|
@@ -240,6 +244,7 @@ async def run(
|
|
|
240
244
|
flow_run_id=None,
|
|
241
245
|
review_override_sha=review_override_sha,
|
|
242
246
|
review_override_reason=review_override_reason,
|
|
247
|
+
expect_head_sha=expect_head_sha,
|
|
243
248
|
)
|
|
244
249
|
|
|
245
250
|
|
|
@@ -363,6 +368,16 @@ def _add_review_args(parser: argparse.ArgumentParser) -> None:
|
|
|
363
368
|
default=None,
|
|
364
369
|
help="Reason for --review-override-sha (1-500 characters); recorded in the scope receipt.",
|
|
365
370
|
)
|
|
371
|
+
parser.add_argument(
|
|
372
|
+
"--expect-head-sha",
|
|
373
|
+
dest="expect_head_sha",
|
|
374
|
+
default=None,
|
|
375
|
+
help=(
|
|
376
|
+
"Fail before any review spend unless the resolved head commit equals this "
|
|
377
|
+
"40-character lowercase SHA. For services that review a specific commit and "
|
|
378
|
+
"must not silently review a newer one if the PR head moved."
|
|
379
|
+
),
|
|
380
|
+
)
|
|
366
381
|
parser.add_argument(
|
|
367
382
|
"--no-prompt-overrides",
|
|
368
383
|
action="store_true",
|
|
@@ -483,10 +498,11 @@ def _validate_review_args(parser: argparse.ArgumentParser, args: argparse.Namesp
|
|
|
483
498
|
"""Cross-flag validation that argparse's own declarative options can't express."""
|
|
484
499
|
if args.post and args.sha and not args.pr:
|
|
485
500
|
parser.error("--post requires --pr (a PR to comment on); --sha alone has nowhere to post")
|
|
486
|
-
from argus.scope import validate_override
|
|
501
|
+
from argus.scope import validate_expect_head_sha, validate_override
|
|
487
502
|
|
|
488
503
|
try:
|
|
489
504
|
validate_override(args.review_override_sha, args.review_override_reason)
|
|
505
|
+
validate_expect_head_sha(args.expect_head_sha)
|
|
490
506
|
except ValueError as exc:
|
|
491
507
|
parser.error(str(exc))
|
|
492
508
|
|
|
@@ -676,6 +692,7 @@ def _run_review(parser: argparse.ArgumentParser, args: argparse.Namespace) -> No
|
|
|
676
692
|
request,
|
|
677
693
|
review_override_sha=args.review_override_sha,
|
|
678
694
|
review_override_reason=args.review_override_reason,
|
|
695
|
+
expect_head_sha=args.expect_head_sha,
|
|
679
696
|
)
|
|
680
697
|
)
|
|
681
698
|
except HistoryBackendConnectivityError as exc:
|
|
@@ -720,7 +737,7 @@ def _packaged_prompt_names() -> list[str]:
|
|
|
720
737
|
# argus.config.Settings). Kept in sync here so `argus prompts list`/`export`
|
|
721
738
|
# (deliberately Settings-free, see below) treats the env var identically to
|
|
722
739
|
# `argus review`, which resolves it through Settings.
|
|
723
|
-
_PYDANTIC_TRUTHY_STRINGS =
|
|
740
|
+
_PYDANTIC_TRUTHY_STRINGS = TRUTHY_STRINGS
|
|
724
741
|
|
|
725
742
|
|
|
726
743
|
def _is_truthy_env_value(value: str) -> bool:
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from functools import lru_cache
|
|
11
11
|
|
|
12
12
|
from pydantic import Field
|
|
13
|
-
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
13
|
+
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict
|
|
14
14
|
|
|
15
15
|
# Single source of truth for the reviewer-subprocess wall-clock budget.
|
|
16
16
|
# ``argus.runners._SUBPROCESS_TIMEOUT_S`` (the fallback used when no Settings
|
|
@@ -209,6 +209,26 @@ class Settings(BaseSettings):
|
|
|
209
209
|
env_file_encoding="utf-8",
|
|
210
210
|
)
|
|
211
211
|
|
|
212
|
+
@classmethod
|
|
213
|
+
def settings_customise_sources(
|
|
214
|
+
cls,
|
|
215
|
+
settings_cls: type[BaseSettings],
|
|
216
|
+
init_settings: PydanticBaseSettingsSource,
|
|
217
|
+
env_settings: PydanticBaseSettingsSource,
|
|
218
|
+
dotenv_settings: PydanticBaseSettingsSource,
|
|
219
|
+
file_secret_settings: PydanticBaseSettingsSource,
|
|
220
|
+
) -> tuple[PydanticBaseSettingsSource, ...]:
|
|
221
|
+
"""Drop the ``.env`` source when ``ARGUS_NO_DOTENV`` is truthy.
|
|
222
|
+
|
|
223
|
+
Enforced here, on the class, so every ``Settings()`` construction honors the
|
|
224
|
+
kill switch, not just the cached accessor.
|
|
225
|
+
"""
|
|
226
|
+
from argus.dotenv_utils import dotenv_disabled
|
|
227
|
+
|
|
228
|
+
if dotenv_disabled():
|
|
229
|
+
return (init_settings, env_settings, file_secret_settings)
|
|
230
|
+
return (init_settings, env_settings, dotenv_settings, file_secret_settings)
|
|
231
|
+
|
|
212
232
|
@property
|
|
213
233
|
def db_url(self) -> str | None:
|
|
214
234
|
"""Resolved Postgres URL: ``ARGUS_DB_URL`` wins over ``SUPABASE_DB_URL``."""
|
|
@@ -13,6 +13,22 @@ import os
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
# The strings pydantic treats as True for a bool setting; shared with the CLI so the
|
|
17
|
+
# two truthy checks cannot drift.
|
|
18
|
+
TRUTHY_STRINGS = frozenset({"1", "true", "t", "yes", "y", "on"})
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def dotenv_disabled() -> bool:
|
|
22
|
+
"""True when ``ARGUS_NO_DOTENV`` is truthy.
|
|
23
|
+
|
|
24
|
+
Official/service runs review untrusted checkouts from a working directory
|
|
25
|
+
that may contain a ``.env``; this switch stops every ``.env`` load (both
|
|
26
|
+
:func:`load_dotenv_early` and the pydantic ``Settings`` env_file) so the
|
|
27
|
+
process environment is the only configuration source.
|
|
28
|
+
"""
|
|
29
|
+
return os.environ.get("ARGUS_NO_DOTENV", "").strip().lower() in TRUTHY_STRINGS
|
|
30
|
+
|
|
31
|
+
|
|
16
32
|
def _preferred_env_filename() -> str:
|
|
17
33
|
"""Pick the default env filename based on ENVIRONMENT."""
|
|
18
34
|
env = (os.environ.get("ENVIRONMENT") or "").lower()
|
|
@@ -61,11 +77,15 @@ def load_dotenv_early(
|
|
|
61
77
|
"""Load dotenv file early (before importing settings/clients).
|
|
62
78
|
|
|
63
79
|
This is a no-op when:
|
|
80
|
+
- ``ARGUS_NO_DOTENV`` is truthy, or
|
|
64
81
|
- running in AWS Lambda (secrets are expected to come from SSM/env), or
|
|
65
82
|
- running in pytest and `skip_in_test=True`, or
|
|
66
83
|
- python-dotenv isn't installed, or
|
|
67
84
|
- no env file can be found.
|
|
68
85
|
"""
|
|
86
|
+
if dotenv_disabled():
|
|
87
|
+
return None
|
|
88
|
+
|
|
69
89
|
# Never load local dotenv inside Lambda.
|
|
70
90
|
if os.environ.get("AWS_LAMBDA_FUNCTION_NAME"):
|
|
71
91
|
return None
|
|
@@ -85,6 +85,7 @@ from argus.models import (
|
|
|
85
85
|
from argus.scope import (
|
|
86
86
|
SCOPE_PROMPT,
|
|
87
87
|
SHA40,
|
|
88
|
+
validate_expect_head_sha,
|
|
88
89
|
ScopeAssessment,
|
|
89
90
|
ScopeUnverified,
|
|
90
91
|
build_scope_input,
|
|
@@ -3605,6 +3606,7 @@ async def run_review(
|
|
|
3605
3606
|
*,
|
|
3606
3607
|
review_override_sha: str | None = None,
|
|
3607
3608
|
review_override_reason: str | None = None,
|
|
3609
|
+
expect_head_sha: str | None = None,
|
|
3608
3610
|
) -> ReviewResponse:
|
|
3609
3611
|
"""Execute the v3 review pipeline via LangGraph StateGraph.
|
|
3610
3612
|
|
|
@@ -3620,6 +3622,7 @@ async def run_review(
|
|
|
3620
3622
|
import uuid
|
|
3621
3623
|
|
|
3622
3624
|
override = validate_override(review_override_sha, review_override_reason)
|
|
3625
|
+
validate_expect_head_sha(expect_head_sha)
|
|
3623
3626
|
|
|
3624
3627
|
# Fail fast on a bad ARGUS_DB_URL / ARGUS_HISTORY_DB_PATH, before any of
|
|
3625
3628
|
# the pipeline's LLM cost runs. Both history-backend implementations
|
|
@@ -3690,6 +3693,13 @@ async def run_review(
|
|
|
3690
3693
|
else:
|
|
3691
3694
|
head_sha_for_worktree = None
|
|
3692
3695
|
|
|
3696
|
+
if expect_head_sha is not None and expect_head_sha != head_sha_for_worktree:
|
|
3697
|
+
raise ValueError(
|
|
3698
|
+
f"expect_head_sha {expect_head_sha[:12]} does not match the resolved head "
|
|
3699
|
+
f"{(head_sha_for_worktree or 'none')[:12]}; the PR head moved, refusing to review "
|
|
3700
|
+
"a different commit"
|
|
3701
|
+
)
|
|
3702
|
+
|
|
3693
3703
|
if override and override[0] != head_sha_for_worktree:
|
|
3694
3704
|
raise ValueError(
|
|
3695
3705
|
f"--review-override-sha {override[0][:12]} does not match the resolved head "
|
|
@@ -219,10 +219,12 @@ _TURN_BUDGET_SYSTEM_PROMPT_LINE_CLAUDE = (
|
|
|
219
219
|
# Tool-call budget warning thresholds and truthful messages for Claude-path
|
|
220
220
|
# sessions.
|
|
221
221
|
#
|
|
222
|
-
# In claude-agent-sdk 0.
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
#
|
|
222
|
+
# In claude-agent-sdk 0.2.168, the hook events in the HookEvent literal that
|
|
223
|
+
# ClaudeAgentOptions.hooks is typed to accept, and that can inject feedback via
|
|
224
|
+
# hookSpecificOutput.additionalContext, are PostToolUse and PostToolUseFailure.
|
|
225
|
+
# PostToolBatch would give exact per-turn nudges and has a typed hookSpecificOutput
|
|
226
|
+
# (PostToolBatchHookSpecificOutput), but it is not in the HookEvent literal, so it
|
|
227
|
+
# is not used here.
|
|
226
228
|
# We attach to PostToolUse and PostToolUseFailure to track combined tool
|
|
227
229
|
# invocations and inject non-blocking convergence/stop nudges.
|
|
228
230
|
#
|
|
@@ -284,10 +286,12 @@ def _make_tool_budget_nudge_hooks(
|
|
|
284
286
|
"""Create PostToolUse and PostToolUseFailure hooks that inject non-blocking
|
|
285
287
|
nudge warnings into Claude-path sessions when tool-call counts cross budget thresholds.
|
|
286
288
|
|
|
287
|
-
In claude-agent-sdk 0.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
In claude-agent-sdk 0.2.168, the hook events in the HookEvent literal that
|
|
290
|
+
ClaudeAgentOptions.hooks is typed to accept, and that can inject feedback via
|
|
291
|
+
hookSpecificOutput.additionalContext, are PostToolUse and PostToolUseFailure.
|
|
292
|
+
PostToolBatch would give exact per-turn nudges and has a typed hookSpecificOutput
|
|
293
|
+
(PostToolBatchHookSpecificOutput), but it is not in the HookEvent literal, so it
|
|
294
|
+
is not used here.
|
|
291
295
|
|
|
292
296
|
One callback is shared by both events, closing over combined tool-call count
|
|
293
297
|
and one-shot flags. Subagent tool calls (bearing agent_id) are skipped.
|
|
@@ -50,6 +50,13 @@ class ScopeAssessment(BaseModel):
|
|
|
50
50
|
rationale: str
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def validate_expect_head_sha(sha: str | None) -> str | None:
|
|
54
|
+
"""Return ``sha`` unchanged if it is ``None`` or a 40-char lowercase hex SHA, else raise."""
|
|
55
|
+
if sha is not None and not SHA40.fullmatch(sha):
|
|
56
|
+
raise ValueError("expect_head_sha must be a 40-character lowercase hex SHA")
|
|
57
|
+
return sha
|
|
58
|
+
|
|
59
|
+
|
|
53
60
|
def validate_override(sha: str | None, reason: str | None) -> tuple[str, str] | None:
|
|
54
61
|
"""Return ``(sha, reason)`` for a valid human override pair, ``None`` if neither is set."""
|
|
55
62
|
if sha is None and reason is None:
|
|
@@ -20,10 +20,15 @@ classifiers = [
|
|
|
20
20
|
]
|
|
21
21
|
dependencies = [
|
|
22
22
|
# Claude Agent SDK for orchestrator + subagent dispatch.
|
|
23
|
-
# Pinned
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
23
|
+
# Pinned exactly: the SDK bundles the Claude Code CLI, and Anthropic's
|
|
24
|
+
# API rejects claude-opus-5-5 from Claude Code older than 2.1.280
|
|
25
|
+
# (claude_code_version_too_old, TECH-7686). 0.2.158 is the first SDK
|
|
26
|
+
# release that bundles CLI 2.1.280 (0.1.81 bundled 2.1.139), but its CLI
|
|
27
|
+
# prints an unrecognized_model warning for claude-sonnet-5-5. 0.2.168
|
|
28
|
+
# bundles CLI 2.1.301, on which both sonnet-5-5 and opus-5-5 were verified
|
|
29
|
+
# clean. Do not lower the pin below a release bundling >= 2.1.280
|
|
30
|
+
# (enforced by tests/test_sdk_bundled_cli_version.py).
|
|
31
|
+
"claude-agent-sdk==0.2.168",
|
|
27
32
|
# LangGraph for pipeline orchestration
|
|
28
33
|
"langgraph>=1.0.10,<2",
|
|
29
34
|
"langgraph-checkpoint-postgres>=2.0.0",
|
|
@@ -152,8 +157,12 @@ prechecks = [
|
|
|
152
157
|
# transitives jsonschema/importlib-metadata/pywin32 -- to that version for
|
|
153
158
|
# EVERY install of this package, not just prechecks users. This is provably
|
|
154
159
|
# safe, not a functional regression: claude-agent-sdk (core, always
|
|
155
|
-
# installed)
|
|
156
|
-
# nothing here depends on an mcp feature
|
|
160
|
+
# installed) requires mcp>=1.23.0,<3 (and jsonschema>=4.20.0) as of 0.2.168,
|
|
161
|
+
# and 1.23.3 satisfies that floor, so nothing here depends on an mcp feature
|
|
162
|
+
# added after 1.23.3. Headroom is thin: semgrep's exact mcp==1.23.3 pin now
|
|
163
|
+
# sits just above the SDK's 1.23.0 floor, so a future SDK bump that raises the
|
|
164
|
+
# mcp floor above 1.23.3 will conflict with the prechecks extra and need the
|
|
165
|
+
# semgrep pin revisited at that point. Investigated
|
|
157
166
|
# uv's `conflicts` mechanism (declaring dev/prechecks mutually exclusive) as
|
|
158
167
|
# a way to fork the resolution and keep the newer mcp for non-prechecks
|
|
159
168
|
# installs -- it has no effect here, because 1.23.3 already satisfies both
|
|
@@ -343,3 +343,26 @@ class TestPromptsSubcommand:
|
|
|
343
343
|
def test_prompts_export_force_defaults_off(self) -> None:
|
|
344
344
|
args = _parse(["prompts", "export", "/tmp/out"])
|
|
345
345
|
assert args.force is False
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class TestExpectHeadShaFlag:
|
|
349
|
+
def test_defaults_to_none(self) -> None:
|
|
350
|
+
assert _parse(["review", "owner/repo", "--pr", "1"]).expect_head_sha is None
|
|
351
|
+
|
|
352
|
+
def test_flag_parses(self) -> None:
|
|
353
|
+
sha = "a" * 40
|
|
354
|
+
args = _parse(["review", "owner/repo", "--pr", "1", "--expect-head-sha", sha])
|
|
355
|
+
assert args.expect_head_sha == sha
|
|
356
|
+
|
|
357
|
+
def test_malformed_sha_is_a_parser_error(self) -> None:
|
|
358
|
+
parser = _build_parser()
|
|
359
|
+
for bad in ("a" * 39, "A" * 40, "g" * 40, ""):
|
|
360
|
+
args = _parse(["review", "owner/repo", "--pr", "1", "--expect-head-sha", bad])
|
|
361
|
+
with pytest.raises(SystemExit) as exc:
|
|
362
|
+
_validate_review_args(parser, args)
|
|
363
|
+
assert exc.value.code == 2
|
|
364
|
+
|
|
365
|
+
def test_valid_sha_passes_validation(self) -> None:
|
|
366
|
+
parser = _build_parser()
|
|
367
|
+
args = _parse(["review", "owner/repo", "--pr", "1", "--expect-head-sha", "a" * 40])
|
|
368
|
+
_validate_review_args(parser, args)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Tests for ``ARGUS_NO_DOTENV``: the kill switch for every ``.env`` load."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterator
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from argus.config import Settings, clear_cache, get_settings
|
|
11
|
+
from argus.dotenv_utils import dotenv_disabled, load_dotenv_early
|
|
12
|
+
|
|
13
|
+
_SENTINEL = "ARGUS_TEST_DOTENV_SENTINEL"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize("value", ["1", "true", "TRUE", " yes ", "On", "t", "y"])
|
|
17
|
+
def test_dotenv_disabled_truthy_values(value: str, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
18
|
+
monkeypatch.setenv("ARGUS_NO_DOTENV", value)
|
|
19
|
+
assert dotenv_disabled() is True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.parametrize("value", ["", "0", "false", "no", "off", "enabled", "2"])
|
|
23
|
+
def test_dotenv_disabled_falsy_values(value: str, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
24
|
+
monkeypatch.setenv("ARGUS_NO_DOTENV", value)
|
|
25
|
+
assert dotenv_disabled() is False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_dotenv_disabled_unset(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
29
|
+
monkeypatch.delenv("ARGUS_NO_DOTENV", raising=False)
|
|
30
|
+
assert dotenv_disabled() is False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_load_dotenv_early_is_a_noop_when_disabled(
|
|
34
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
35
|
+
) -> None:
|
|
36
|
+
(tmp_path / ".env").write_text(f"{_SENTINEL}=loaded\n")
|
|
37
|
+
monkeypatch.delenv(_SENTINEL, raising=False)
|
|
38
|
+
monkeypatch.setenv("ARGUS_NO_DOTENV", "1")
|
|
39
|
+
assert load_dotenv_early(start=tmp_path, env_filename=".env") is None
|
|
40
|
+
import os
|
|
41
|
+
|
|
42
|
+
assert _SENTINEL not in os.environ
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_load_dotenv_early_still_loads_when_not_disabled(
|
|
46
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
47
|
+
) -> None:
|
|
48
|
+
import os
|
|
49
|
+
|
|
50
|
+
(tmp_path / ".env").write_text(f"{_SENTINEL}=loaded\n")
|
|
51
|
+
monkeypatch.delenv(_SENTINEL, raising=False)
|
|
52
|
+
monkeypatch.delenv("ARGUS_NO_DOTENV", raising=False)
|
|
53
|
+
try:
|
|
54
|
+
assert load_dotenv_early(start=tmp_path, env_filename=".env") is not None
|
|
55
|
+
assert os.environ[_SENTINEL] == "loaded"
|
|
56
|
+
finally:
|
|
57
|
+
os.environ.pop(_SENTINEL, None)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestSettingsHonorKillSwitch:
|
|
61
|
+
@pytest.fixture(autouse=True)
|
|
62
|
+
def _isolate(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
|
|
63
|
+
(tmp_path / ".env").write_text("ARGUS_GEMINI_CACHE_TTL=7\n")
|
|
64
|
+
monkeypatch.chdir(tmp_path)
|
|
65
|
+
monkeypatch.delenv("ARGUS_GEMINI_CACHE_TTL", raising=False)
|
|
66
|
+
clear_cache()
|
|
67
|
+
yield
|
|
68
|
+
clear_cache()
|
|
69
|
+
|
|
70
|
+
def test_cached_settings_read_dotenv_by_default(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
71
|
+
monkeypatch.delenv("ARGUS_NO_DOTENV", raising=False)
|
|
72
|
+
assert get_settings().ARGUS_GEMINI_CACHE_TTL == 7
|
|
73
|
+
|
|
74
|
+
def test_cached_settings_ignore_dotenv_when_disabled(
|
|
75
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
76
|
+
) -> None:
|
|
77
|
+
monkeypatch.setenv("ARGUS_NO_DOTENV", "true")
|
|
78
|
+
default = Settings.model_fields["ARGUS_GEMINI_CACHE_TTL"].default
|
|
79
|
+
assert get_settings().ARGUS_GEMINI_CACHE_TTL == default
|
|
80
|
+
|
|
81
|
+
def test_direct_settings_construction_also_ignores_dotenv(
|
|
82
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
83
|
+
) -> None:
|
|
84
|
+
monkeypatch.setenv("ARGUS_NO_DOTENV", "1")
|
|
85
|
+
default = Settings.model_fields["ARGUS_GEMINI_CACHE_TTL"].default
|
|
86
|
+
assert Settings().ARGUS_GEMINI_CACHE_TTL == default
|
|
87
|
+
|
|
88
|
+
def test_real_environment_still_wins_when_disabled(
|
|
89
|
+
self, monkeypatch: pytest.MonkeyPatch
|
|
90
|
+
) -> None:
|
|
91
|
+
monkeypatch.setenv("ARGUS_NO_DOTENV", "1")
|
|
92
|
+
monkeypatch.setenv("ARGUS_GEMINI_CACHE_TTL", "42")
|
|
93
|
+
assert Settings().ARGUS_GEMINI_CACHE_TTL == 42
|
|
@@ -39,7 +39,6 @@ import pytest
|
|
|
39
39
|
from claude_agent_sdk import (
|
|
40
40
|
AssistantMessage,
|
|
41
41
|
ClaudeAgentOptions,
|
|
42
|
-
ClaudeSDKClient,
|
|
43
42
|
HookMatcher,
|
|
44
43
|
ResultMessage,
|
|
45
44
|
ToolResultBlock,
|
|
@@ -47,9 +46,10 @@ from claude_agent_sdk import (
|
|
|
47
46
|
UserMessage,
|
|
48
47
|
)
|
|
49
48
|
|
|
50
|
-
# Pinned internal import: the exact SDK pin (0.
|
|
49
|
+
# Pinned internal import: the exact SDK pin (0.2.168) is load-bearing;
|
|
51
50
|
# this test and internal import must be revisited whenever claude-agent-sdk is bumped.
|
|
52
51
|
from claude_agent_sdk._internal.query import _convert_hook_output_for_cli
|
|
52
|
+
from claude_agent_sdk.types import _hooks_to_internal_format
|
|
53
53
|
from claude_agent_sdk.types import (
|
|
54
54
|
HookContext,
|
|
55
55
|
HookEvent,
|
|
@@ -692,8 +692,9 @@ class TestClaudeAgentSdkHookContract:
|
|
|
692
692
|
assert isinstance(hooks["PostToolUse"][0], HookMatcher)
|
|
693
693
|
assert isinstance(hooks["PostToolUseFailure"][0], HookMatcher)
|
|
694
694
|
|
|
695
|
-
|
|
696
|
-
|
|
695
|
+
# claude-agent-sdk 0.2.x moved this from a ClaudeSDKClient method to a
|
|
696
|
+
# module-level helper in claude_agent_sdk.types (TECH-7686).
|
|
697
|
+
internal = _hooks_to_internal_format(hooks)
|
|
697
698
|
assert "PostToolUse" in internal
|
|
698
699
|
assert "PostToolUseFailure" in internal
|
|
699
700
|
assert len(internal["PostToolUse"]) == 1
|
|
@@ -476,6 +476,22 @@ class TestRunReviewHaltAndReceipt:
|
|
|
476
476
|
with pytest.raises(ValueError, match="stale"):
|
|
477
477
|
await _run({}, review_override_sha="d" * 40, review_override_reason="r")
|
|
478
478
|
|
|
479
|
+
async def test_expect_head_sha_mismatch_fails_before_any_graph_run(self) -> None:
|
|
480
|
+
with pytest.raises(ValueError, match="head moved"):
|
|
481
|
+
await _run({}, expect_head_sha="d" * 40)
|
|
482
|
+
|
|
483
|
+
async def test_expect_head_sha_must_be_a_full_lowercase_sha(self) -> None:
|
|
484
|
+
for bad in (HEAD[:12], HEAD.upper(), "z" * 40):
|
|
485
|
+
with pytest.raises(ValueError, match="40-character"):
|
|
486
|
+
await _run({}, expect_head_sha=bad)
|
|
487
|
+
|
|
488
|
+
async def test_expect_head_sha_match_runs_the_review(self) -> None:
|
|
489
|
+
resp, _, _ = await _run(
|
|
490
|
+
{"response": _approve(), "head_sha": HEAD, "findings": [], "scope": _scope_dict()},
|
|
491
|
+
expect_head_sha=HEAD,
|
|
492
|
+
)
|
|
493
|
+
assert resp.scope is not None and resp.scope.head_sha == HEAD
|
|
494
|
+
|
|
479
495
|
async def test_override_pair_is_required(self) -> None:
|
|
480
496
|
with pytest.raises(ValueError, match="together"):
|
|
481
497
|
await _run({}, review_override_sha=HEAD)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Guard: the SDK-bundled Claude Code CLI must be new enough for claude-opus-5-5.
|
|
2
|
+
|
|
3
|
+
Anthropic's API rejects claude-opus-5-5 from Claude Code older than 2.1.280
|
|
4
|
+
(HTTP 400, ``claude_code_version_too_old``). The Claude Agent SDK bundles the
|
|
5
|
+
CLI, so the SDK pin determines it. Pinned SDK 0.2.168 bundles 2.1.301; 2.1.280 is
|
|
6
|
+
the minimum for opus-5-5. See TECH-7686.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
import subprocess
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import claude_agent_sdk
|
|
14
|
+
|
|
15
|
+
MIN_BUNDLED_CLI = (2, 1, 280)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_bundled_claude_cli_supports_opus_5_5() -> None:
|
|
19
|
+
cli = Path(claude_agent_sdk.__file__).parent / "_bundled" / "claude"
|
|
20
|
+
assert cli.exists(), f"bundled Claude Code CLI missing at {cli}"
|
|
21
|
+
out = subprocess.run(
|
|
22
|
+
[str(cli), "--version"], capture_output=True, text=True, timeout=60, check=True
|
|
23
|
+
).stdout
|
|
24
|
+
match = re.search(r"(\d+)\.(\d+)\.(\d+)", out)
|
|
25
|
+
assert match, f"could not parse CLI version from {out!r}"
|
|
26
|
+
version = tuple(int(g) for g in match.groups())
|
|
27
|
+
assert version >= MIN_BUNDLED_CLI, (
|
|
28
|
+
f"bundled Claude Code {version} is older than {MIN_BUNDLED_CLI}; "
|
|
29
|
+
"claude-opus-5-5 requests will be rejected (TECH-7686)"
|
|
30
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/eslint_bundle/eslint.config.js
RENAMED
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/eslint_bundle/package-lock.json
RENAMED
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/precheck/eslint_bundle/package.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-blocking-validator.md
RENAMED
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-coverage-check.md
RENAMED
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-cross-cutting.md
RENAMED
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-feedback-verifier.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-preflight-router.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-deployment.md
RENAMED
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-frontend.md
RENAMED
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-infra.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-security.md
RENAMED
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-slackbot.md
RENAMED
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-specialist-sql.md
RENAMED
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/argus/prompts/pr-review-tests-and-docs.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/011_add_review_progress_columns.sql
RENAMED
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/016_add_agent_runs_failure_reason.sql
RENAMED
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/schema/018_widen_agent_runs_failure_reason.sql
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/golden/review_response.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_github_client_checks_signal.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_graph_preflight_image_bump.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_precheck_engine_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_precheck_shadow_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_review_patterns_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{argus_code_review-0.3.0 → argus_code_review-0.3.2}/tests/test_terraform_scanner_integration.py
RENAMED
|
File without changes
|
|
File without changes
|