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.
Files changed (265) hide show
  1. {agentlint-2.2.0 → agentlint-2.3.0}/CHANGELOG.md +48 -0
  2. {agentlint-2.2.0 → agentlint-2.3.0}/PKG-INFO +16 -6
  3. {agentlint-2.2.0 → agentlint-2.3.0}/README.md +14 -2
  4. {agentlint-2.2.0 → agentlint-2.3.0}/agentlint.yml.example +5 -0
  5. {agentlint-2.2.0 → agentlint-2.3.0}/pyproject.toml +2 -5
  6. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/__init__.py +1 -1
  7. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/cli.py +36 -6
  8. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/engine.py +9 -0
  9. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_unnecessary_async.py +23 -0
  10. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_python_pre.py +43 -0
  11. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_ci.py +20 -0
  12. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_cli.py +10 -0
  13. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_engine.py +11 -0
  14. {agentlint-2.2.0 → agentlint-2.3.0}/uv.lock +4 -8
  15. {agentlint-2.2.0 → agentlint-2.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  16. {agentlint-2.2.0 → agentlint-2.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  17. {agentlint-2.2.0 → agentlint-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  18. {agentlint-2.2.0 → agentlint-2.3.0}/.github/workflows/ci.yml +0 -0
  19. {agentlint-2.2.0 → agentlint-2.3.0}/.github/workflows/publish.yml +0 -0
  20. {agentlint-2.2.0 → agentlint-2.3.0}/.gitignore +0 -0
  21. {agentlint-2.2.0 → agentlint-2.3.0}/.python-version +0 -0
  22. {agentlint-2.2.0 → agentlint-2.3.0}/CODE_OF_CONDUCT.md +0 -0
  23. {agentlint-2.2.0 → agentlint-2.3.0}/CONTRIBUTING.md +0 -0
  24. {agentlint-2.2.0 → agentlint-2.3.0}/LICENSE +0 -0
  25. {agentlint-2.2.0 → agentlint-2.3.0}/SECURITY.md +0 -0
  26. {agentlint-2.2.0 → agentlint-2.3.0}/codecov.yml +0 -0
  27. {agentlint-2.2.0 → agentlint-2.3.0}/docs/README.md +0 -0
  28. {agentlint-2.2.0 → agentlint-2.3.0}/docs/adapters.md +0 -0
  29. {agentlint-2.2.0 → agentlint-2.3.0}/docs/architecture.md +0 -0
  30. {agentlint-2.2.0 → agentlint-2.3.0}/docs/configuration.md +0 -0
  31. {agentlint-2.2.0 → agentlint-2.3.0}/docs/custom-rules.md +0 -0
  32. {agentlint-2.2.0 → agentlint-2.3.0}/docs/mcp.md +0 -0
  33. {agentlint-2.2.0 → agentlint-2.3.0}/docs/migration-v2.md +0 -0
  34. {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-claude.md +0 -0
  35. {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-codex.md +0 -0
  36. {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-continue.md +0 -0
  37. {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-cursor.md +0 -0
  38. {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-gemini.md +0 -0
  39. {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-generic.md +0 -0
  40. {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-grok.md +0 -0
  41. {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-kimi.md +0 -0
  42. {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-mcp.md +0 -0
  43. {agentlint-2.2.0 → agentlint-2.3.0}/docs/setup-openai.md +0 -0
  44. {agentlint-2.2.0 → agentlint-2.3.0}/docs/subagent-safety.md +0 -0
  45. {agentlint-2.2.0 → agentlint-2.3.0}/plugin/.claude-plugin/plugin.json +0 -0
  46. {agentlint-2.2.0 → agentlint-2.3.0}/plugin/agents/doctor.md +0 -0
  47. {agentlint-2.2.0 → agentlint-2.3.0}/plugin/agents/fix.md +0 -0
  48. {agentlint-2.2.0 → agentlint-2.3.0}/plugin/agents/security-audit.md +0 -0
  49. {agentlint-2.2.0 → agentlint-2.3.0}/plugin/bin/resolve-and-run.sh +0 -0
  50. {agentlint-2.2.0 → agentlint-2.3.0}/plugin/commands/lint-config.md +0 -0
  51. {agentlint-2.2.0 → agentlint-2.3.0}/plugin/commands/lint-status.md +0 -0
  52. {agentlint-2.2.0 → agentlint-2.3.0}/plugin/hooks/hooks.json +0 -0
  53. {agentlint-2.2.0 → agentlint-2.3.0}/plugin/mcp.json +0 -0
  54. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/__main__.py +0 -0
  55. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/__init__.py +0 -0
  56. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/_utils.py +0 -0
  57. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/base.py +0 -0
  58. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/claude.py +0 -0
  59. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/codex.py +0 -0
  60. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/continue_dev.py +0 -0
  61. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/cursor.py +0 -0
  62. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/gemini.py +0 -0
  63. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/generic.py +0 -0
  64. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/grok.py +0 -0
  65. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/kimi.py +0 -0
  66. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/mcp.py +0 -0
  67. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/adapters/openai_agents.py +0 -0
  68. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/__init__.py +0 -0
  69. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/client.py +0 -0
  70. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/feeds.py +0 -0
  71. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/policy.py +0 -0
  72. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/queue.py +0 -0
  73. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agentchute/sync.py +0 -0
  74. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/agents_md.py +0 -0
  75. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/circuit_breaker.py +0 -0
  76. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/config.py +0 -0
  77. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/core/__init__.py +0 -0
  78. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/core/models.py +0 -0
  79. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/detector.py +0 -0
  80. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/filters.py +0 -0
  81. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/__init__.py +0 -0
  82. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/base.py +0 -0
  83. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/claude_hooks.py +0 -0
  84. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/cursor_hooks.py +0 -0
  85. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/gemini_hooks.py +0 -0
  86. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/formats/plain_json.py +0 -0
  87. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/mcp_server.py +0 -0
  88. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/models.py +0 -0
  89. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/__init__.py +0 -0
  90. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/__init__.py +0 -0
  91. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/_dangerous_patterns.py +0 -0
  92. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/bash_rate_limiter.py +0 -0
  93. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cloud_infra_mutation.py +0 -0
  94. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cloud_paid_resource_creation.py +0 -0
  95. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cloud_resource_deletion.py +0 -0
  96. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cross_account_guard.py +0 -0
  97. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/destructive_confirmation_gate.py +0 -0
  98. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/docker_volume_guard.py +0 -0
  99. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/dry_run_required.py +0 -0
  100. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/network_firewall_guard.py +0 -0
  101. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/operation_journal.py +0 -0
  102. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/package_manager_in_chroot.py +0 -0
  103. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/production_guard.py +0 -0
  104. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/remote_boot_partition_guard.py +0 -0
  105. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/remote_chroot_guard.py +0 -0
  106. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/ssh_destructive_command_guard.py +0 -0
  107. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/subagent_safety_briefing.py +0 -0
  108. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/subagent_transcript_audit.py +0 -0
  109. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/autopilot/system_scheduler_guard.py +0 -0
  110. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/__init__.py +0 -0
  111. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/_helpers.py +0 -0
  112. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_form_labels.py +0 -0
  113. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_heading_hierarchy.py +0 -0
  114. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_image_alt.py +0 -0
  115. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_interactive_elements.py +0 -0
  116. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/mobile_responsive_patterns.py +0 -0
  117. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/mobile_touch_targets.py +0 -0
  118. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/style_focus_visible.py +0 -0
  119. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/frontend/style_no_arbitrary_values.py +0 -0
  120. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/__init__.py +0 -0
  121. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_bare_except.py +0 -0
  122. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_dangerous_migration.py +0 -0
  123. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_sql_injection.py +0 -0
  124. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_unsafe_shell.py +0 -0
  125. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/python/no_wildcard_import.py +0 -0
  126. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/__init__.py +0 -0
  127. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/commit_message_format.py +0 -0
  128. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/naming_conventions.py +0 -0
  129. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/no_dead_imports.py +0 -0
  130. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/no_error_handling_removal.py +0 -0
  131. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/no_file_creation_sprawl.py +0 -0
  132. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/no_large_diff.py +0 -0
  133. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/quality/self_review_prompt.py +0 -0
  134. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/react/__init__.py +0 -0
  135. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/react/react_empty_state.py +0 -0
  136. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/react/react_lazy_loading.py +0 -0
  137. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/react/react_query_loading_state.py +0 -0
  138. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/__init__.py +0 -0
  139. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/env_credential_reference.py +0 -0
  140. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_bash_file_write.py +0 -0
  141. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_blocked_domain_fetch.py +0 -0
  142. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_compromised_action.py +0 -0
  143. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_leaked_secret_pattern.py +0 -0
  144. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_malicious_url_fetch.py +0 -0
  145. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/security/no_network_exfil.py +0 -0
  146. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/__init__.py +0 -0
  147. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/_helpers.py +0 -0
  148. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_open_graph.py +0 -0
  149. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_page_metadata.py +0 -0
  150. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_semantic_html.py +0 -0
  151. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_structured_data.py +0 -0
  152. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/__init__.py +0 -0
  153. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/cicd_pipeline_guard.py +0 -0
  154. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/cli_integration.py +0 -0
  155. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/dependency_hygiene.py +0 -0
  156. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/drift_detector.py +0 -0
  157. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/file_scope.py +0 -0
  158. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/git_checkpoint.py +0 -0
  159. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/max_file_size.py +0 -0
  160. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_compromised_dependency.py +0 -0
  161. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_debug_artifacts.py +0 -0
  162. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_destructive_commands.py +0 -0
  163. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_env_commit.py +0 -0
  164. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_force_push.py +0 -0
  165. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_push_to_main.py +0 -0
  166. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_secrets.py +0 -0
  167. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_skip_hooks.py +0 -0
  168. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_test_weakening.py +0 -0
  169. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_todo_left.py +0 -0
  170. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_vulnerable_import.py +0 -0
  171. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/no_vulnerable_version_install.py +0 -0
  172. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/package_publish_guard.py +0 -0
  173. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/test_with_changes.py +0 -0
  174. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/token_budget.py +0 -0
  175. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/packs/universal/token_burn_against_team_budget.py +0 -0
  176. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/py.typed +0 -0
  177. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/recorder.py +0 -0
  178. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/reporter.py +0 -0
  179. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/session.py +0 -0
  180. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/setup.py +0 -0
  181. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/template.py +0 -0
  182. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/utils/__init__.py +0 -0
  183. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/utils/bash.py +0 -0
  184. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/utils/git.py +0 -0
  185. {agentlint-2.2.0 → agentlint-2.3.0}/src/agentlint/utils/paths.py +0 -0
  186. {agentlint-2.2.0 → agentlint-2.3.0}/tests/__init__.py +0 -0
  187. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/__init__.py +0 -0
  188. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/security/test_no_blocked_domain_fetch.py +0 -0
  189. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/security/test_no_compromised_action.py +0 -0
  190. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/security/test_no_leaked_secret_pattern.py +0 -0
  191. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/security/test_no_malicious_url_fetch.py +0 -0
  192. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_autopilot_integration.py +0 -0
  193. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_autopilot_pack_loads.py +0 -0
  194. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_autopilot_post.py +0 -0
  195. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_autopilot_pre.py +0 -0
  196. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_cicd_pipeline_guard.py +0 -0
  197. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_cloud_infra_mutation.py +0 -0
  198. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_cloud_paid_resource_creation.py +0 -0
  199. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_cloud_resource_deletion.py +0 -0
  200. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_docker_volume_guard.py +0 -0
  201. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_edge_cases.py +0 -0
  202. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_env_credential_reference.py +0 -0
  203. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_frontend.py +0 -0
  204. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_git_checkpoint.py +0 -0
  205. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_network_firewall_guard.py +0 -0
  206. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_no_compromised_dependency.py +0 -0
  207. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_no_vulnerable_import.py +0 -0
  208. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_no_vulnerable_version_install.py +0 -0
  209. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_package_manager_in_chroot.py +0 -0
  210. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_package_publish_guard.py +0 -0
  211. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_python_post.py +0 -0
  212. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_quality_post.py +0 -0
  213. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_quality_pre.py +0 -0
  214. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_quality_stop.py +0 -0
  215. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_react.py +0 -0
  216. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_remote_boot_partition_guard.py +0 -0
  217. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_remote_chroot_guard.py +0 -0
  218. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_security_pre.py +0 -0
  219. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_seo.py +0 -0
  220. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_ssh_destructive_command_guard.py +0 -0
  221. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_subagent_safety_briefing.py +0 -0
  222. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_subagent_transcript_audit.py +0 -0
  223. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_system_scheduler_guard.py +0 -0
  224. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_token_budget.py +0 -0
  225. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_token_burn_against_team_budget.py +0 -0
  226. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_universal_post.py +0 -0
  227. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_universal_pre.py +0 -0
  228. {agentlint-2.2.0 → agentlint-2.3.0}/tests/packs/test_universal_stop.py +0 -0
  229. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_adapter_utils.py +0 -0
  230. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_agentchute_client.py +0 -0
  231. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_agentchute_feeds.py +0 -0
  232. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_agentchute_policy_queue_sync.py +0 -0
  233. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_agents_md.py +0 -0
  234. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_bash_utils.py +0 -0
  235. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_circuit_breaker.py +0 -0
  236. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_cli_integration.py +0 -0
  237. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_codex_adapter.py +0 -0
  238. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_config.py +0 -0
  239. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_continue_adapter.py +0 -0
  240. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_cursor_adapter.py +0 -0
  241. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_custom_rules.py +0 -0
  242. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_detector.py +0 -0
  243. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_file_scope.py +0 -0
  244. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_filters.py +0 -0
  245. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_gemini_adapter.py +0 -0
  246. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_generic_adapter.py +0 -0
  247. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_git.py +0 -0
  248. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_git_utils_checkpoint.py +0 -0
  249. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_grok_adapter.py +0 -0
  250. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_integration.py +0 -0
  251. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_kimi_adapter.py +0 -0
  252. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_main_module.py +0 -0
  253. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_mcp_adapter.py +0 -0
  254. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_mcp_server.py +0 -0
  255. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_models.py +0 -0
  256. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_openai_adapter.py +0 -0
  257. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_packs_loader.py +0 -0
  258. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_paths_utils.py +0 -0
  259. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_quality_new_rules.py +0 -0
  260. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_recorder.py +0 -0
  261. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_reporter.py +0 -0
  262. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_rule_inventory.py +0 -0
  263. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_session.py +0 -0
  264. {agentlint-2.2.0 → agentlint-2.3.0}/tests/test_setup.py +0 -0
  265. {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.2.0
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
- Provides-Extra: agentchute
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.2.0 highlights:** Public documentation cleanup, platform-neutral setup flow in the core README, and removal of internal planning/release artifacts from the package repository. Previous: 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.
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.2.0 highlights:** Public documentation cleanup, platform-neutral setup flow in the core README, and removal of internal planning/release artifacts from the package repository. Previous: 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.
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.2.0"
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"
@@ -1,6 +1,6 @@
1
1
  """AgentLint - Real-time quality guardrails for AI coding agents."""
2
2
 
3
- __version__ = "2.2.0"
3
+ __version__ = "2.3.0"
4
4
 
5
5
  from agentlint.core.models import (
6
6
  AgentEvent,
@@ -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 v in all_violations:
490
- sev = v.severity.value.upper()
491
- loc = f"{v.file_path}" if v.file_path else "?"
492
- click.echo(f"{loc} [{v.rule_id}] {sev} {v.message}")
493
- if v.suggestion:
494
- click.echo(f" → {v.suggestion}")
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.2.0"
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", marker = "extra == 'agentchute'", specifier = ">=2.31" },
39
- { name = "requests", marker = "extra == 'dev'", specifier = ">=2.31" },
35
+ { name = "requests", specifier = ">=2.31" },
40
36
  ]
41
- provides-extras = ["dev", "mcp", "agentchute"]
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