agentlint 2.3.0__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.3.0 → agentlint-2.3.1}/CHANGELOG.md +29 -0
  2. {agentlint-2.3.0 → agentlint-2.3.1}/PKG-INFO +2 -2
  3. {agentlint-2.3.0 → agentlint-2.3.1}/README.md +1 -1
  4. {agentlint-2.3.0 → agentlint-2.3.1}/docs/setup-claude.md +12 -0
  5. {agentlint-2.3.0 → agentlint-2.3.1}/docs/setup-codex.md +33 -0
  6. {agentlint-2.3.0 → agentlint-2.3.1}/docs/setup-cursor.md +19 -0
  7. {agentlint-2.3.0 → agentlint-2.3.1}/pyproject.toml +1 -1
  8. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/__init__.py +1 -1
  9. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/agentchute/queue.py +25 -1
  10. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/cli.py +98 -0
  11. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_agentchute_policy_queue_sync.py +95 -1
  12. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_cli.py +99 -2
  13. {agentlint-2.3.0 → agentlint-2.3.1}/uv.lock +1 -1
  14. {agentlint-2.3.0 → agentlint-2.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  15. {agentlint-2.3.0 → agentlint-2.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  16. {agentlint-2.3.0 → agentlint-2.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {agentlint-2.3.0 → agentlint-2.3.1}/.github/workflows/ci.yml +0 -0
  18. {agentlint-2.3.0 → agentlint-2.3.1}/.github/workflows/publish.yml +0 -0
  19. {agentlint-2.3.0 → agentlint-2.3.1}/.gitignore +0 -0
  20. {agentlint-2.3.0 → agentlint-2.3.1}/.python-version +0 -0
  21. {agentlint-2.3.0 → agentlint-2.3.1}/CODE_OF_CONDUCT.md +0 -0
  22. {agentlint-2.3.0 → agentlint-2.3.1}/CONTRIBUTING.md +0 -0
  23. {agentlint-2.3.0 → agentlint-2.3.1}/LICENSE +0 -0
  24. {agentlint-2.3.0 → agentlint-2.3.1}/SECURITY.md +0 -0
  25. {agentlint-2.3.0 → agentlint-2.3.1}/agentlint.yml.example +0 -0
  26. {agentlint-2.3.0 → agentlint-2.3.1}/codecov.yml +0 -0
  27. {agentlint-2.3.0 → agentlint-2.3.1}/docs/README.md +0 -0
  28. {agentlint-2.3.0 → agentlint-2.3.1}/docs/adapters.md +0 -0
  29. {agentlint-2.3.0 → agentlint-2.3.1}/docs/architecture.md +0 -0
  30. {agentlint-2.3.0 → agentlint-2.3.1}/docs/configuration.md +0 -0
  31. {agentlint-2.3.0 → agentlint-2.3.1}/docs/custom-rules.md +0 -0
  32. {agentlint-2.3.0 → agentlint-2.3.1}/docs/mcp.md +0 -0
  33. {agentlint-2.3.0 → agentlint-2.3.1}/docs/migration-v2.md +0 -0
  34. {agentlint-2.3.0 → agentlint-2.3.1}/docs/setup-continue.md +0 -0
  35. {agentlint-2.3.0 → agentlint-2.3.1}/docs/setup-gemini.md +0 -0
  36. {agentlint-2.3.0 → agentlint-2.3.1}/docs/setup-generic.md +0 -0
  37. {agentlint-2.3.0 → agentlint-2.3.1}/docs/setup-grok.md +0 -0
  38. {agentlint-2.3.0 → agentlint-2.3.1}/docs/setup-kimi.md +0 -0
  39. {agentlint-2.3.0 → agentlint-2.3.1}/docs/setup-mcp.md +0 -0
  40. {agentlint-2.3.0 → agentlint-2.3.1}/docs/setup-openai.md +0 -0
  41. {agentlint-2.3.0 → agentlint-2.3.1}/docs/subagent-safety.md +0 -0
  42. {agentlint-2.3.0 → agentlint-2.3.1}/plugin/.claude-plugin/plugin.json +0 -0
  43. {agentlint-2.3.0 → agentlint-2.3.1}/plugin/agents/doctor.md +0 -0
  44. {agentlint-2.3.0 → agentlint-2.3.1}/plugin/agents/fix.md +0 -0
  45. {agentlint-2.3.0 → agentlint-2.3.1}/plugin/agents/security-audit.md +0 -0
  46. {agentlint-2.3.0 → agentlint-2.3.1}/plugin/bin/resolve-and-run.sh +0 -0
  47. {agentlint-2.3.0 → agentlint-2.3.1}/plugin/commands/lint-config.md +0 -0
  48. {agentlint-2.3.0 → agentlint-2.3.1}/plugin/commands/lint-status.md +0 -0
  49. {agentlint-2.3.0 → agentlint-2.3.1}/plugin/hooks/hooks.json +0 -0
  50. {agentlint-2.3.0 → agentlint-2.3.1}/plugin/mcp.json +0 -0
  51. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/__main__.py +0 -0
  52. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/__init__.py +0 -0
  53. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/_utils.py +0 -0
  54. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/base.py +0 -0
  55. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/claude.py +0 -0
  56. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/codex.py +0 -0
  57. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/continue_dev.py +0 -0
  58. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/cursor.py +0 -0
  59. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/gemini.py +0 -0
  60. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/generic.py +0 -0
  61. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/grok.py +0 -0
  62. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/kimi.py +0 -0
  63. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/mcp.py +0 -0
  64. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/adapters/openai_agents.py +0 -0
  65. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/agentchute/__init__.py +0 -0
  66. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/agentchute/client.py +0 -0
  67. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/agentchute/feeds.py +0 -0
  68. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/agentchute/policy.py +0 -0
  69. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/agentchute/sync.py +0 -0
  70. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/agents_md.py +0 -0
  71. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/circuit_breaker.py +0 -0
  72. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/config.py +0 -0
  73. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/core/__init__.py +0 -0
  74. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/core/models.py +0 -0
  75. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/detector.py +0 -0
  76. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/engine.py +0 -0
  77. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/filters.py +0 -0
  78. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/formats/__init__.py +0 -0
  79. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/formats/base.py +0 -0
  80. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/formats/claude_hooks.py +0 -0
  81. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/formats/cursor_hooks.py +0 -0
  82. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/formats/gemini_hooks.py +0 -0
  83. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/formats/plain_json.py +0 -0
  84. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/mcp_server.py +0 -0
  85. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/models.py +0 -0
  86. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/__init__.py +0 -0
  87. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/__init__.py +0 -0
  88. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/_dangerous_patterns.py +0 -0
  89. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/bash_rate_limiter.py +0 -0
  90. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cloud_infra_mutation.py +0 -0
  91. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cloud_paid_resource_creation.py +0 -0
  92. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cloud_resource_deletion.py +0 -0
  93. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cross_account_guard.py +0 -0
  94. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/destructive_confirmation_gate.py +0 -0
  95. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/docker_volume_guard.py +0 -0
  96. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/dry_run_required.py +0 -0
  97. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/network_firewall_guard.py +0 -0
  98. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/operation_journal.py +0 -0
  99. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/package_manager_in_chroot.py +0 -0
  100. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/production_guard.py +0 -0
  101. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/remote_boot_partition_guard.py +0 -0
  102. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/remote_chroot_guard.py +0 -0
  103. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/ssh_destructive_command_guard.py +0 -0
  104. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/subagent_safety_briefing.py +0 -0
  105. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/subagent_transcript_audit.py +0 -0
  106. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/autopilot/system_scheduler_guard.py +0 -0
  107. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/frontend/__init__.py +0 -0
  108. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/frontend/_helpers.py +0 -0
  109. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_form_labels.py +0 -0
  110. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_heading_hierarchy.py +0 -0
  111. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_image_alt.py +0 -0
  112. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_interactive_elements.py +0 -0
  113. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/frontend/mobile_responsive_patterns.py +0 -0
  114. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/frontend/mobile_touch_targets.py +0 -0
  115. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/frontend/style_focus_visible.py +0 -0
  116. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/frontend/style_no_arbitrary_values.py +0 -0
  117. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/python/__init__.py +0 -0
  118. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/python/no_bare_except.py +0 -0
  119. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/python/no_dangerous_migration.py +0 -0
  120. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/python/no_sql_injection.py +0 -0
  121. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/python/no_unnecessary_async.py +0 -0
  122. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/python/no_unsafe_shell.py +0 -0
  123. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/python/no_wildcard_import.py +0 -0
  124. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/quality/__init__.py +0 -0
  125. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/quality/commit_message_format.py +0 -0
  126. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/quality/naming_conventions.py +0 -0
  127. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/quality/no_dead_imports.py +0 -0
  128. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/quality/no_error_handling_removal.py +0 -0
  129. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/quality/no_file_creation_sprawl.py +0 -0
  130. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/quality/no_large_diff.py +0 -0
  131. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/quality/self_review_prompt.py +0 -0
  132. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/react/__init__.py +0 -0
  133. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/react/react_empty_state.py +0 -0
  134. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/react/react_lazy_loading.py +0 -0
  135. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/react/react_query_loading_state.py +0 -0
  136. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/security/__init__.py +0 -0
  137. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/security/env_credential_reference.py +0 -0
  138. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/security/no_bash_file_write.py +0 -0
  139. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/security/no_blocked_domain_fetch.py +0 -0
  140. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/security/no_compromised_action.py +0 -0
  141. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/security/no_leaked_secret_pattern.py +0 -0
  142. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/security/no_malicious_url_fetch.py +0 -0
  143. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/security/no_network_exfil.py +0 -0
  144. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/seo/__init__.py +0 -0
  145. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/seo/_helpers.py +0 -0
  146. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_open_graph.py +0 -0
  147. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_page_metadata.py +0 -0
  148. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_semantic_html.py +0 -0
  149. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_structured_data.py +0 -0
  150. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/__init__.py +0 -0
  151. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/cicd_pipeline_guard.py +0 -0
  152. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/cli_integration.py +0 -0
  153. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/dependency_hygiene.py +0 -0
  154. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/drift_detector.py +0 -0
  155. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/file_scope.py +0 -0
  156. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/git_checkpoint.py +0 -0
  157. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/max_file_size.py +0 -0
  158. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_compromised_dependency.py +0 -0
  159. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_debug_artifacts.py +0 -0
  160. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_destructive_commands.py +0 -0
  161. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_env_commit.py +0 -0
  162. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_force_push.py +0 -0
  163. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_push_to_main.py +0 -0
  164. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_secrets.py +0 -0
  165. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_skip_hooks.py +0 -0
  166. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_test_weakening.py +0 -0
  167. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_todo_left.py +0 -0
  168. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_vulnerable_import.py +0 -0
  169. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/no_vulnerable_version_install.py +0 -0
  170. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/package_publish_guard.py +0 -0
  171. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/test_with_changes.py +0 -0
  172. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/token_budget.py +0 -0
  173. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/packs/universal/token_burn_against_team_budget.py +0 -0
  174. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/py.typed +0 -0
  175. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/recorder.py +0 -0
  176. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/reporter.py +0 -0
  177. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/session.py +0 -0
  178. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/setup.py +0 -0
  179. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/template.py +0 -0
  180. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/utils/__init__.py +0 -0
  181. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/utils/bash.py +0 -0
  182. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/utils/git.py +0 -0
  183. {agentlint-2.3.0 → agentlint-2.3.1}/src/agentlint/utils/paths.py +0 -0
  184. {agentlint-2.3.0 → agentlint-2.3.1}/tests/__init__.py +0 -0
  185. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/__init__.py +0 -0
  186. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/security/test_no_blocked_domain_fetch.py +0 -0
  187. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/security/test_no_compromised_action.py +0 -0
  188. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/security/test_no_leaked_secret_pattern.py +0 -0
  189. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/security/test_no_malicious_url_fetch.py +0 -0
  190. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_autopilot_integration.py +0 -0
  191. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_autopilot_pack_loads.py +0 -0
  192. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_autopilot_post.py +0 -0
  193. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_autopilot_pre.py +0 -0
  194. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_cicd_pipeline_guard.py +0 -0
  195. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_cloud_infra_mutation.py +0 -0
  196. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_cloud_paid_resource_creation.py +0 -0
  197. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_cloud_resource_deletion.py +0 -0
  198. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_docker_volume_guard.py +0 -0
  199. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_edge_cases.py +0 -0
  200. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_env_credential_reference.py +0 -0
  201. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_frontend.py +0 -0
  202. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_git_checkpoint.py +0 -0
  203. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_network_firewall_guard.py +0 -0
  204. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_no_compromised_dependency.py +0 -0
  205. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_no_vulnerable_import.py +0 -0
  206. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_no_vulnerable_version_install.py +0 -0
  207. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_package_manager_in_chroot.py +0 -0
  208. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_package_publish_guard.py +0 -0
  209. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_python_post.py +0 -0
  210. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_python_pre.py +0 -0
  211. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_quality_post.py +0 -0
  212. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_quality_pre.py +0 -0
  213. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_quality_stop.py +0 -0
  214. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_react.py +0 -0
  215. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_remote_boot_partition_guard.py +0 -0
  216. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_remote_chroot_guard.py +0 -0
  217. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_security_pre.py +0 -0
  218. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_seo.py +0 -0
  219. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_ssh_destructive_command_guard.py +0 -0
  220. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_subagent_safety_briefing.py +0 -0
  221. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_subagent_transcript_audit.py +0 -0
  222. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_system_scheduler_guard.py +0 -0
  223. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_token_budget.py +0 -0
  224. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_token_burn_against_team_budget.py +0 -0
  225. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_universal_post.py +0 -0
  226. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_universal_pre.py +0 -0
  227. {agentlint-2.3.0 → agentlint-2.3.1}/tests/packs/test_universal_stop.py +0 -0
  228. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_adapter_utils.py +0 -0
  229. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_agentchute_client.py +0 -0
  230. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_agentchute_feeds.py +0 -0
  231. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_agents_md.py +0 -0
  232. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_bash_utils.py +0 -0
  233. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_ci.py +0 -0
  234. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_circuit_breaker.py +0 -0
  235. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_cli_integration.py +0 -0
  236. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_codex_adapter.py +0 -0
  237. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_config.py +0 -0
  238. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_continue_adapter.py +0 -0
  239. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_cursor_adapter.py +0 -0
  240. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_custom_rules.py +0 -0
  241. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_detector.py +0 -0
  242. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_engine.py +0 -0
  243. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_file_scope.py +0 -0
  244. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_filters.py +0 -0
  245. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_gemini_adapter.py +0 -0
  246. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_generic_adapter.py +0 -0
  247. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_git.py +0 -0
  248. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_git_utils_checkpoint.py +0 -0
  249. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_grok_adapter.py +0 -0
  250. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_integration.py +0 -0
  251. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_kimi_adapter.py +0 -0
  252. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_main_module.py +0 -0
  253. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_mcp_adapter.py +0 -0
  254. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_mcp_server.py +0 -0
  255. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_models.py +0 -0
  256. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_openai_adapter.py +0 -0
  257. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_packs_loader.py +0 -0
  258. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_paths_utils.py +0 -0
  259. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_quality_new_rules.py +0 -0
  260. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_recorder.py +0 -0
  261. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_reporter.py +0 -0
  262. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_rule_inventory.py +0 -0
  263. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_session.py +0 -0
  264. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_setup.py +0 -0
  265. {agentlint-2.3.0 → agentlint-2.3.1}/tests/test_template.py +0 -0
@@ -1,5 +1,34 @@
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
+
3
32
  ## v2.3.0 (2026-05-14) — CI Signal Polish
4
33
 
5
34
  This release applies real-world Codex and AgentLint dogfooding feedback. It
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentlint
3
- Version: 2.3.0
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.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.
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
  |------|----------|-------------|
@@ -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.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.
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
  |------|----------|-------------|
@@ -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.3.0"
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.3.0"
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
 
@@ -585,8 +670,12 @@ agentchute:
585
670
  click.echo(f"Created {config_path}")
586
671
  click.echo(f"Detected packs: {', '.join(packs)}")
587
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)
588
676
  click.echo("")
589
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}")
590
679
  click.echo(f"export AGENTCHUTE_LICENSE_KEY={team_key}")
591
680
  click.echo("export AGENTCHUTE_ENABLED=true")
592
681
 
@@ -714,6 +803,15 @@ def setup(platform: str, scope: str, project_dir: str | None, dry_run: bool):
714
803
 
715
804
  click.echo(f"Installed AgentLint hooks for {platform}")
716
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
+
717
815
  # Also create agentlint.yml if it doesn't exist
718
816
  config_path = os.path.join(project_dir, "agentlint.yml")
719
817
  if not dry_run and not os.path.exists(config_path):
@@ -252,7 +252,35 @@ def test_queue_enqueue_flush_dry_run_and_status(tmp_path, monkeypatch):
252
252
 
253
253
  assert result.attempted == 1
254
254
  assert result.delivered == 1
255
- assert queue.queue_status()["pending"] == 0
255
+ assert queue.queue_status()["pending"] == 1
256
+ assert not (tmp_path / "flush.lock").exists()
257
+
258
+
259
+ def test_queue_flush_dry_run_counts_limited_poison_entries(tmp_path, monkeypatch):
260
+ from agentlint.agentchute import queue
261
+
262
+ monkeypatch.setenv("AGENTLINT_AGENTCHUTE_QUEUE_DIR", str(tmp_path))
263
+ monkeypatch.setenv("AGENTCHUTE_LICENSE_KEY", "ac_team_test_x")
264
+ monkeypatch.setenv("AGENTCHUTE_ENABLED", "true")
265
+ queue._save_json(tmp_path / "cursor.json", {"offset": 1})
266
+ (tmp_path / "queue.jsonl").write_text(
267
+ "\n".join(
268
+ [
269
+ json.dumps({"event_id": "already-delivered", "event": {}}),
270
+ "",
271
+ "{not-json",
272
+ json.dumps({"event_id": "e1", "event": {"n": 1}}),
273
+ ]
274
+ ),
275
+ encoding="utf-8",
276
+ )
277
+
278
+ result = queue.flush_queue(max_events=3, dry_run=True)
279
+
280
+ assert result.skipped == 2
281
+ assert result.attempted == 1
282
+ assert result.delivered == 1
283
+ assert json.loads((tmp_path / "cursor.json").read_text())["offset"] == 1
256
284
 
257
285
 
258
286
  def test_queue_disabled_and_oversized_events_are_not_queued(tmp_path, monkeypatch):
@@ -330,6 +358,35 @@ def test_queue_flush_success_paths_and_aborts(tmp_path, monkeypatch):
330
358
  assert json.loads((tmp_path / "retry.json").read_text())["failures"] == 1
331
359
 
332
360
 
361
+ def test_queue_flush_empty_queue_clears_retry(tmp_path, monkeypatch):
362
+ from agentlint.agentchute import queue
363
+
364
+ monkeypatch.setenv("AGENTLINT_AGENTCHUTE_QUEUE_DIR", str(tmp_path))
365
+ monkeypatch.setenv("AGENTCHUTE_LICENSE_KEY", "ac_team_test_x")
366
+ monkeypatch.setenv("AGENTCHUTE_ENABLED", "true")
367
+ queue._save_json(tmp_path / "retry.json", {"failures": 2, "next_attempt_at": 123})
368
+
369
+ result = queue.flush_queue()
370
+
371
+ assert result.attempted == 0
372
+ assert not (tmp_path / "retry.json").exists()
373
+
374
+
375
+ def test_queue_flush_advances_over_poison_only_batch(tmp_path, monkeypatch):
376
+ from agentlint.agentchute import queue
377
+
378
+ monkeypatch.setenv("AGENTLINT_AGENTCHUTE_QUEUE_DIR", str(tmp_path))
379
+ monkeypatch.setenv("AGENTCHUTE_LICENSE_KEY", "ac_team_test_x")
380
+ monkeypatch.setenv("AGENTCHUTE_ENABLED", "true")
381
+ (tmp_path / "queue.jsonl").write_text("\n{not-json\n", encoding="utf-8")
382
+
383
+ result = queue.flush_queue(batch_size=2)
384
+
385
+ assert result.skipped == 2
386
+ assert result.attempted == 0
387
+ assert json.loads((tmp_path / "cursor.json").read_text())["offset"] == 2
388
+
389
+
333
390
  def test_queue_flush_handles_lock_and_stale_lock(tmp_path, monkeypatch):
334
391
  from agentlint.agentchute import queue
335
392
 
@@ -349,11 +406,48 @@ def test_queue_flush_handles_lock_and_stale_lock(tmp_path, monkeypatch):
349
406
  assert queue.flush_queue(dry_run=True).locked is False
350
407
 
351
408
 
409
+ def test_queue_filesystem_failures_are_nonfatal(tmp_path, monkeypatch):
410
+ from agentlint.agentchute import queue
411
+
412
+ monkeypatch.setenv("AGENTLINT_AGENTCHUTE_QUEUE_DIR", str(tmp_path))
413
+ monkeypatch.setenv("AGENTCHUTE_LICENSE_KEY", "ac_team_test_x")
414
+ monkeypatch.setenv("AGENTCHUTE_ENABLED", "true")
415
+ queue._save_json(tmp_path / "queue.jsonl", {"not": "jsonl"})
416
+
417
+ with patch("pathlib.Path.read_text", side_effect=OSError("cannot read")):
418
+ assert queue._read_lines() == []
419
+
420
+ with patch("builtins.open", side_effect=OSError("cannot count")):
421
+ assert queue._count_lines(tmp_path / "queue.jsonl") == 0
422
+
423
+ with patch("os.open", side_effect=OSError("cannot lock")):
424
+ assert queue.flush_queue().locked is True
425
+
426
+ lock = tmp_path / "flush.lock"
427
+ lock.write_text("other", encoding="utf-8")
428
+ old = 1
429
+ import os
430
+
431
+ os.utime(lock, (old, old))
432
+ with patch("pathlib.Path.unlink", side_effect=OSError("cannot unlink stale lock")):
433
+ assert queue.flush_queue().locked is True
434
+
435
+ with patch("pathlib.Path.unlink", side_effect=OSError("cannot unlink")):
436
+ queue._release_lock()
437
+ queue._clear_retry()
438
+
439
+
352
440
  def test_trigger_background_flush_respects_retry_and_spawns(tmp_path, monkeypatch):
353
441
  from agentlint.agentchute import queue
354
442
 
355
443
  monkeypatch.setenv("AGENTLINT_AGENTCHUTE_QUEUE_DIR", str(tmp_path))
356
444
  monkeypatch.setenv("AGENTCHUTE_LICENSE_KEY", "ac_team_test_x")
445
+ monkeypatch.setenv("AGENTCHUTE_ENABLED", "true")
446
+ monkeypatch.setenv("AGENTCHUTE_ENABLED", "false")
447
+ with patch("subprocess.Popen") as popen:
448
+ queue.trigger_background_flush()
449
+ popen.assert_not_called()
450
+
357
451
  monkeypatch.setenv("AGENTCHUTE_ENABLED", "true")
358
452
  queue._save_json(tmp_path / "retry.json", {"next_attempt_at": 9999999999})
359
453
  with patch("subprocess.Popen") as popen:
@@ -3,11 +3,12 @@ from __future__ import annotations
3
3
 
4
4
  import json
5
5
  import os
6
+ import tomllib
6
7
  from unittest.mock import patch
7
8
 
8
9
  from click.testing import CliRunner
9
10
 
10
- from agentlint.cli import _resolve_adapter, main
11
+ from agentlint.cli import _codex_hooks_enabled, _enable_codex_hooks, _resolve_adapter, main
11
12
 
12
13
 
13
14
  class TestMainCommand:
@@ -17,7 +18,7 @@ class TestMainCommand:
17
18
  result = runner.invoke(main, ["--version"])
18
19
 
19
20
  assert result.exit_code == 0
20
- assert result.output.strip() == "agentlint 2.3.0"
21
+ assert result.output.strip() == "agentlint 2.3.1"
21
22
 
22
23
 
23
24
  class TestCheckCommand:
@@ -102,6 +103,7 @@ class TestInitCommand:
102
103
  assert "agentchute:" in config_text
103
104
  assert "enabled: true" in config_text
104
105
  assert "ac_team_test_secret" not in config_text
106
+ assert "export AGENTCHUTE_API_URL=" in result.output
105
107
  assert "export AGENTCHUTE_LICENSE_KEY=ac_team_test_secret" in result.output
106
108
 
107
109
 
@@ -2005,6 +2007,101 @@ class TestSetupPlatformSubcommands:
2005
2007
  assert data["version"] == 1
2006
2008
  assert "hooks" in data
2007
2009
 
2010
+ def test_setup_codex_enables_global_hooks(self, tmp_path, monkeypatch) -> None:
2011
+ monkeypatch.setenv("HOME", str(tmp_path / "home"))
2012
+ runner = CliRunner()
2013
+ result = runner.invoke(main, ["setup", "codex", "--project-dir", str(tmp_path)])
2014
+ assert result.exit_code == 0
2015
+ assert "Installed AgentLint hooks for codex" in result.output
2016
+ assert "Enabled Codex hooks" in result.output
2017
+ assert "codex_hooks = true" in result.output
2018
+ assert "Restart Codex" in result.output
2019
+
2020
+ hooks_file = tmp_path / ".codex" / "hooks.json"
2021
+ assert hooks_file.exists()
2022
+ config_file = tmp_path / "home" / ".codex" / "config.toml"
2023
+ config = tomllib.loads(config_file.read_text())
2024
+ assert config["features"]["codex_hooks"] is True
2025
+
2026
+ def test_setup_codex_reports_existing_global_hooks(self, tmp_path, monkeypatch) -> None:
2027
+ home = tmp_path / "home"
2028
+ monkeypatch.setenv("HOME", str(home))
2029
+ config_file = home / ".codex" / "config.toml"
2030
+ config_file.parent.mkdir(parents=True)
2031
+ config_file.write_text("[features]\ncodex_hooks = true\n", encoding="utf-8")
2032
+
2033
+ runner = CliRunner()
2034
+ result = runner.invoke(main, ["setup", "codex", "--project-dir", str(tmp_path)])
2035
+
2036
+ assert result.exit_code == 0
2037
+ assert "Codex hooks are enabled in ~/.codex/config.toml." in result.output
2038
+ assert "Enabled Codex hooks" not in result.output
2039
+
2040
+ def test_setup_codex_repairs_orphan_codex_hooks_key(self, tmp_path, monkeypatch) -> None:
2041
+ home = tmp_path / "home"
2042
+ monkeypatch.setenv("HOME", str(home))
2043
+ config_file = home / ".codex" / "config.toml"
2044
+ config_file.parent.mkdir(parents=True)
2045
+ config_file.write_text(
2046
+ "\n".join(
2047
+ [
2048
+ 'model = "gpt-5.5"',
2049
+ "",
2050
+ "[features]",
2051
+ "goals = true",
2052
+ "",
2053
+ "[tui.model_availability_nux]",
2054
+ '"gpt-5.5" = 4',
2055
+ "codex_hooks = true",
2056
+ "",
2057
+ ]
2058
+ )
2059
+ )
2060
+
2061
+ runner = CliRunner()
2062
+ result = runner.invoke(main, ["setup", "codex", "--project-dir", str(tmp_path)])
2063
+ assert result.exit_code == 0
2064
+
2065
+ config = tomllib.loads(config_file.read_text())
2066
+ assert config["features"]["codex_hooks"] is True
2067
+ assert config["tui"]["model_availability_nux"] == {"gpt-5.5": 4}
2068
+
2069
+ def test_codex_hooks_enabled_reads_features_only(self, tmp_path, monkeypatch) -> None:
2070
+ home = tmp_path / "home"
2071
+ monkeypatch.setenv("HOME", str(home))
2072
+ config_file = home / ".codex" / "config.toml"
2073
+ config_file.parent.mkdir(parents=True)
2074
+
2075
+ assert _codex_hooks_enabled() is False
2076
+
2077
+ config_file.write_text("[features\n", encoding="utf-8")
2078
+ assert _codex_hooks_enabled() is False
2079
+
2080
+ config_file.write_text('features = "not-a-table"\n', encoding="utf-8")
2081
+ assert _codex_hooks_enabled() is False
2082
+
2083
+ config_file.write_text("[features]\nhooks = true\n", encoding="utf-8")
2084
+ assert _codex_hooks_enabled() is True
2085
+
2086
+ def test_enable_codex_hooks_handles_existing_feature_variants(self, tmp_path, monkeypatch) -> None:
2087
+ home = tmp_path / "home"
2088
+ monkeypatch.setenv("HOME", str(home))
2089
+ config_file = home / ".codex" / "config.toml"
2090
+ config_file.parent.mkdir(parents=True)
2091
+
2092
+ config_file.write_text("[features]\ncodex_hooks = false\n", encoding="utf-8")
2093
+ _enable_codex_hooks()
2094
+ assert tomllib.loads(config_file.read_text())["features"]["codex_hooks"] is True
2095
+
2096
+ config_file.write_text("[features]\nhooks = false\n", encoding="utf-8")
2097
+ _enable_codex_hooks()
2098
+ assert tomllib.loads(config_file.read_text())["features"]["hooks"] is True
2099
+
2100
+ config_file.write_text('model = "gpt-5.5"\n', encoding="utf-8")
2101
+ _enable_codex_hooks()
2102
+ text = config_file.read_text()
2103
+ assert '\n\n[features]\ncodex_hooks = true\n' in text
2104
+
2008
2105
  def test_setup_default_is_claude(self, tmp_path) -> None:
2009
2106
  """Backward compat: agentlint setup without platform defaults to claude."""
2010
2107
  runner = CliRunner()
@@ -4,7 +4,7 @@ requires-python = ">=3.11"
4
4
 
5
5
  [[package]]
6
6
  name = "agentlint"
7
- version = "2.3.0"
7
+ version = "2.3.1"
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