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