agentlint 2.2.1__tar.gz → 2.3.1__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.1}/CHANGELOG.md +55 -0
  2. {agentlint-2.2.1 → agentlint-2.3.1}/PKG-INFO +13 -3
  3. {agentlint-2.2.1 → agentlint-2.3.1}/README.md +12 -2
  4. {agentlint-2.2.1 → agentlint-2.3.1}/agentlint.yml.example +5 -0
  5. {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-claude.md +12 -0
  6. {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-codex.md +33 -0
  7. {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-cursor.md +19 -0
  8. {agentlint-2.2.1 → agentlint-2.3.1}/pyproject.toml +1 -1
  9. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/__init__.py +1 -1
  10. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/queue.py +25 -1
  11. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/cli.py +132 -6
  12. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/engine.py +9 -0
  13. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_unnecessary_async.py +23 -0
  14. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_python_pre.py +43 -0
  15. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_agentchute_policy_queue_sync.py +95 -1
  16. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_ci.py +20 -0
  17. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_cli.py +99 -2
  18. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_engine.py +11 -0
  19. {agentlint-2.2.1 → agentlint-2.3.1}/uv.lock +1 -1
  20. {agentlint-2.2.1 → agentlint-2.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  21. {agentlint-2.2.1 → agentlint-2.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {agentlint-2.2.1 → agentlint-2.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  23. {agentlint-2.2.1 → agentlint-2.3.1}/.github/workflows/ci.yml +0 -0
  24. {agentlint-2.2.1 → agentlint-2.3.1}/.github/workflows/publish.yml +0 -0
  25. {agentlint-2.2.1 → agentlint-2.3.1}/.gitignore +0 -0
  26. {agentlint-2.2.1 → agentlint-2.3.1}/.python-version +0 -0
  27. {agentlint-2.2.1 → agentlint-2.3.1}/CODE_OF_CONDUCT.md +0 -0
  28. {agentlint-2.2.1 → agentlint-2.3.1}/CONTRIBUTING.md +0 -0
  29. {agentlint-2.2.1 → agentlint-2.3.1}/LICENSE +0 -0
  30. {agentlint-2.2.1 → agentlint-2.3.1}/SECURITY.md +0 -0
  31. {agentlint-2.2.1 → agentlint-2.3.1}/codecov.yml +0 -0
  32. {agentlint-2.2.1 → agentlint-2.3.1}/docs/README.md +0 -0
  33. {agentlint-2.2.1 → agentlint-2.3.1}/docs/adapters.md +0 -0
  34. {agentlint-2.2.1 → agentlint-2.3.1}/docs/architecture.md +0 -0
  35. {agentlint-2.2.1 → agentlint-2.3.1}/docs/configuration.md +0 -0
  36. {agentlint-2.2.1 → agentlint-2.3.1}/docs/custom-rules.md +0 -0
  37. {agentlint-2.2.1 → agentlint-2.3.1}/docs/mcp.md +0 -0
  38. {agentlint-2.2.1 → agentlint-2.3.1}/docs/migration-v2.md +0 -0
  39. {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-continue.md +0 -0
  40. {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-gemini.md +0 -0
  41. {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-generic.md +0 -0
  42. {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-grok.md +0 -0
  43. {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-kimi.md +0 -0
  44. {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-mcp.md +0 -0
  45. {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-openai.md +0 -0
  46. {agentlint-2.2.1 → agentlint-2.3.1}/docs/subagent-safety.md +0 -0
  47. {agentlint-2.2.1 → agentlint-2.3.1}/plugin/.claude-plugin/plugin.json +0 -0
  48. {agentlint-2.2.1 → agentlint-2.3.1}/plugin/agents/doctor.md +0 -0
  49. {agentlint-2.2.1 → agentlint-2.3.1}/plugin/agents/fix.md +0 -0
  50. {agentlint-2.2.1 → agentlint-2.3.1}/plugin/agents/security-audit.md +0 -0
  51. {agentlint-2.2.1 → agentlint-2.3.1}/plugin/bin/resolve-and-run.sh +0 -0
  52. {agentlint-2.2.1 → agentlint-2.3.1}/plugin/commands/lint-config.md +0 -0
  53. {agentlint-2.2.1 → agentlint-2.3.1}/plugin/commands/lint-status.md +0 -0
  54. {agentlint-2.2.1 → agentlint-2.3.1}/plugin/hooks/hooks.json +0 -0
  55. {agentlint-2.2.1 → agentlint-2.3.1}/plugin/mcp.json +0 -0
  56. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/__main__.py +0 -0
  57. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/__init__.py +0 -0
  58. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/_utils.py +0 -0
  59. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/base.py +0 -0
  60. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/claude.py +0 -0
  61. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/codex.py +0 -0
  62. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/continue_dev.py +0 -0
  63. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/cursor.py +0 -0
  64. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/gemini.py +0 -0
  65. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/generic.py +0 -0
  66. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/grok.py +0 -0
  67. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/kimi.py +0 -0
  68. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/mcp.py +0 -0
  69. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/openai_agents.py +0 -0
  70. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/__init__.py +0 -0
  71. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/client.py +0 -0
  72. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/feeds.py +0 -0
  73. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/policy.py +0 -0
  74. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/sync.py +0 -0
  75. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agents_md.py +0 -0
  76. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/circuit_breaker.py +0 -0
  77. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/config.py +0 -0
  78. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/core/__init__.py +0 -0
  79. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/core/models.py +0 -0
  80. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/detector.py +0 -0
  81. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/filters.py +0 -0
  82. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/__init__.py +0 -0
  83. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/base.py +0 -0
  84. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/claude_hooks.py +0 -0
  85. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/cursor_hooks.py +0 -0
  86. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/gemini_hooks.py +0 -0
  87. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/plain_json.py +0 -0
  88. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/mcp_server.py +0 -0
  89. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/models.py +0 -0
  90. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/__init__.py +0 -0
  91. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/__init__.py +0 -0
  92. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/_dangerous_patterns.py +0 -0
  93. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/bash_rate_limiter.py +0 -0
  94. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cloud_infra_mutation.py +0 -0
  95. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cloud_paid_resource_creation.py +0 -0
  96. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cloud_resource_deletion.py +0 -0
  97. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cross_account_guard.py +0 -0
  98. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/destructive_confirmation_gate.py +0 -0
  99. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/docker_volume_guard.py +0 -0
  100. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/dry_run_required.py +0 -0
  101. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/network_firewall_guard.py +0 -0
  102. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/operation_journal.py +0 -0
  103. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/package_manager_in_chroot.py +0 -0
  104. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/production_guard.py +0 -0
  105. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/remote_boot_partition_guard.py +0 -0
  106. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/remote_chroot_guard.py +0 -0
  107. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/ssh_destructive_command_guard.py +0 -0
  108. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/subagent_safety_briefing.py +0 -0
  109. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/subagent_transcript_audit.py +0 -0
  110. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/system_scheduler_guard.py +0 -0
  111. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/__init__.py +0 -0
  112. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/_helpers.py +0 -0
  113. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_form_labels.py +0 -0
  114. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_heading_hierarchy.py +0 -0
  115. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_image_alt.py +0 -0
  116. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_interactive_elements.py +0 -0
  117. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/mobile_responsive_patterns.py +0 -0
  118. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/mobile_touch_targets.py +0 -0
  119. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/style_focus_visible.py +0 -0
  120. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/style_no_arbitrary_values.py +0 -0
  121. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/__init__.py +0 -0
  122. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_bare_except.py +0 -0
  123. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_dangerous_migration.py +0 -0
  124. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_sql_injection.py +0 -0
  125. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_unsafe_shell.py +0 -0
  126. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_wildcard_import.py +0 -0
  127. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/__init__.py +0 -0
  128. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/commit_message_format.py +0 -0
  129. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/naming_conventions.py +0 -0
  130. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/no_dead_imports.py +0 -0
  131. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/no_error_handling_removal.py +0 -0
  132. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/no_file_creation_sprawl.py +0 -0
  133. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/no_large_diff.py +0 -0
  134. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/self_review_prompt.py +0 -0
  135. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/react/__init__.py +0 -0
  136. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/react/react_empty_state.py +0 -0
  137. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/react/react_lazy_loading.py +0 -0
  138. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/react/react_query_loading_state.py +0 -0
  139. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/__init__.py +0 -0
  140. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/env_credential_reference.py +0 -0
  141. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_bash_file_write.py +0 -0
  142. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_blocked_domain_fetch.py +0 -0
  143. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_compromised_action.py +0 -0
  144. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_leaked_secret_pattern.py +0 -0
  145. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_malicious_url_fetch.py +0 -0
  146. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_network_exfil.py +0 -0
  147. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/__init__.py +0 -0
  148. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/_helpers.py +0 -0
  149. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_open_graph.py +0 -0
  150. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_page_metadata.py +0 -0
  151. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_semantic_html.py +0 -0
  152. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_structured_data.py +0 -0
  153. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/__init__.py +0 -0
  154. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/cicd_pipeline_guard.py +0 -0
  155. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/cli_integration.py +0 -0
  156. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/dependency_hygiene.py +0 -0
  157. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/drift_detector.py +0 -0
  158. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/file_scope.py +0 -0
  159. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/git_checkpoint.py +0 -0
  160. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/max_file_size.py +0 -0
  161. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_compromised_dependency.py +0 -0
  162. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_debug_artifacts.py +0 -0
  163. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_destructive_commands.py +0 -0
  164. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_env_commit.py +0 -0
  165. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_force_push.py +0 -0
  166. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_push_to_main.py +0 -0
  167. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_secrets.py +0 -0
  168. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_skip_hooks.py +0 -0
  169. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_test_weakening.py +0 -0
  170. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_todo_left.py +0 -0
  171. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_vulnerable_import.py +0 -0
  172. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_vulnerable_version_install.py +0 -0
  173. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/package_publish_guard.py +0 -0
  174. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/test_with_changes.py +0 -0
  175. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/token_budget.py +0 -0
  176. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/token_burn_against_team_budget.py +0 -0
  177. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/py.typed +0 -0
  178. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/recorder.py +0 -0
  179. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/reporter.py +0 -0
  180. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/session.py +0 -0
  181. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/setup.py +0 -0
  182. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/template.py +0 -0
  183. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/utils/__init__.py +0 -0
  184. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/utils/bash.py +0 -0
  185. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/utils/git.py +0 -0
  186. {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/utils/paths.py +0 -0
  187. {agentlint-2.2.1 → agentlint-2.3.1}/tests/__init__.py +0 -0
  188. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/__init__.py +0 -0
  189. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/security/test_no_blocked_domain_fetch.py +0 -0
  190. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/security/test_no_compromised_action.py +0 -0
  191. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/security/test_no_leaked_secret_pattern.py +0 -0
  192. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/security/test_no_malicious_url_fetch.py +0 -0
  193. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_autopilot_integration.py +0 -0
  194. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_autopilot_pack_loads.py +0 -0
  195. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_autopilot_post.py +0 -0
  196. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_autopilot_pre.py +0 -0
  197. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_cicd_pipeline_guard.py +0 -0
  198. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_cloud_infra_mutation.py +0 -0
  199. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_cloud_paid_resource_creation.py +0 -0
  200. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_cloud_resource_deletion.py +0 -0
  201. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_docker_volume_guard.py +0 -0
  202. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_edge_cases.py +0 -0
  203. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_env_credential_reference.py +0 -0
  204. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_frontend.py +0 -0
  205. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_git_checkpoint.py +0 -0
  206. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_network_firewall_guard.py +0 -0
  207. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_no_compromised_dependency.py +0 -0
  208. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_no_vulnerable_import.py +0 -0
  209. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_no_vulnerable_version_install.py +0 -0
  210. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_package_manager_in_chroot.py +0 -0
  211. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_package_publish_guard.py +0 -0
  212. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_python_post.py +0 -0
  213. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_quality_post.py +0 -0
  214. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_quality_pre.py +0 -0
  215. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_quality_stop.py +0 -0
  216. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_react.py +0 -0
  217. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_remote_boot_partition_guard.py +0 -0
  218. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_remote_chroot_guard.py +0 -0
  219. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_security_pre.py +0 -0
  220. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_seo.py +0 -0
  221. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_ssh_destructive_command_guard.py +0 -0
  222. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_subagent_safety_briefing.py +0 -0
  223. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_subagent_transcript_audit.py +0 -0
  224. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_system_scheduler_guard.py +0 -0
  225. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_token_budget.py +0 -0
  226. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_token_burn_against_team_budget.py +0 -0
  227. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_universal_post.py +0 -0
  228. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_universal_pre.py +0 -0
  229. {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_universal_stop.py +0 -0
  230. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_adapter_utils.py +0 -0
  231. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_agentchute_client.py +0 -0
  232. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_agentchute_feeds.py +0 -0
  233. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_agents_md.py +0 -0
  234. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_bash_utils.py +0 -0
  235. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_circuit_breaker.py +0 -0
  236. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_cli_integration.py +0 -0
  237. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_codex_adapter.py +0 -0
  238. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_config.py +0 -0
  239. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_continue_adapter.py +0 -0
  240. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_cursor_adapter.py +0 -0
  241. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_custom_rules.py +0 -0
  242. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_detector.py +0 -0
  243. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_file_scope.py +0 -0
  244. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_filters.py +0 -0
  245. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_gemini_adapter.py +0 -0
  246. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_generic_adapter.py +0 -0
  247. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_git.py +0 -0
  248. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_git_utils_checkpoint.py +0 -0
  249. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_grok_adapter.py +0 -0
  250. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_integration.py +0 -0
  251. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_kimi_adapter.py +0 -0
  252. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_main_module.py +0 -0
  253. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_mcp_adapter.py +0 -0
  254. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_mcp_server.py +0 -0
  255. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_models.py +0 -0
  256. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_openai_adapter.py +0 -0
  257. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_packs_loader.py +0 -0
  258. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_paths_utils.py +0 -0
  259. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_quality_new_rules.py +0 -0
  260. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_recorder.py +0 -0
  261. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_reporter.py +0 -0
  262. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_rule_inventory.py +0 -0
  263. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_session.py +0 -0
  264. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_setup.py +0 -0
  265. {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_template.py +0 -0
@@ -1,5 +1,60 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.3.1 (2026-05-15) — AgentChute Onboarding Polish
4
+
5
+ This release applies first-run AgentChute setup feedback from local Codex
6
+ dogfooding. It makes the install path less ambiguous and keeps diagnostics from
7
+ mutating or crashing on the local sync queue.
8
+
9
+ ### Changed
10
+
11
+ - `agentlint init --team-key ...` now prints `AGENTCHUTE_API_URL` alongside the
12
+ license key and enable flag, so local AgentChute tests keep targeting the
13
+ local API instead of silently falling back to production defaults.
14
+ - `agentlint setup codex` now enables `codex_hooks = true` under `[features]` in
15
+ `~/.codex/config.toml`, and repairs stale root or nested `codex_hooks` keys
16
+ that can make Codex fail to load its config.
17
+ - Codex, Claude Code, and Cursor setup docs now include AgentChute local testing
18
+ env vars and restart/reload guidance.
19
+ - `agentlint sync --dry-run` now counts queued events without acquiring the
20
+ flush lock or advancing the delivery cursor.
21
+ - AgentChute queue flushing now treats lock acquisition failures as a locked
22
+ queue instead of crashing.
23
+
24
+ ### Compatibility
25
+
26
+ - Existing hook files and AgentLint configs remain valid.
27
+ - Codex users must still restart Codex after enabling hooks; Codex loads hooks
28
+ when the session starts.
29
+
30
+ ---
31
+
32
+ ## v2.3.0 (2026-05-14) — CI Signal Polish
33
+
34
+ This release applies real-world Codex and AgentLint dogfooding feedback. It
35
+ keeps CI adoption safe by preserving INFO-as-non-blocking behavior while
36
+ reducing repeated output and avoiding framework-convention false positives.
37
+
38
+ ### Changed
39
+
40
+ - `no-unnecessary-async` is now FastAPI-aware and skips route handlers such as
41
+ `@router.get(...)` and `@app.post(...)` by default.
42
+ - `agentlint ci --format text` now groups repeated findings by file, rule, and
43
+ severity while leaving JSON output unchanged.
44
+ - Added per-rule `ignore_paths` as an accepted-pattern alias for documented
45
+ local conventions.
46
+ - Documented `ignore_fastapi_routes`, per-rule `ignore_paths`, and `reason`
47
+ examples in the sample config and README.
48
+
49
+ ### Compatibility
50
+
51
+ - INFO findings still do not fail CI by default.
52
+ - Teams that want strict async-route checks can set
53
+ `rules.no-unnecessary-async.ignore_fastapi_routes: false`.
54
+ - Existing `allow_paths` behavior is unchanged.
55
+
56
+ ---
57
+
3
58
  ## v2.2.1 (2026-05-10) — Install Experience Polish
4
59
 
5
60
  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.1
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.1 highlights:** AgentChute onboarding polish with clearer local setup output, safe Codex hook enablement, safer sync dry-run diagnostics, 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.1 highlights:** AgentChute onboarding polish with clearer local setup output, safe Codex hook enablement, safer sync dry-run diagnostics, 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
 
@@ -18,6 +18,17 @@ This creates `.claude/settings.json` in your project root with hooks for:
18
18
  - `Notification` — notification-triggered rules
19
19
  - `Stop` — session summary report
20
20
 
21
+ For AgentChute local testing, set the API URL before starting Claude Code:
22
+
23
+ ```bash
24
+ export AGENTCHUTE_API_URL=http://localhost:8000/v1
25
+ export AGENTCHUTE_LICENSE_KEY=ac_team_...
26
+ export AGENTCHUTE_ENABLED=true
27
+ ```
28
+
29
+ Restart Claude Code in the project after running `agentlint setup claude` so it
30
+ loads the updated hook config.
31
+
21
32
  ## Hook Format
22
33
 
23
34
  Claude Code uses a JSON-based hook protocol:
@@ -74,6 +85,7 @@ Removes only AgentLint hooks; preserves any other custom hooks you have configur
74
85
  **Hooks not firing?**
75
86
  - Verify `.claude/settings.json` exists and contains a `"hooks"` key
76
87
  - Check that the `agentlint` binary is on PATH or that `agentlint setup` embedded an absolute path
88
+ - Restart Claude Code after installing hooks
77
89
  - Run `agentlint doctor` to diagnose installation issues
78
90
 
79
91
  **Blocking rules not actually blocking?**
@@ -16,6 +16,33 @@ This creates `.codex/hooks.json` in your project root with hooks for:
16
16
  - `SessionStart` — session initialization
17
17
  - `Stop` — session summary report
18
18
 
19
+ Codex also requires native hooks to be enabled globally. `agentlint setup codex`
20
+ updates `~/.codex/config.toml` for you by adding the flag under `[features]`:
21
+
22
+ ```toml
23
+ [features]
24
+ codex_hooks = true
25
+ ```
26
+
27
+ Do not append `codex_hooks = true` to the end of `config.toml` manually. TOML
28
+ assigns bare keys to the most recent table, so appending after a section such as
29
+ `[tui.model_availability_nux]` can make Codex fail to load its config.
30
+
31
+ Restart Codex from the terminal where your AgentLint/AgentChute environment
32
+ variables are set:
33
+
34
+ ```bash
35
+ codex
36
+ ```
37
+
38
+ For AgentChute local testing, set the API URL before starting Codex:
39
+
40
+ ```bash
41
+ export AGENTCHUTE_API_URL=http://localhost:8000/v1
42
+ export AGENTCHUTE_LICENSE_KEY=ac_team_...
43
+ export AGENTCHUTE_ENABLED=true
44
+ ```
45
+
19
46
  ## Important: Codex Hook Coverage
20
47
 
21
48
  Codex's PreToolUse hook currently only reliably intercepts **Bash tool calls**. Coverage for `apply_patch` edits and MCP tool calls is intermittent and depends on the Codex CLI version. This is a known upstream limitation.
@@ -71,6 +98,12 @@ Removes only AgentLint hooks; preserves any other custom hooks you have configur
71
98
  - Ensure your Codex CLI is up to date; hook support is evolving rapidly
72
99
  - Bash tool calls should always trigger PreToolUse hooks
73
100
 
101
+ **No events in AgentChute?**
102
+ - Ensure `codex_hooks = true` is present under `[features]` in `~/.codex/config.toml`
103
+ - Start a fresh Codex session after running `agentlint setup codex`
104
+ - Launch Codex from the same terminal where `AGENTCHUTE_API_URL`, `AGENTCHUTE_LICENSE_KEY`, and `AGENTCHUTE_ENABLED` are exported
105
+ - Run `agentlint agentchute status` and `agentlint sync` from the project root
106
+
74
107
  **Need full file-write coverage?**
75
108
  - Consider using AgentLint's MCP server alongside hooks for pre-validation
76
109
  - Run `agentlint doctor` for alternative integration suggestions
@@ -17,6 +17,17 @@ This creates `.cursor/hooks.json` in your project root with hooks for:
17
17
  - `subagentStart` / `subagentStop` — subagent safety
18
18
  - `stop` — session summary report
19
19
 
20
+ For AgentChute local testing, set the API URL before launching Cursor:
21
+
22
+ ```bash
23
+ export AGENTCHUTE_API_URL=http://localhost:8000/v1
24
+ export AGENTCHUTE_LICENSE_KEY=ac_team_...
25
+ export AGENTCHUTE_ENABLED=true
26
+ ```
27
+
28
+ Reload the Cursor window after running `agentlint setup cursor` so it loads the
29
+ updated hook config.
30
+
20
31
  ## Hook Format
21
32
 
22
33
  Cursor hooks use a JSON-based protocol:
@@ -57,3 +68,11 @@ agentlint uninstall cursor
57
68
  | `CURSOR_SESSION_ID` | Session ID for state tracking |
58
69
  | `AGENTLINT_PROJECT_DIR` | Generic project directory (takes precedence) |
59
70
  | `AGENTLINT_SESSION_ID` | Generic session ID (takes precedence) |
71
+
72
+ ## Troubleshooting
73
+
74
+ **No events in AgentChute?**
75
+ - Verify `.cursor/hooks.json` exists in the project
76
+ - Reload the Cursor window after installing hooks
77
+ - Ensure Cursor inherits `AGENTCHUTE_API_URL`, `AGENTCHUTE_LICENSE_KEY`, and `AGENTCHUTE_ENABLED`
78
+ - Run `agentlint agentchute status` and `agentlint sync` from the project root
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentlint"
3
- version = "2.2.1"
3
+ version = "2.3.1"
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.1"
4
4
 
5
5
  from agentlint.core.models import (
6
6
  AgentEvent,
@@ -140,10 +140,31 @@ def flush_queue(
140
140
  """Flush queued events to AgentChute in bounded batches."""
141
141
  result = FlushResult()
142
142
  client = AgentChuteClient.from_env()
143
- if client is None:
143
+ if client is None and not dry_run:
144
144
  result.aborted_reason = "AGENTCHUTE_LICENSE_KEY not set"
145
145
  return result
146
146
 
147
+ if dry_run:
148
+ cursor = int(_load_json(_cursor_path(), {"offset": 0}).get("offset", 0))
149
+ lines = _read_lines()
150
+ if cursor >= len(lines):
151
+ return result
152
+ pending = lines[cursor:]
153
+ if max_events is not None:
154
+ pending = pending[:max_events]
155
+ for raw in pending:
156
+ if not raw.strip():
157
+ result.skipped += 1
158
+ continue
159
+ try:
160
+ json.loads(raw)
161
+ except json.JSONDecodeError:
162
+ result.skipped += 1
163
+ continue
164
+ result.attempted += 1
165
+ result.delivered += 1
166
+ return result
167
+
147
168
  if not _acquire_lock():
148
169
  result.locked = True
149
170
  return result
@@ -271,6 +292,9 @@ def _acquire_lock() -> bool:
271
292
  return False
272
293
  return _acquire_lock()
273
294
  return False
295
+ except OSError:
296
+ logger.debug("agentlint.agentchute: could not acquire flush lock", exc_info=True)
297
+ return False
274
298
 
275
299
 
276
300
  def _release_lock() -> None:
@@ -4,8 +4,10 @@ from __future__ import annotations
4
4
  import json
5
5
  import logging
6
6
  import os
7
+ import re
7
8
  import sys
8
9
  import time
10
+ import tomllib
9
11
  from pathlib import Path
10
12
  from typing import Any
11
13
 
@@ -40,6 +42,10 @@ from agentlint.utils.git import get_changed_files, get_diff_files
40
42
 
41
43
  logger = logging.getLogger("agentlint")
42
44
 
45
+ _TOML_SECTION_RE = re.compile(r"^\s*\[([^\[\]]+)\]\s*(?:#.*)?$")
46
+ _CODEX_HOOKS_KEY_RE = re.compile(r"^(\s*)codex_hooks\s*=.*$")
47
+ _HOOKS_KEY_RE = re.compile(r"^(\s*)hooks\s*=.*$")
48
+
43
49
  _SUPPORTED_ADAPTERS = (
44
50
  "claude",
45
51
  "cursor",
@@ -64,6 +70,85 @@ def _resolve_project_dir(project_dir: str | None = None) -> str:
64
70
  )
65
71
 
66
72
 
73
+ def _codex_hooks_enabled() -> bool:
74
+ """Return whether Codex native hooks are enabled in ~/.codex/config.toml."""
75
+ config_path = Path.home() / ".codex" / "config.toml"
76
+ try:
77
+ text = config_path.read_text()
78
+ except OSError:
79
+ return False
80
+
81
+ try:
82
+ config = tomllib.loads(text)
83
+ except tomllib.TOMLDecodeError:
84
+ return False
85
+
86
+ features = config.get("features")
87
+ if not isinstance(features, dict):
88
+ return False
89
+ return features.get("hooks") is True or features.get("codex_hooks") is True
90
+
91
+
92
+ def _enable_codex_hooks() -> Path:
93
+ """Enable Codex hooks in ~/.codex/config.toml without corrupting TOML tables."""
94
+ config_path = Path.home() / ".codex" / "config.toml"
95
+ config_path.parent.mkdir(parents=True, exist_ok=True)
96
+
97
+ try:
98
+ text = config_path.read_text()
99
+ except OSError:
100
+ text = ""
101
+
102
+ lines = text.splitlines()
103
+ output: list[str] = []
104
+ current_section: str | None = None
105
+ features_start: int | None = None
106
+ features_end: int | None = None
107
+ features_codex_hooks_index: int | None = None
108
+ features_hooks_index: int | None = None
109
+
110
+ for line in lines:
111
+ section_match = _TOML_SECTION_RE.match(line)
112
+ if section_match:
113
+ if current_section == "features" and features_end is None:
114
+ features_end = len(output)
115
+ current_section = section_match.group(1).strip()
116
+ if current_section == "features":
117
+ features_start = len(output)
118
+ output.append(line)
119
+ continue
120
+
121
+ if _CODEX_HOOKS_KEY_RE.match(line):
122
+ if current_section == "features":
123
+ features_codex_hooks_index = len(output)
124
+ output.append("codex_hooks = true")
125
+ # Drop stale root or nested codex_hooks keys. The flag is only valid
126
+ # under [features], and nested keys can break Codex schema loading.
127
+ continue
128
+
129
+ if current_section == "features" and _HOOKS_KEY_RE.match(line):
130
+ features_hooks_index = len(output)
131
+ output.append("hooks = true")
132
+ continue
133
+
134
+ output.append(line)
135
+
136
+ if current_section == "features" and features_end is None:
137
+ features_end = len(output)
138
+
139
+ if features_hooks_index is None and features_codex_hooks_index is None:
140
+ if features_start is None:
141
+ if output and output[-1].strip():
142
+ output.append("")
143
+ output.extend(["[features]", "codex_hooks = true"])
144
+ else:
145
+ insert_at = features_end if features_end is not None else len(output)
146
+ output.insert(insert_at, "codex_hooks = true")
147
+
148
+ config_path.write_text("\n".join(output) + "\n")
149
+ return config_path
150
+
151
+
67
152
  def _resolve_adapter(adapter_name: str | None = None) -> Any:
68
153
  """Resolve the agent adapter from explicit flag or environment detection.
69
154
 
@@ -488,12 +573,27 @@ def ci(diff: str | None, project_dir: str | None, output_format: str):
488
573
  if not all_violations:
489
574
  click.echo(f"Clean — {len(changed_files)} files scanned, {total_evaluated} rules evaluated.")
490
575
  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}")
576
+ for group in _group_ci_violations(all_violations):
577
+ if len(group) == 1:
578
+ v = group[0]
579
+ sev = v.severity.value.upper()
580
+ loc = f"{v.file_path}" if v.file_path else "?"
581
+ click.echo(f"{loc} [{v.rule_id}] {sev} {v.message}")
582
+ if v.suggestion:
583
+ click.echo(f" → {v.suggestion}")
584
+ click.echo()
585
+ continue
586
+
587
+ first = group[0]
588
+ sev = first.severity.value.upper()
589
+ loc = f"{first.file_path}" if first.file_path else "?"
590
+ click.echo(f"{loc} [{first.rule_id}] {sev} {len(group)} findings")
591
+ for v in group:
592
+ line = f"line {v.line}: " if v.line is not None else ""
593
+ click.echo(f" - {line}{v.message}")
594
+ suggestions = sorted({v.suggestion for v in group if v.suggestion})
595
+ if len(suggestions) == 1:
596
+ click.echo(f" → {suggestions[0]}")
497
597
  click.echo()
498
598
  click.echo(
499
599
  f"{len(all_violations)} violation(s) "
@@ -504,6 +604,19 @@ def ci(diff: str | None, project_dir: str | None, output_format: str):
504
604
  sys.exit(1 if has_errors else 0)
505
605
 
506
606
 
607
+ def _group_ci_violations(violations: list) -> list[list]:
608
+ """Group repeated text CI findings by file, rule, and severity."""
609
+ grouped: list[list] = []
610
+ index: dict[tuple, list] = {}
611
+ for violation in violations:
612
+ key = (violation.file_path, violation.rule_id, violation.severity.value)
613
+ if key not in index:
614
+ index[key] = []
615
+ grouped.append(index[key])
616
+ index[key].append(violation)
617
+ return grouped
618
+
619
+
507
620
  @main.command()
508
621
  @click.option("--project-dir", default=None, help="Project directory")
509
622
  @click.option(
@@ -557,8 +670,12 @@ agentchute:
557
670
  click.echo(f"Created {config_path}")
558
671
  click.echo(f"Detected packs: {', '.join(packs)}")
559
672
  if team_key:
673
+ from agentlint.agentchute.client import DEFAULT_API_URL, ENV_AGENTCHUTE_API_URL
674
+
675
+ api_url = os.environ.get(ENV_AGENTCHUTE_API_URL, DEFAULT_API_URL)
560
676
  click.echo("")
561
677
  click.echo("AgentChute enabled. Add these env vars to your shell or AI tool settings:")
678
+ click.echo(f"export AGENTCHUTE_API_URL={api_url}")
562
679
  click.echo(f"export AGENTCHUTE_LICENSE_KEY={team_key}")
563
680
  click.echo("export AGENTCHUTE_ENABLED=true")
564
681
 
@@ -686,6 +803,15 @@ def setup(platform: str, scope: str, project_dir: str | None, dry_run: bool):
686
803
 
687
804
  click.echo(f"Installed AgentLint hooks for {platform}")
688
805
 
806
+ if platform == "codex" and not dry_run:
807
+ if _codex_hooks_enabled():
808
+ click.echo("Codex hooks are enabled in ~/.codex/config.toml.")
809
+ else:
810
+ config_path = _enable_codex_hooks()
811
+ click.echo(f"Enabled Codex hooks in {config_path}.")
812
+ click.echo("Set [features].codex_hooks = true.")
813
+ click.echo("Restart Codex from the terminal where your AgentLint/AgentChute env vars are set.")
814
+
689
815
  # Also create agentlint.yml if it doesn't exist
690
816
  config_path = os.path.join(project_dir, "agentlint.yml")
691
817
  if not dry_run and not os.path.exists(config_path):
@@ -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
  # ---------------------------------------------------------------------------