agentlint 2.2.1__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.1 → agentlint-2.3.0}/CHANGELOG.md +26 -0
  2. {agentlint-2.2.1 → agentlint-2.3.0}/PKG-INFO +13 -3
  3. {agentlint-2.2.1 → agentlint-2.3.0}/README.md +12 -2
  4. {agentlint-2.2.1 → agentlint-2.3.0}/agentlint.yml.example +5 -0
  5. {agentlint-2.2.1 → agentlint-2.3.0}/pyproject.toml +1 -1
  6. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/__init__.py +1 -1
  7. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/cli.py +34 -6
  8. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/engine.py +9 -0
  9. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/python/no_unnecessary_async.py +23 -0
  10. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_python_pre.py +43 -0
  11. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_ci.py +20 -0
  12. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_cli.py +1 -1
  13. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_engine.py +11 -0
  14. {agentlint-2.2.1 → agentlint-2.3.0}/uv.lock +1 -1
  15. {agentlint-2.2.1 → agentlint-2.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  16. {agentlint-2.2.1 → agentlint-2.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  17. {agentlint-2.2.1 → agentlint-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  18. {agentlint-2.2.1 → agentlint-2.3.0}/.github/workflows/ci.yml +0 -0
  19. {agentlint-2.2.1 → agentlint-2.3.0}/.github/workflows/publish.yml +0 -0
  20. {agentlint-2.2.1 → agentlint-2.3.0}/.gitignore +0 -0
  21. {agentlint-2.2.1 → agentlint-2.3.0}/.python-version +0 -0
  22. {agentlint-2.2.1 → agentlint-2.3.0}/CODE_OF_CONDUCT.md +0 -0
  23. {agentlint-2.2.1 → agentlint-2.3.0}/CONTRIBUTING.md +0 -0
  24. {agentlint-2.2.1 → agentlint-2.3.0}/LICENSE +0 -0
  25. {agentlint-2.2.1 → agentlint-2.3.0}/SECURITY.md +0 -0
  26. {agentlint-2.2.1 → agentlint-2.3.0}/codecov.yml +0 -0
  27. {agentlint-2.2.1 → agentlint-2.3.0}/docs/README.md +0 -0
  28. {agentlint-2.2.1 → agentlint-2.3.0}/docs/adapters.md +0 -0
  29. {agentlint-2.2.1 → agentlint-2.3.0}/docs/architecture.md +0 -0
  30. {agentlint-2.2.1 → agentlint-2.3.0}/docs/configuration.md +0 -0
  31. {agentlint-2.2.1 → agentlint-2.3.0}/docs/custom-rules.md +0 -0
  32. {agentlint-2.2.1 → agentlint-2.3.0}/docs/mcp.md +0 -0
  33. {agentlint-2.2.1 → agentlint-2.3.0}/docs/migration-v2.md +0 -0
  34. {agentlint-2.2.1 → agentlint-2.3.0}/docs/setup-claude.md +0 -0
  35. {agentlint-2.2.1 → agentlint-2.3.0}/docs/setup-codex.md +0 -0
  36. {agentlint-2.2.1 → agentlint-2.3.0}/docs/setup-continue.md +0 -0
  37. {agentlint-2.2.1 → agentlint-2.3.0}/docs/setup-cursor.md +0 -0
  38. {agentlint-2.2.1 → agentlint-2.3.0}/docs/setup-gemini.md +0 -0
  39. {agentlint-2.2.1 → agentlint-2.3.0}/docs/setup-generic.md +0 -0
  40. {agentlint-2.2.1 → agentlint-2.3.0}/docs/setup-grok.md +0 -0
  41. {agentlint-2.2.1 → agentlint-2.3.0}/docs/setup-kimi.md +0 -0
  42. {agentlint-2.2.1 → agentlint-2.3.0}/docs/setup-mcp.md +0 -0
  43. {agentlint-2.2.1 → agentlint-2.3.0}/docs/setup-openai.md +0 -0
  44. {agentlint-2.2.1 → agentlint-2.3.0}/docs/subagent-safety.md +0 -0
  45. {agentlint-2.2.1 → agentlint-2.3.0}/plugin/.claude-plugin/plugin.json +0 -0
  46. {agentlint-2.2.1 → agentlint-2.3.0}/plugin/agents/doctor.md +0 -0
  47. {agentlint-2.2.1 → agentlint-2.3.0}/plugin/agents/fix.md +0 -0
  48. {agentlint-2.2.1 → agentlint-2.3.0}/plugin/agents/security-audit.md +0 -0
  49. {agentlint-2.2.1 → agentlint-2.3.0}/plugin/bin/resolve-and-run.sh +0 -0
  50. {agentlint-2.2.1 → agentlint-2.3.0}/plugin/commands/lint-config.md +0 -0
  51. {agentlint-2.2.1 → agentlint-2.3.0}/plugin/commands/lint-status.md +0 -0
  52. {agentlint-2.2.1 → agentlint-2.3.0}/plugin/hooks/hooks.json +0 -0
  53. {agentlint-2.2.1 → agentlint-2.3.0}/plugin/mcp.json +0 -0
  54. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/__main__.py +0 -0
  55. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/__init__.py +0 -0
  56. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/_utils.py +0 -0
  57. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/base.py +0 -0
  58. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/claude.py +0 -0
  59. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/codex.py +0 -0
  60. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/continue_dev.py +0 -0
  61. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/cursor.py +0 -0
  62. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/gemini.py +0 -0
  63. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/generic.py +0 -0
  64. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/grok.py +0 -0
  65. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/kimi.py +0 -0
  66. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/mcp.py +0 -0
  67. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/adapters/openai_agents.py +0 -0
  68. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/agentchute/__init__.py +0 -0
  69. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/agentchute/client.py +0 -0
  70. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/agentchute/feeds.py +0 -0
  71. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/agentchute/policy.py +0 -0
  72. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/agentchute/queue.py +0 -0
  73. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/agentchute/sync.py +0 -0
  74. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/agents_md.py +0 -0
  75. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/circuit_breaker.py +0 -0
  76. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/config.py +0 -0
  77. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/core/__init__.py +0 -0
  78. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/core/models.py +0 -0
  79. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/detector.py +0 -0
  80. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/filters.py +0 -0
  81. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/formats/__init__.py +0 -0
  82. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/formats/base.py +0 -0
  83. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/formats/claude_hooks.py +0 -0
  84. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/formats/cursor_hooks.py +0 -0
  85. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/formats/gemini_hooks.py +0 -0
  86. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/formats/plain_json.py +0 -0
  87. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/mcp_server.py +0 -0
  88. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/models.py +0 -0
  89. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/__init__.py +0 -0
  90. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/__init__.py +0 -0
  91. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/_dangerous_patterns.py +0 -0
  92. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/bash_rate_limiter.py +0 -0
  93. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cloud_infra_mutation.py +0 -0
  94. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cloud_paid_resource_creation.py +0 -0
  95. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cloud_resource_deletion.py +0 -0
  96. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/cross_account_guard.py +0 -0
  97. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/destructive_confirmation_gate.py +0 -0
  98. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/docker_volume_guard.py +0 -0
  99. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/dry_run_required.py +0 -0
  100. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/network_firewall_guard.py +0 -0
  101. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/operation_journal.py +0 -0
  102. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/package_manager_in_chroot.py +0 -0
  103. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/production_guard.py +0 -0
  104. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/remote_boot_partition_guard.py +0 -0
  105. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/remote_chroot_guard.py +0 -0
  106. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/ssh_destructive_command_guard.py +0 -0
  107. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/subagent_safety_briefing.py +0 -0
  108. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/subagent_transcript_audit.py +0 -0
  109. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/autopilot/system_scheduler_guard.py +0 -0
  110. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/frontend/__init__.py +0 -0
  111. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/frontend/_helpers.py +0 -0
  112. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_form_labels.py +0 -0
  113. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_heading_hierarchy.py +0 -0
  114. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_image_alt.py +0 -0
  115. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/frontend/a11y_interactive_elements.py +0 -0
  116. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/frontend/mobile_responsive_patterns.py +0 -0
  117. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/frontend/mobile_touch_targets.py +0 -0
  118. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/frontend/style_focus_visible.py +0 -0
  119. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/frontend/style_no_arbitrary_values.py +0 -0
  120. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/python/__init__.py +0 -0
  121. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/python/no_bare_except.py +0 -0
  122. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/python/no_dangerous_migration.py +0 -0
  123. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/python/no_sql_injection.py +0 -0
  124. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/python/no_unsafe_shell.py +0 -0
  125. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/python/no_wildcard_import.py +0 -0
  126. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/quality/__init__.py +0 -0
  127. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/quality/commit_message_format.py +0 -0
  128. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/quality/naming_conventions.py +0 -0
  129. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/quality/no_dead_imports.py +0 -0
  130. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/quality/no_error_handling_removal.py +0 -0
  131. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/quality/no_file_creation_sprawl.py +0 -0
  132. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/quality/no_large_diff.py +0 -0
  133. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/quality/self_review_prompt.py +0 -0
  134. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/react/__init__.py +0 -0
  135. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/react/react_empty_state.py +0 -0
  136. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/react/react_lazy_loading.py +0 -0
  137. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/react/react_query_loading_state.py +0 -0
  138. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/security/__init__.py +0 -0
  139. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/security/env_credential_reference.py +0 -0
  140. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/security/no_bash_file_write.py +0 -0
  141. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/security/no_blocked_domain_fetch.py +0 -0
  142. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/security/no_compromised_action.py +0 -0
  143. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/security/no_leaked_secret_pattern.py +0 -0
  144. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/security/no_malicious_url_fetch.py +0 -0
  145. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/security/no_network_exfil.py +0 -0
  146. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/seo/__init__.py +0 -0
  147. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/seo/_helpers.py +0 -0
  148. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_open_graph.py +0 -0
  149. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_page_metadata.py +0 -0
  150. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_semantic_html.py +0 -0
  151. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/seo/seo_structured_data.py +0 -0
  152. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/__init__.py +0 -0
  153. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/cicd_pipeline_guard.py +0 -0
  154. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/cli_integration.py +0 -0
  155. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/dependency_hygiene.py +0 -0
  156. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/drift_detector.py +0 -0
  157. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/file_scope.py +0 -0
  158. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/git_checkpoint.py +0 -0
  159. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/max_file_size.py +0 -0
  160. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_compromised_dependency.py +0 -0
  161. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_debug_artifacts.py +0 -0
  162. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_destructive_commands.py +0 -0
  163. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_env_commit.py +0 -0
  164. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_force_push.py +0 -0
  165. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_push_to_main.py +0 -0
  166. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_secrets.py +0 -0
  167. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_skip_hooks.py +0 -0
  168. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_test_weakening.py +0 -0
  169. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_todo_left.py +0 -0
  170. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_vulnerable_import.py +0 -0
  171. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/no_vulnerable_version_install.py +0 -0
  172. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/package_publish_guard.py +0 -0
  173. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/test_with_changes.py +0 -0
  174. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/token_budget.py +0 -0
  175. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/packs/universal/token_burn_against_team_budget.py +0 -0
  176. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/py.typed +0 -0
  177. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/recorder.py +0 -0
  178. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/reporter.py +0 -0
  179. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/session.py +0 -0
  180. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/setup.py +0 -0
  181. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/template.py +0 -0
  182. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/utils/__init__.py +0 -0
  183. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/utils/bash.py +0 -0
  184. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/utils/git.py +0 -0
  185. {agentlint-2.2.1 → agentlint-2.3.0}/src/agentlint/utils/paths.py +0 -0
  186. {agentlint-2.2.1 → agentlint-2.3.0}/tests/__init__.py +0 -0
  187. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/__init__.py +0 -0
  188. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/security/test_no_blocked_domain_fetch.py +0 -0
  189. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/security/test_no_compromised_action.py +0 -0
  190. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/security/test_no_leaked_secret_pattern.py +0 -0
  191. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/security/test_no_malicious_url_fetch.py +0 -0
  192. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_autopilot_integration.py +0 -0
  193. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_autopilot_pack_loads.py +0 -0
  194. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_autopilot_post.py +0 -0
  195. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_autopilot_pre.py +0 -0
  196. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_cicd_pipeline_guard.py +0 -0
  197. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_cloud_infra_mutation.py +0 -0
  198. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_cloud_paid_resource_creation.py +0 -0
  199. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_cloud_resource_deletion.py +0 -0
  200. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_docker_volume_guard.py +0 -0
  201. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_edge_cases.py +0 -0
  202. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_env_credential_reference.py +0 -0
  203. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_frontend.py +0 -0
  204. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_git_checkpoint.py +0 -0
  205. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_network_firewall_guard.py +0 -0
  206. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_no_compromised_dependency.py +0 -0
  207. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_no_vulnerable_import.py +0 -0
  208. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_no_vulnerable_version_install.py +0 -0
  209. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_package_manager_in_chroot.py +0 -0
  210. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_package_publish_guard.py +0 -0
  211. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_python_post.py +0 -0
  212. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_quality_post.py +0 -0
  213. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_quality_pre.py +0 -0
  214. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_quality_stop.py +0 -0
  215. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_react.py +0 -0
  216. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_remote_boot_partition_guard.py +0 -0
  217. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_remote_chroot_guard.py +0 -0
  218. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_security_pre.py +0 -0
  219. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_seo.py +0 -0
  220. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_ssh_destructive_command_guard.py +0 -0
  221. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_subagent_safety_briefing.py +0 -0
  222. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_subagent_transcript_audit.py +0 -0
  223. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_system_scheduler_guard.py +0 -0
  224. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_token_budget.py +0 -0
  225. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_token_burn_against_team_budget.py +0 -0
  226. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_universal_post.py +0 -0
  227. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_universal_pre.py +0 -0
  228. {agentlint-2.2.1 → agentlint-2.3.0}/tests/packs/test_universal_stop.py +0 -0
  229. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_adapter_utils.py +0 -0
  230. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_agentchute_client.py +0 -0
  231. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_agentchute_feeds.py +0 -0
  232. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_agentchute_policy_queue_sync.py +0 -0
  233. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_agents_md.py +0 -0
  234. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_bash_utils.py +0 -0
  235. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_circuit_breaker.py +0 -0
  236. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_cli_integration.py +0 -0
  237. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_codex_adapter.py +0 -0
  238. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_config.py +0 -0
  239. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_continue_adapter.py +0 -0
  240. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_cursor_adapter.py +0 -0
  241. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_custom_rules.py +0 -0
  242. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_detector.py +0 -0
  243. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_file_scope.py +0 -0
  244. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_filters.py +0 -0
  245. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_gemini_adapter.py +0 -0
  246. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_generic_adapter.py +0 -0
  247. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_git.py +0 -0
  248. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_git_utils_checkpoint.py +0 -0
  249. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_grok_adapter.py +0 -0
  250. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_integration.py +0 -0
  251. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_kimi_adapter.py +0 -0
  252. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_main_module.py +0 -0
  253. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_mcp_adapter.py +0 -0
  254. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_mcp_server.py +0 -0
  255. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_models.py +0 -0
  256. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_openai_adapter.py +0 -0
  257. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_packs_loader.py +0 -0
  258. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_paths_utils.py +0 -0
  259. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_quality_new_rules.py +0 -0
  260. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_recorder.py +0 -0
  261. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_reporter.py +0 -0
  262. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_rule_inventory.py +0 -0
  263. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_session.py +0 -0
  264. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_setup.py +0 -0
  265. {agentlint-2.2.1 → agentlint-2.3.0}/tests/test_template.py +0 -0
@@ -1,5 +1,31 @@
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
+
3
29
  ## v2.2.1 (2026-05-10) — Install Experience Polish
4
30
 
5
31
  This release removes the last extra-step surprise from AgentChute setup.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentlint
3
- Version: 2.2.1
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
@@ -62,7 +62,7 @@ We don't have a mature answer to that yet. Nobody does. The **autopilot pack** i
62
62
 
63
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.
64
64
 
65
- **v2.2.1 highlights:** Install experience polish with `agentlint --version` and AgentChute sync support from the default `pip install agentlint` package. Previous: 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.
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.
66
66
 
67
67
  | Rule | Severity | What it does |
68
68
  |------|----------|-------------|
@@ -116,7 +116,7 @@ AgentLint ships with 76 rules across 8 packs and normalizes tool events across s
116
116
  | `no-unsafe-shell` | ERROR | Blocks unsafe shell execution via subprocess or os module |
117
117
  | `no-dangerous-migration` | WARNING | Warns on risky Alembic migration operations |
118
118
  | `no-wildcard-import` | WARNING | Prevents `from module import *` |
119
- | `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 |
120
120
  | `no-sql-injection` | ERROR | Blocks SQL via string interpolation (f-strings, `.format()`) |
121
121
 
122
122
  </details>
@@ -194,6 +194,16 @@ rules:
194
194
 
195
195
  </details>
196
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
+
197
207
  <details>
198
208
  <summary><strong>Autopilot pack</strong> (18 rules) — ⚠️ experimental, opt-in</summary>
199
209
 
@@ -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.1 highlights:** Install experience polish with `agentlint --version` and AgentChute sync support from the default `pip install agentlint` package. Previous: 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.
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
 
@@ -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.1"
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"
@@ -1,6 +1,6 @@
1
1
  """AgentLint - Real-time quality guardrails for AI coding agents."""
2
2
 
3
- __version__ = "2.2.1"
3
+ __version__ = "2.3.0"
4
4
 
5
5
  from agentlint.core.models import (
6
6
  AgentEvent,
@@ -488,12 +488,27 @@ def ci(diff: str | None, project_dir: str | None, output_format: str):
488
488
  if not all_violations:
489
489
  click.echo(f"Clean — {len(changed_files)} files scanned, {total_evaluated} rules evaluated.")
490
490
  else:
491
- for v in all_violations:
492
- sev = v.severity.value.upper()
493
- loc = f"{v.file_path}" if v.file_path else "?"
494
- click.echo(f"{loc} [{v.rule_id}] {sev} {v.message}")
495
- if v.suggestion:
496
- 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]}")
497
512
  click.echo()
498
513
  click.echo(
499
514
  f"{len(all_violations)} violation(s) "
@@ -504,6 +519,19 @@ def ci(diff: str | None, project_dir: str | None, output_format: str):
504
519
  sys.exit(1 if has_errors else 0)
505
520
 
506
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
+
507
535
  @main.command()
508
536
  @click.option("--project-dir", default=None, help="Project directory")
509
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
@@ -17,7 +17,7 @@ class TestMainCommand:
17
17
  result = runner.invoke(main, ["--version"])
18
18
 
19
19
  assert result.exit_code == 0
20
- assert result.output.strip() == "agentlint 2.2.1"
20
+ assert result.output.strip() == "agentlint 2.3.0"
21
21
 
22
22
 
23
23
  class TestCheckCommand:
@@ -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,7 +4,7 @@ requires-python = ">=3.11"
4
4
 
5
5
  [[package]]
6
6
  name = "agentlint"
7
- version = "2.2.1"
7
+ version = "2.3.0"
8
8
  source = { editable = "." }
9
9
  dependencies = [
10
10
  { name = "click" },
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