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.
- {agentlint-2.2.1 → agentlint-2.3.1}/CHANGELOG.md +55 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/PKG-INFO +13 -3
- {agentlint-2.2.1 → agentlint-2.3.1}/README.md +12 -2
- {agentlint-2.2.1 → agentlint-2.3.1}/agentlint.yml.example +5 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-claude.md +12 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-codex.md +33 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-cursor.md +19 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/pyproject.toml +1 -1
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/__init__.py +1 -1
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/queue.py +25 -1
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/cli.py +132 -6
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/engine.py +9 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_unnecessary_async.py +23 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_python_pre.py +43 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_agentchute_policy_queue_sync.py +95 -1
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_ci.py +20 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_cli.py +99 -2
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_engine.py +11 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/uv.lock +1 -1
- {agentlint-2.2.1 → agentlint-2.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/.github/workflows/ci.yml +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/.github/workflows/publish.yml +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/.gitignore +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/.python-version +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/CODE_OF_CONDUCT.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/CONTRIBUTING.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/LICENSE +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/SECURITY.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/codecov.yml +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/README.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/adapters.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/architecture.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/configuration.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/custom-rules.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/mcp.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/migration-v2.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-continue.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-gemini.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-generic.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-grok.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-kimi.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-mcp.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/setup-openai.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/docs/subagent-safety.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/plugin/.claude-plugin/plugin.json +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/plugin/agents/doctor.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/plugin/agents/fix.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/plugin/agents/security-audit.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/plugin/bin/resolve-and-run.sh +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/plugin/commands/lint-config.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/plugin/commands/lint-status.md +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/plugin/hooks/hooks.json +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/plugin/mcp.json +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/__main__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/_utils.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/base.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/claude.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/codex.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/continue_dev.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/cursor.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/gemini.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/generic.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/grok.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/kimi.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/mcp.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/adapters/openai_agents.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/client.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/feeds.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/policy.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agentchute/sync.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/agents_md.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/circuit_breaker.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/config.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/core/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/core/models.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/detector.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/filters.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/base.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/claude_hooks.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/cursor_hooks.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/gemini_hooks.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/formats/plain_json.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/mcp_server.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/models.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/_dangerous_patterns.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/bash_rate_limiter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cloud_infra_mutation.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cloud_paid_resource_creation.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cloud_resource_deletion.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/cross_account_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/destructive_confirmation_gate.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/docker_volume_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/dry_run_required.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/network_firewall_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/operation_journal.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/package_manager_in_chroot.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/production_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/remote_boot_partition_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/remote_chroot_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/ssh_destructive_command_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/subagent_safety_briefing.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/subagent_transcript_audit.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/autopilot/system_scheduler_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/_helpers.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_form_labels.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_heading_hierarchy.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_image_alt.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/a11y_interactive_elements.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/mobile_responsive_patterns.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/mobile_touch_targets.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/style_focus_visible.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/frontend/style_no_arbitrary_values.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_bare_except.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_dangerous_migration.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_sql_injection.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_unsafe_shell.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/python/no_wildcard_import.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/commit_message_format.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/naming_conventions.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/no_dead_imports.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/no_error_handling_removal.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/no_file_creation_sprawl.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/no_large_diff.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/quality/self_review_prompt.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/react/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/react/react_empty_state.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/react/react_lazy_loading.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/react/react_query_loading_state.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/env_credential_reference.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_bash_file_write.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_blocked_domain_fetch.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_compromised_action.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_leaked_secret_pattern.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_malicious_url_fetch.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/security/no_network_exfil.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/_helpers.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_open_graph.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_page_metadata.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_semantic_html.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/seo/seo_structured_data.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/cicd_pipeline_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/cli_integration.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/dependency_hygiene.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/drift_detector.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/file_scope.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/git_checkpoint.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/max_file_size.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_compromised_dependency.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_debug_artifacts.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_destructive_commands.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_env_commit.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_force_push.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_push_to_main.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_secrets.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_skip_hooks.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_test_weakening.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_todo_left.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_vulnerable_import.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/no_vulnerable_version_install.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/package_publish_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/test_with_changes.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/token_budget.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/packs/universal/token_burn_against_team_budget.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/py.typed +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/recorder.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/reporter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/session.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/setup.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/template.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/utils/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/utils/bash.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/utils/git.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/src/agentlint/utils/paths.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/__init__.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/security/test_no_blocked_domain_fetch.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/security/test_no_compromised_action.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/security/test_no_leaked_secret_pattern.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/security/test_no_malicious_url_fetch.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_autopilot_integration.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_autopilot_pack_loads.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_autopilot_post.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_autopilot_pre.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_cicd_pipeline_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_cloud_infra_mutation.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_cloud_paid_resource_creation.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_cloud_resource_deletion.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_docker_volume_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_edge_cases.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_env_credential_reference.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_frontend.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_git_checkpoint.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_network_firewall_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_no_compromised_dependency.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_no_vulnerable_import.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_no_vulnerable_version_install.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_package_manager_in_chroot.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_package_publish_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_python_post.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_quality_post.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_quality_pre.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_quality_stop.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_react.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_remote_boot_partition_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_remote_chroot_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_security_pre.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_seo.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_ssh_destructive_command_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_subagent_safety_briefing.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_subagent_transcript_audit.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_system_scheduler_guard.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_token_budget.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_token_burn_against_team_budget.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_universal_post.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_universal_pre.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/packs/test_universal_stop.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_adapter_utils.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_agentchute_client.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_agentchute_feeds.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_agents_md.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_bash_utils.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_circuit_breaker.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_cli_integration.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_codex_adapter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_config.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_continue_adapter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_cursor_adapter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_custom_rules.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_detector.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_file_scope.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_filters.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_gemini_adapter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_generic_adapter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_git.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_git_utils_checkpoint.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_grok_adapter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_integration.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_kimi_adapter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_main_module.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_mcp_adapter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_mcp_server.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_models.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_openai_adapter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_packs_loader.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_paths_utils.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_quality_new_rules.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_recorder.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_reporter.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_rule_inventory.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_session.py +0 -0
- {agentlint-2.2.1 → agentlint-2.3.1}/tests/test_setup.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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
|
|
@@ -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
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
click.echo(f"
|
|
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
|
# ---------------------------------------------------------------------------
|