agentlint 2.2.0__tar.gz → 2.3.0__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.
- {agentlint-2.2.0 → agentlint-2.3.0}/CHANGELOG.md +48 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/PKG-INFO +16 -6
- {agentlint-2.2.0 → agentlint-2.3.0}/README.md +14 -2
- {agentlint-2.2.0 → agentlint-2.3.0}/agentlint.yml.example +5 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/pyproject.toml +2 -5
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/__init__.py +1 -1
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/cli.py +36 -6
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/engine.py +9 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_unnecessary_async.py +23 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_python_pre.py +43 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_ci.py +20 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_cli.py +10 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_engine.py +11 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/uv.lock +4 -8
- {agentlint-2.2.0 → agentlint-2.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/.github/workflows/ci.yml +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/.github/workflows/publish.yml +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/.gitignore +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/.python-version +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/CODE_OF_CONDUCT.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/CONTRIBUTING.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/LICENSE +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/SECURITY.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/codecov.yml +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/README.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/adapters.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/architecture.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/configuration.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/custom-rules.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/mcp.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/migration-v2.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-claude.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-codex.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-continue.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-cursor.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-gemini.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-generic.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-grok.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-kimi.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-mcp.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-openai.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/docs/subagent-safety.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/plugin/.claude-plugin/plugin.json +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/plugin/agents/doctor.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/plugin/agents/fix.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/plugin/agents/security-audit.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/plugin/bin/resolve-and-run.sh +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/plugin/commands/lint-config.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/plugin/commands/lint-status.md +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/plugin/hooks/hooks.json +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/plugin/mcp.json +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/__main__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/_utils.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/base.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/claude.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/codex.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/continue_dev.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/cursor.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/gemini.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/generic.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/grok.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/kimi.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/mcp.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/openai_agents.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/client.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/feeds.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/policy.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/queue.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/sync.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agents_md.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/circuit_breaker.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/config.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/core/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/core/models.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/detector.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/filters.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/base.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/claude_hooks.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/cursor_hooks.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/gemini_hooks.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/plain_json.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/mcp_server.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/models.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/_dangerous_patterns.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/bash_rate_limiter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cloud_infra_mutation.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cloud_paid_resource_creation.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cloud_resource_deletion.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cross_account_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/destructive_confirmation_gate.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/docker_volume_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/dry_run_required.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/network_firewall_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/operation_journal.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/package_manager_in_chroot.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/production_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/remote_boot_partition_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/remote_chroot_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/ssh_destructive_command_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/subagent_safety_briefing.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/subagent_transcript_audit.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/system_scheduler_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/_helpers.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_form_labels.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_heading_hierarchy.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_image_alt.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_interactive_elements.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/mobile_responsive_patterns.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/mobile_touch_targets.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/style_focus_visible.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/style_no_arbitrary_values.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_bare_except.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_dangerous_migration.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_sql_injection.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_unsafe_shell.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_wildcard_import.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/commit_message_format.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/naming_conventions.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/no_dead_imports.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/no_error_handling_removal.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/no_file_creation_sprawl.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/no_large_diff.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/self_review_prompt.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/react/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/react/react_empty_state.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/react/react_lazy_loading.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/react/react_query_loading_state.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/env_credential_reference.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_bash_file_write.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_blocked_domain_fetch.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_compromised_action.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_leaked_secret_pattern.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_malicious_url_fetch.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_network_exfil.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/_helpers.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_open_graph.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_page_metadata.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_semantic_html.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_structured_data.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/cicd_pipeline_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/cli_integration.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/dependency_hygiene.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/drift_detector.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/file_scope.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/git_checkpoint.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/max_file_size.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_compromised_dependency.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_debug_artifacts.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_destructive_commands.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_env_commit.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_force_push.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_push_to_main.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_secrets.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_skip_hooks.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_test_weakening.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_todo_left.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_vulnerable_import.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_vulnerable_version_install.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/package_publish_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/test_with_changes.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/token_budget.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/token_burn_against_team_budget.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/py.typed +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/recorder.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/reporter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/session.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/setup.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/template.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/utils/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/utils/bash.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/utils/git.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/utils/paths.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/__init__.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/security/test_no_blocked_domain_fetch.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/security/test_no_compromised_action.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/security/test_no_leaked_secret_pattern.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/security/test_no_malicious_url_fetch.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_autopilot_integration.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_autopilot_pack_loads.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_autopilot_post.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_autopilot_pre.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_cicd_pipeline_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_cloud_infra_mutation.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_cloud_paid_resource_creation.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_cloud_resource_deletion.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_docker_volume_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_edge_cases.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_env_credential_reference.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_frontend.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_git_checkpoint.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_network_firewall_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_no_compromised_dependency.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_no_vulnerable_import.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_no_vulnerable_version_install.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_package_manager_in_chroot.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_package_publish_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_python_post.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_quality_post.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_quality_pre.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_quality_stop.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_react.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_remote_boot_partition_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_remote_chroot_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_security_pre.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_seo.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_ssh_destructive_command_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_subagent_safety_briefing.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_subagent_transcript_audit.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_system_scheduler_guard.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_token_budget.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_token_burn_against_team_budget.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_universal_post.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_universal_pre.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_universal_stop.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_adapter_utils.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_agentchute_client.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_agentchute_feeds.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_agentchute_policy_queue_sync.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_agents_md.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_bash_utils.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_circuit_breaker.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_cli_integration.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_codex_adapter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_config.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_continue_adapter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_cursor_adapter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_custom_rules.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_detector.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_file_scope.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_filters.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_gemini_adapter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_generic_adapter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_git.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_git_utils_checkpoint.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_grok_adapter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_integration.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_kimi_adapter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_main_module.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_mcp_adapter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_mcp_server.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_models.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_openai_adapter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_packs_loader.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_paths_utils.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_quality_new_rules.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_recorder.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_reporter.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_rule_inventory.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_session.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_setup.py +0 -0
- {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_template.py +0 -0
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.3.0 (2026-05-14) — CI Signal Polish
|
|
4
|
+
|
|
5
|
+
This release applies real-world Codex and AgentLint dogfooding feedback. It
|
|
6
|
+
keeps CI adoption safe by preserving INFO-as-non-blocking behavior while
|
|
7
|
+
reducing repeated output and avoiding framework-convention false positives.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `no-unnecessary-async` is now FastAPI-aware and skips route handlers such as
|
|
12
|
+
`@router.get(...)` and `@app.post(...)` by default.
|
|
13
|
+
- `agentlint ci --format text` now groups repeated findings by file, rule, and
|
|
14
|
+
severity while leaving JSON output unchanged.
|
|
15
|
+
- Added per-rule `ignore_paths` as an accepted-pattern alias for documented
|
|
16
|
+
local conventions.
|
|
17
|
+
- Documented `ignore_fastapi_routes`, per-rule `ignore_paths`, and `reason`
|
|
18
|
+
examples in the sample config and README.
|
|
19
|
+
|
|
20
|
+
### Compatibility
|
|
21
|
+
|
|
22
|
+
- INFO findings still do not fail CI by default.
|
|
23
|
+
- Teams that want strict async-route checks can set
|
|
24
|
+
`rules.no-unnecessary-async.ignore_fastapi_routes: false`.
|
|
25
|
+
- Existing `allow_paths` behavior is unchanged.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## v2.2.1 (2026-05-10) — Install Experience Polish
|
|
30
|
+
|
|
31
|
+
This release removes the last extra-step surprise from AgentChute setup.
|
|
32
|
+
`pip install agentlint` now includes the HTTP client dependency needed for
|
|
33
|
+
team sync, so users do not need to know about an optional extra before their
|
|
34
|
+
first event can reach AgentChute.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Added `agentlint --version` for quick install verification.
|
|
39
|
+
- Made `requests>=2.31` a base dependency so AgentChute policy, feed, and event
|
|
40
|
+
sync work from the default install.
|
|
41
|
+
- Removed the obsolete `agentchute` optional extra.
|
|
42
|
+
|
|
43
|
+
### Compatibility
|
|
44
|
+
|
|
45
|
+
- Existing local rules, MCP usage through `agentlint[mcp]`, and all platform
|
|
46
|
+
adapters remain unchanged.
|
|
47
|
+
- `pip install agentlint[agentchute]` is no longer supported.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
3
51
|
## v2.2.0 (2026-05-10) — Public Docs Cleanup
|
|
4
52
|
|
|
5
53
|
This release cleans the public repository after the AgentChute-ready 2.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentlint
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Real-time quality guardrails for AI coding agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/mauhpr/agentlint
|
|
6
6
|
Project-URL: Repository, https://github.com/mauhpr/agentlint
|
|
@@ -24,14 +24,12 @@ Classifier: Typing :: Typed
|
|
|
24
24
|
Requires-Python: >=3.11
|
|
25
25
|
Requires-Dist: click>=8.0
|
|
26
26
|
Requires-Dist: pyyaml>=6.0
|
|
27
|
-
|
|
28
|
-
Requires-Dist: requests>=2.31; extra == 'agentchute'
|
|
27
|
+
Requires-Dist: requests>=2.31
|
|
29
28
|
Provides-Extra: dev
|
|
30
29
|
Requires-Dist: fastmcp>=3.0; extra == 'dev'
|
|
31
30
|
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
32
31
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
33
32
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
-
Requires-Dist: requests>=2.31; extra == 'dev'
|
|
35
33
|
Provides-Extra: mcp
|
|
36
34
|
Requires-Dist: fastmcp>=3.0; extra == 'mcp'
|
|
37
35
|
Description-Content-Type: text/markdown
|
|
@@ -64,7 +62,7 @@ We don't have a mature answer to that yet. Nobody does. The **autopilot pack** i
|
|
|
64
62
|
|
|
65
63
|
AgentLint ships with 76 rules across 8 packs and normalizes tool events across supported AI coding agents. The 23 **universal** rules and 7 **quality** rules work with any tech stack; 4 additional packs auto-activate based on your project files; the **security** pack is opt-in; and the **autopilot** pack is opt-in and experimental.
|
|
66
64
|
|
|
67
|
-
**v2.
|
|
65
|
+
**v2.3.0 highlights:** Codex dogfooding polish with FastAPI-aware `no-unnecessary-async`, grouped text CI findings, and repo-local accepted patterns through per-rule `ignore_paths`. Previous: Install experience polish, `agentlint --version`, AgentChute sync support from the default `pip install agentlint` package, public documentation cleanup, AgentChute-ready team sync, hybrid cloud feeds, privacy-safe event queueing, multi-platform adapter architecture, unified `AgentEvent` taxonomy, `NormalizedTool` cross-platform mappings, session summary dashboard, MCP server, global config defaults, warning suppression, auto-suppress, `diff_only` mode, and `auto-fix` mode.
|
|
68
66
|
|
|
69
67
|
| Rule | Severity | What it does |
|
|
70
68
|
|------|----------|-------------|
|
|
@@ -118,7 +116,7 @@ AgentLint ships with 76 rules across 8 packs and normalizes tool events across s
|
|
|
118
116
|
| `no-unsafe-shell` | ERROR | Blocks unsafe shell execution via subprocess or os module |
|
|
119
117
|
| `no-dangerous-migration` | WARNING | Warns on risky Alembic migration operations |
|
|
120
118
|
| `no-wildcard-import` | WARNING | Prevents `from module import *` |
|
|
121
|
-
| `no-unnecessary-async` | INFO | Flags async functions that never use `await
|
|
119
|
+
| `no-unnecessary-async` | INFO | Flags async functions that never use `await`; skips FastAPI route handlers by default |
|
|
122
120
|
| `no-sql-injection` | ERROR | Blocks SQL via string interpolation (f-strings, `.format()`) |
|
|
123
121
|
|
|
124
122
|
</details>
|
|
@@ -196,6 +194,16 @@ rules:
|
|
|
196
194
|
|
|
197
195
|
</details>
|
|
198
196
|
|
|
197
|
+
Per-rule accepted patterns can be documented locally:
|
|
198
|
+
|
|
199
|
+
```yaml
|
|
200
|
+
rules:
|
|
201
|
+
no-unnecessary-async:
|
|
202
|
+
ignore_paths:
|
|
203
|
+
- "app/api/*_routes.py"
|
|
204
|
+
reason: "FastAPI route consistency"
|
|
205
|
+
```
|
|
206
|
+
|
|
199
207
|
<details>
|
|
200
208
|
<summary><strong>Autopilot pack</strong> (18 rules) — ⚠️ experimental, opt-in</summary>
|
|
201
209
|
|
|
@@ -363,6 +371,8 @@ rules:
|
|
|
363
371
|
AgentLint runs locally by default. No event data leaves your machine unless you enable AgentChute with a license key and an explicit opt-in:
|
|
364
372
|
|
|
365
373
|
```bash
|
|
374
|
+
pip install agentlint
|
|
375
|
+
agentlint --version
|
|
366
376
|
agentlint init --team-key=ac_team_...
|
|
367
377
|
```
|
|
368
378
|
|
|
@@ -26,7 +26,7 @@ We don't have a mature answer to that yet. Nobody does. The **autopilot pack** i
|
|
|
26
26
|
|
|
27
27
|
AgentLint ships with 76 rules across 8 packs and normalizes tool events across supported AI coding agents. The 23 **universal** rules and 7 **quality** rules work with any tech stack; 4 additional packs auto-activate based on your project files; the **security** pack is opt-in; and the **autopilot** pack is opt-in and experimental.
|
|
28
28
|
|
|
29
|
-
**v2.
|
|
29
|
+
**v2.3.0 highlights:** Codex dogfooding polish with FastAPI-aware `no-unnecessary-async`, grouped text CI findings, and repo-local accepted patterns through per-rule `ignore_paths`. Previous: Install experience polish, `agentlint --version`, AgentChute sync support from the default `pip install agentlint` package, public documentation cleanup, AgentChute-ready team sync, hybrid cloud feeds, privacy-safe event queueing, multi-platform adapter architecture, unified `AgentEvent` taxonomy, `NormalizedTool` cross-platform mappings, session summary dashboard, MCP server, global config defaults, warning suppression, auto-suppress, `diff_only` mode, and `auto-fix` mode.
|
|
30
30
|
|
|
31
31
|
| Rule | Severity | What it does |
|
|
32
32
|
|------|----------|-------------|
|
|
@@ -80,7 +80,7 @@ AgentLint ships with 76 rules across 8 packs and normalizes tool events across s
|
|
|
80
80
|
| `no-unsafe-shell` | ERROR | Blocks unsafe shell execution via subprocess or os module |
|
|
81
81
|
| `no-dangerous-migration` | WARNING | Warns on risky Alembic migration operations |
|
|
82
82
|
| `no-wildcard-import` | WARNING | Prevents `from module import *` |
|
|
83
|
-
| `no-unnecessary-async` | INFO | Flags async functions that never use `await
|
|
83
|
+
| `no-unnecessary-async` | INFO | Flags async functions that never use `await`; skips FastAPI route handlers by default |
|
|
84
84
|
| `no-sql-injection` | ERROR | Blocks SQL via string interpolation (f-strings, `.format()`) |
|
|
85
85
|
|
|
86
86
|
</details>
|
|
@@ -158,6 +158,16 @@ rules:
|
|
|
158
158
|
|
|
159
159
|
</details>
|
|
160
160
|
|
|
161
|
+
Per-rule accepted patterns can be documented locally:
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
rules:
|
|
165
|
+
no-unnecessary-async:
|
|
166
|
+
ignore_paths:
|
|
167
|
+
- "app/api/*_routes.py"
|
|
168
|
+
reason: "FastAPI route consistency"
|
|
169
|
+
```
|
|
170
|
+
|
|
161
171
|
<details>
|
|
162
172
|
<summary><strong>Autopilot pack</strong> (18 rules) — ⚠️ experimental, opt-in</summary>
|
|
163
173
|
|
|
@@ -325,6 +335,8 @@ rules:
|
|
|
325
335
|
AgentLint runs locally by default. No event data leaves your machine unless you enable AgentChute with a license key and an explicit opt-in:
|
|
326
336
|
|
|
327
337
|
```bash
|
|
338
|
+
pip install agentlint
|
|
339
|
+
agentlint --version
|
|
328
340
|
agentlint init --team-key=ac_team_...
|
|
329
341
|
```
|
|
330
342
|
|
|
@@ -51,6 +51,11 @@ rules:
|
|
|
51
51
|
# ── PostToolUse (warnings after the action) ─────────────
|
|
52
52
|
# max-file-size:
|
|
53
53
|
# limit: 500 # Warn if file exceeds this many lines
|
|
54
|
+
# no-unnecessary-async:
|
|
55
|
+
# ignore_fastapi_routes: true # Default: don't flag FastAPI route handlers
|
|
56
|
+
# ignore_paths:
|
|
57
|
+
# - "app/api/*_routes.py"
|
|
58
|
+
# reason: "FastAPI route consistency"
|
|
54
59
|
# drift-detector:
|
|
55
60
|
# threshold: 10 # Warn after this many edits without tests
|
|
56
61
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentlint"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0"
|
|
4
4
|
description = "Real-time quality guardrails for AI coding agents"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -24,6 +24,7 @@ classifiers = [
|
|
|
24
24
|
dependencies = [
|
|
25
25
|
"click>=8.0",
|
|
26
26
|
"pyyaml>=6.0",
|
|
27
|
+
"requests>=2.31",
|
|
27
28
|
]
|
|
28
29
|
|
|
29
30
|
[project.optional-dependencies]
|
|
@@ -32,14 +33,10 @@ dev = [
|
|
|
32
33
|
"pytest-cov>=5.0",
|
|
33
34
|
"pytest-asyncio>=0.24",
|
|
34
35
|
"fastmcp>=3.0",
|
|
35
|
-
"requests>=2.31", # for the agentchute/ HTTP client tests
|
|
36
36
|
]
|
|
37
37
|
mcp = [
|
|
38
38
|
"fastmcp>=3.0",
|
|
39
39
|
]
|
|
40
|
-
agentchute = [
|
|
41
|
-
"requests>=2.31", # AgentChute policy/feed/event HTTP client (opt-in feature)
|
|
42
|
-
]
|
|
43
40
|
|
|
44
41
|
[project.urls]
|
|
45
42
|
Homepage = "https://github.com/mauhpr/agentlint"
|
|
@@ -11,6 +11,7 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
import click
|
|
13
13
|
|
|
14
|
+
from agentlint import __version__
|
|
14
15
|
from agentlint.agents_md import find_agents_md, generate_config, map_to_config, merge_with_existing, parse_agents_md
|
|
15
16
|
from agentlint.config import load_config
|
|
16
17
|
from agentlint.detector import detect_stack
|
|
@@ -123,6 +124,7 @@ def _configure_logging() -> None:
|
|
|
123
124
|
|
|
124
125
|
|
|
125
126
|
@click.group()
|
|
127
|
+
@click.version_option(__version__, prog_name="agentlint", message="agentlint %(version)s")
|
|
126
128
|
def main():
|
|
127
129
|
"""AgentLint - Real-time quality guardrails for AI coding agents."""
|
|
128
130
|
_configure_logging()
|
|
@@ -486,12 +488,27 @@ def ci(diff: str | None, project_dir: str | None, output_format: str):
|
|
|
486
488
|
if not all_violations:
|
|
487
489
|
click.echo(f"Clean — {len(changed_files)} files scanned, {total_evaluated} rules evaluated.")
|
|
488
490
|
else:
|
|
489
|
-
for
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
click.echo(f"
|
|
491
|
+
for group in _group_ci_violations(all_violations):
|
|
492
|
+
if len(group) == 1:
|
|
493
|
+
v = group[0]
|
|
494
|
+
sev = v.severity.value.upper()
|
|
495
|
+
loc = f"{v.file_path}" if v.file_path else "?"
|
|
496
|
+
click.echo(f"{loc} [{v.rule_id}] {sev} {v.message}")
|
|
497
|
+
if v.suggestion:
|
|
498
|
+
click.echo(f" → {v.suggestion}")
|
|
499
|
+
click.echo()
|
|
500
|
+
continue
|
|
501
|
+
|
|
502
|
+
first = group[0]
|
|
503
|
+
sev = first.severity.value.upper()
|
|
504
|
+
loc = f"{first.file_path}" if first.file_path else "?"
|
|
505
|
+
click.echo(f"{loc} [{first.rule_id}] {sev} {len(group)} findings")
|
|
506
|
+
for v in group:
|
|
507
|
+
line = f"line {v.line}: " if v.line is not None else ""
|
|
508
|
+
click.echo(f" - {line}{v.message}")
|
|
509
|
+
suggestions = sorted({v.suggestion for v in group if v.suggestion})
|
|
510
|
+
if len(suggestions) == 1:
|
|
511
|
+
click.echo(f" → {suggestions[0]}")
|
|
495
512
|
click.echo()
|
|
496
513
|
click.echo(
|
|
497
514
|
f"{len(all_violations)} violation(s) "
|
|
@@ -502,6 +519,19 @@ def ci(diff: str | None, project_dir: str | None, output_format: str):
|
|
|
502
519
|
sys.exit(1 if has_errors else 0)
|
|
503
520
|
|
|
504
521
|
|
|
522
|
+
def _group_ci_violations(violations: list) -> list[list]:
|
|
523
|
+
"""Group repeated text CI findings by file, rule, and severity."""
|
|
524
|
+
grouped: list[list] = []
|
|
525
|
+
index: dict[tuple, list] = {}
|
|
526
|
+
for violation in violations:
|
|
527
|
+
key = (violation.file_path, violation.rule_id, violation.severity.value)
|
|
528
|
+
if key not in index:
|
|
529
|
+
index[key] = []
|
|
530
|
+
grouped.append(index[key])
|
|
531
|
+
index[key].append(violation)
|
|
532
|
+
return grouped
|
|
533
|
+
|
|
534
|
+
|
|
505
535
|
@main.command()
|
|
506
536
|
@click.option("--project-dir", default=None, help="Project directory")
|
|
507
537
|
@click.option(
|
|
@@ -59,6 +59,15 @@ class Engine:
|
|
|
59
59
|
if any(fnmatch(context.file_path, p) or fnmatch(basename, p) for p in rule_allow):
|
|
60
60
|
continue
|
|
61
61
|
|
|
62
|
+
# Per-rule ignore_paths — accepted-pattern alias for allow_paths.
|
|
63
|
+
# Useful when teams want to document why a rule does not apply
|
|
64
|
+
# to a local convention without disabling the rule globally.
|
|
65
|
+
rule_ignore = get_rule_setting(context.config, rule.id, "ignore_paths", [])
|
|
66
|
+
if isinstance(rule_ignore, list) and rule_ignore:
|
|
67
|
+
basename = os.path.basename(context.file_path)
|
|
68
|
+
if any(fnmatch(context.file_path, p) or fnmatch(basename, p) for p in rule_ignore):
|
|
69
|
+
continue
|
|
70
|
+
|
|
62
71
|
result.rules_evaluated += 1
|
|
63
72
|
|
|
64
73
|
try:
|
|
@@ -9,6 +9,7 @@ _WRITE_TOOLS = {"Write", "Edit"}
|
|
|
9
9
|
|
|
10
10
|
_ASYNC_DEF_RE = re.compile(r"^(\s*)async\s+def\s+(\w+)\s*\(", re.MULTILINE)
|
|
11
11
|
_AWAIT_RE = re.compile(r"\bawait\b")
|
|
12
|
+
_FASTAPI_ROUTE_METHODS = {"get", "post", "put", "patch", "delete", "options", "head", "trace", "websocket"}
|
|
12
13
|
_SKIP_DECORATORS = {"property", "override", "abstractmethod"}
|
|
13
14
|
_SKIP_BODIES = {"pass", "...", "raise NotImplementedError"}
|
|
14
15
|
|
|
@@ -45,6 +46,7 @@ class NoUnnecessaryAsync(Rule):
|
|
|
45
46
|
|
|
46
47
|
ignore_decorators = set(context.config.get("ignore_decorators", []))
|
|
47
48
|
all_skip_decorators = _SKIP_DECORATORS | ignore_decorators
|
|
49
|
+
ignore_fastapi_routes = context.config.get("ignore_fastapi_routes", True)
|
|
48
50
|
|
|
49
51
|
violations: list[Violation] = []
|
|
50
52
|
lines = content.splitlines()
|
|
@@ -57,9 +59,11 @@ class NoUnnecessaryAsync(Rule):
|
|
|
57
59
|
# Check decorators above
|
|
58
60
|
decorator_line = func_line - 1
|
|
59
61
|
skip = False
|
|
62
|
+
decorators: list[str] = []
|
|
60
63
|
while decorator_line >= 0:
|
|
61
64
|
dline = lines[decorator_line].strip()
|
|
62
65
|
if dline.startswith("@"):
|
|
66
|
+
decorators.append(dline)
|
|
63
67
|
dec_name = dline[1:].split("(")[0].split(".")[-1]
|
|
64
68
|
if dec_name in all_skip_decorators:
|
|
65
69
|
skip = True
|
|
@@ -72,6 +76,8 @@ class NoUnnecessaryAsync(Rule):
|
|
|
72
76
|
|
|
73
77
|
if skip:
|
|
74
78
|
continue
|
|
79
|
+
if ignore_fastapi_routes and _has_fastapi_route_decorator(decorators):
|
|
80
|
+
continue
|
|
75
81
|
|
|
76
82
|
# Extract body lines
|
|
77
83
|
body_lines = []
|
|
@@ -104,3 +110,20 @@ class NoUnnecessaryAsync(Rule):
|
|
|
104
110
|
)
|
|
105
111
|
|
|
106
112
|
return violations
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _has_fastapi_route_decorator(decorators: list[str]) -> bool:
|
|
116
|
+
"""Return True for common FastAPI route decorators.
|
|
117
|
+
|
|
118
|
+
Async FastAPI handlers are a framework convention even when a handler
|
|
119
|
+
currently has no direct await; dependencies, middleware, or later
|
|
120
|
+
instrumentation often remain async.
|
|
121
|
+
"""
|
|
122
|
+
for decorator in decorators:
|
|
123
|
+
target = decorator[1:].split("(", 1)[0]
|
|
124
|
+
parts = target.split(".")
|
|
125
|
+
if len(parts) >= 2 and parts[-1] in _FASTAPI_ROUTE_METHODS:
|
|
126
|
+
receiver = parts[-2].lower()
|
|
127
|
+
if receiver == "app" or receiver.endswith("router"):
|
|
128
|
+
return True
|
|
129
|
+
return False
|
|
@@ -8,6 +8,7 @@ from agentlint.packs.python.no_sql_injection import NoSqlInjection
|
|
|
8
8
|
from agentlint.packs.python.no_bare_except import NoBareExcept
|
|
9
9
|
from agentlint.packs.python.no_unsafe_shell import NoUnsafeShell
|
|
10
10
|
from agentlint.packs.python.no_dangerous_migration import NoDangerousMigration
|
|
11
|
+
from agentlint.packs.python.no_unnecessary_async import NoUnnecessaryAsync
|
|
11
12
|
from agentlint.packs.python.no_wildcard_import import NoWildcardImport
|
|
12
13
|
|
|
13
14
|
|
|
@@ -612,6 +613,48 @@ class TestNoWildcardImport:
|
|
|
612
613
|
assert len(violations) == 0
|
|
613
614
|
|
|
614
615
|
|
|
616
|
+
# ---------------------------------------------------------------------------
|
|
617
|
+
# NoUnnecessaryAsync
|
|
618
|
+
# ---------------------------------------------------------------------------
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
class TestNoUnnecessaryAsync:
|
|
622
|
+
rule = NoUnnecessaryAsync()
|
|
623
|
+
|
|
624
|
+
def test_detects_plain_async_without_await(self):
|
|
625
|
+
ctx = _ctx("Write", {
|
|
626
|
+
"file_path": "app/service.py",
|
|
627
|
+
"content": "async def compute():\n return 1\n",
|
|
628
|
+
})
|
|
629
|
+
violations = self.rule.evaluate(ctx)
|
|
630
|
+
assert len(violations) == 1
|
|
631
|
+
assert violations[0].severity == Severity.INFO
|
|
632
|
+
|
|
633
|
+
def test_skips_fastapi_route_by_default(self):
|
|
634
|
+
ctx = _ctx("Write", {
|
|
635
|
+
"file_path": "app/api/routes.py",
|
|
636
|
+
"content": "@router.get('/health')\nasync def health():\n return {'ok': True}\n",
|
|
637
|
+
})
|
|
638
|
+
violations = self.rule.evaluate(ctx)
|
|
639
|
+
assert violations == []
|
|
640
|
+
|
|
641
|
+
def test_skips_fastapi_app_route_by_default(self):
|
|
642
|
+
ctx = _ctx("Write", {
|
|
643
|
+
"file_path": "app/main.py",
|
|
644
|
+
"content": "@app.post('/items')\nasync def create_item():\n return {'ok': True}\n",
|
|
645
|
+
})
|
|
646
|
+
violations = self.rule.evaluate(ctx)
|
|
647
|
+
assert violations == []
|
|
648
|
+
|
|
649
|
+
def test_fastapi_route_can_be_checked_when_configured(self):
|
|
650
|
+
ctx = _ctx("Write", {
|
|
651
|
+
"file_path": "app/api/routes.py",
|
|
652
|
+
"content": "@router.get('/health')\nasync def health():\n return {'ok': True}\n",
|
|
653
|
+
}, config={"ignore_fastapi_routes": False})
|
|
654
|
+
violations = self.rule.evaluate(ctx)
|
|
655
|
+
assert len(violations) == 1
|
|
656
|
+
|
|
657
|
+
|
|
615
658
|
# ---------------------------------------------------------------------------
|
|
616
659
|
# Pack loader
|
|
617
660
|
# ---------------------------------------------------------------------------
|
|
@@ -8,6 +8,7 @@ import sys
|
|
|
8
8
|
from click.testing import CliRunner
|
|
9
9
|
|
|
10
10
|
from agentlint.cli import main
|
|
11
|
+
from agentlint.models import Severity, Violation
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class TestCiCommand:
|
|
@@ -142,6 +143,25 @@ class TestCiCommand:
|
|
|
142
143
|
assert "Clean" in result.output or "No changed" in result.output
|
|
143
144
|
|
|
144
145
|
|
|
146
|
+
class TestCiTextGrouping:
|
|
147
|
+
def test_groups_repeated_rule_findings(self):
|
|
148
|
+
from agentlint.cli import _group_ci_violations
|
|
149
|
+
|
|
150
|
+
violations = [
|
|
151
|
+
Violation("no-unnecessary-async", "async def one() has no await expression", Severity.INFO, "app.py", 1),
|
|
152
|
+
Violation("no-unnecessary-async", "async def two() has no await expression", Severity.INFO, "app.py", 4),
|
|
153
|
+
Violation("max-file-size", "File has 600 lines", Severity.WARNING, "app.py"),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
groups = _group_ci_violations(violations)
|
|
157
|
+
assert len(groups) == 2
|
|
158
|
+
assert [v.message for v in groups[0]] == [
|
|
159
|
+
"async def one() has no await expression",
|
|
160
|
+
"async def two() has no await expression",
|
|
161
|
+
]
|
|
162
|
+
assert groups[1][0].rule_id == "max-file-size"
|
|
163
|
+
|
|
164
|
+
|
|
145
165
|
class TestGetDiffFiles:
|
|
146
166
|
def test_diff_range_returns_files(self, tmp_path):
|
|
147
167
|
from agentlint.utils.git import get_diff_files
|
|
@@ -10,6 +10,16 @@ from click.testing import CliRunner
|
|
|
10
10
|
from agentlint.cli import _resolve_adapter, main
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
class TestMainCommand:
|
|
14
|
+
def test_version_option(self) -> None:
|
|
15
|
+
"""--version should print the installed AgentLint version."""
|
|
16
|
+
runner = CliRunner()
|
|
17
|
+
result = runner.invoke(main, ["--version"])
|
|
18
|
+
|
|
19
|
+
assert result.exit_code == 0
|
|
20
|
+
assert result.output.strip() == "agentlint 2.3.0"
|
|
21
|
+
|
|
22
|
+
|
|
13
23
|
class TestCheckCommand:
|
|
14
24
|
def test_check_with_empty_input(self, tmp_path) -> None:
|
|
15
25
|
"""Empty JSON input should produce no violations and exit 0."""
|
|
@@ -592,6 +592,17 @@ class TestPerRuleAllowPaths:
|
|
|
592
592
|
result = engine.evaluate(ctx)
|
|
593
593
|
assert len(result.violations) == 0
|
|
594
594
|
|
|
595
|
+
def test_rule_ignore_paths_alias_skips_specific_rule(self) -> None:
|
|
596
|
+
ctx = self._make_context("/project/app/api/users_routes.py", config={
|
|
597
|
+
"always-warn": {
|
|
598
|
+
"ignore_paths": ["**/api/*_routes.py"],
|
|
599
|
+
"reason": "FastAPI route consistency",
|
|
600
|
+
},
|
|
601
|
+
})
|
|
602
|
+
engine = self._make_engine()
|
|
603
|
+
result = engine.evaluate(ctx)
|
|
604
|
+
assert len(result.violations) == 0
|
|
605
|
+
|
|
595
606
|
def test_other_rules_still_fire(self) -> None:
|
|
596
607
|
"""Rule A has allow_paths, rule B does not — B should still fire."""
|
|
597
608
|
ctx = self._make_context("/project/generated/out.py", config={
|
|
@@ -4,23 +4,20 @@ requires-python = ">=3.11"
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "agentlint"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.3.0"
|
|
8
8
|
source = { editable = "." }
|
|
9
9
|
dependencies = [
|
|
10
10
|
{ name = "click" },
|
|
11
11
|
{ name = "pyyaml" },
|
|
12
|
+
{ name = "requests" },
|
|
12
13
|
]
|
|
13
14
|
|
|
14
15
|
[package.optional-dependencies]
|
|
15
|
-
agentchute = [
|
|
16
|
-
{ name = "requests" },
|
|
17
|
-
]
|
|
18
16
|
dev = [
|
|
19
17
|
{ name = "fastmcp" },
|
|
20
18
|
{ name = "pytest" },
|
|
21
19
|
{ name = "pytest-asyncio" },
|
|
22
20
|
{ name = "pytest-cov" },
|
|
23
|
-
{ name = "requests" },
|
|
24
21
|
]
|
|
25
22
|
mcp = [
|
|
26
23
|
{ name = "fastmcp" },
|
|
@@ -35,10 +32,9 @@ requires-dist = [
|
|
|
35
32
|
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24" },
|
|
36
33
|
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=5.0" },
|
|
37
34
|
{ name = "pyyaml", specifier = ">=6.0" },
|
|
38
|
-
{ name = "requests",
|
|
39
|
-
{ name = "requests", marker = "extra == 'dev'", specifier = ">=2.31" },
|
|
35
|
+
{ name = "requests", specifier = ">=2.31" },
|
|
40
36
|
]
|
|
41
|
-
provides-extras = ["dev", "mcp"
|
|
37
|
+
provides-extras = ["dev", "mcp"]
|
|
42
38
|
|
|
43
39
|
[[package]]
|
|
44
40
|
name = "aiofile"
|
|
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
|
|
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
|