controlzero 1.8.1__tar.gz → 1.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {controlzero-1.8.1 → controlzero-1.9.0}/CHANGELOG.md +108 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/PKG-INFO +2 -1
- {controlzero-1.8.1 → controlzero-1.9.0}/README.md +1 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/__init__.py +1 -1
- controlzero-1.9.0/controlzero/cli/hosts/antigravity.py +161 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/hosts/base.py +9 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/hosts/claude_code.py +14 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/kiro_adapter.py +42 -2
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/main.py +422 -7
- controlzero-1.9.0/controlzero/cli/templates/antigravity/hooks.json +28 -0
- controlzero-1.9.0/controlzero/cli/templates/antigravity.yaml +93 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +2 -2
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +2 -2
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +3 -3
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/device.py +24 -3
- {controlzero-1.8.1 → controlzero-1.9.0}/pyproject.toml +8 -1
- controlzero-1.9.0/tests/test_antigravity_adapter.py +275 -0
- controlzero-1.9.0/tests/test_antigravity_hook_check.py +160 -0
- controlzero-1.9.0/tests/test_antigravity_install.py +383 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_device.py +54 -1
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hosts_adapter.py +6 -3
- controlzero-1.9.0/tests/test_kiro_hook_templates.py +324 -0
- controlzero-1.8.1/controlzero/cli/hosts/antigravity.py +0 -76
- {controlzero-1.8.1 → controlzero-1.9.0}/.gitignore +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/Dockerfile.test +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/LICENSE +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/enforcer.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/_internal/types.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/audit_local.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/audit_remote.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/canonical.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/console.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/hosts/codex_cli.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/hosts/kiro.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/claude-code.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/client.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/enrollment.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/error_codes.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/errors.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/hitl/status.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/hosted_policy.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/google.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/layout_migration.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/policy_loader.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/_state.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/tamper.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/controlzero/tracecontext.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/examples/hello_world.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/conftest.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/integrations/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/integrations/test_google.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/__init__.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/conftest.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_action_aliases.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_audit_remote.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_cli_hook.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_cli_init.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_cli_tail.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_cli_test.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_cli_validate.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_coding_agent_hooks.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_conditions.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_conformance.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_console.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_credential_hook.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_default_action.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_doctor.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_enrollment.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_error_codes.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_glob_matching.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_6a_request_approval.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_phase2b_protocol.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_install_hook_command.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_install_hooks.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_kiro_adapter.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_kiro_install.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_log_rotation.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_migrate.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_policy_settings.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_quarantine.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_reason_code.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_refresh.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_secrets.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_synthetic_policy_id_t79.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_tamper.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_tracecontext.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.8.1 → controlzero-1.9.0}/tools/cz-kiro-adapter +0 -0
|
@@ -1,5 +1,113 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.0 -- 2026-06-15 (Antigravity install CLI, epic gh#925)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Google Antigravity (`agy`) governance -- BETA.** The `controlzero install
|
|
8
|
+
antigravity` / `uninstall antigravity` commands complete the Antigravity
|
|
9
|
+
integration on top of the host adapter that shipped in 1.8.0 (gh#927). Both
|
|
10
|
+
Antigravity surfaces (the IDE and the `agy` CLI) deliver the hook payload as
|
|
11
|
+
JSON on stdin, so they route directly through the shared
|
|
12
|
+
`controlzero hook-check` -> policy engine -> audit path with no shim, and
|
|
13
|
+
audit rows are attributed to `agent="antigravity"`.
|
|
14
|
+
|
|
15
|
+
- **Real EXTERNAL hooks.json contract.** Wires the `PreToolUse` (deciding
|
|
16
|
+
gate) + `PostToolUse` (observe) file-based events -- stdin JSON in, stdout
|
|
17
|
+
JSON out (camelCase), NOT exit-code based -- which is the contract the agy
|
|
18
|
+
IDE/CLI actually executes. (The in-process Python-SDK hook *class* names
|
|
19
|
+
`PreToolCallDecideHook` / `PostToolCallHook` are a different API and would
|
|
20
|
+
never fire from hooks.json.) The adapter ALWAYS emits an explicit
|
|
21
|
+
`{"decision": ...}`: an empty `{}` is read by agy as `invalid_args` and
|
|
22
|
+
DENIES the tool (cmux#5358), so silence-that-prints-`{}` is impossible.
|
|
23
|
+
|
|
24
|
+
- **`controlzero install antigravity`** (gh#928). Merges a `PreToolUse` +
|
|
25
|
+
`PostToolUse` block (broad `*` matcher) into the Antigravity `hooks.json`.
|
|
26
|
+
Writes the engine-EXECUTED path `~/.gemini/config/hooks.json` by default
|
|
27
|
+
and, for the user scope, also mirrors to the `agy` TUI `/hooks` display
|
|
28
|
+
path `~/.gemini/antigravity-cli/hooks.json` (antigravity-cli#49) so the
|
|
29
|
+
hook both fires AND displays; `<cwd>/.agents/hooks.json` with `--project`,
|
|
30
|
+
or a custom path with `--settings`. Idempotent (re-installs collapse to
|
|
31
|
+
one entry; byte-identical state), preserves pre-existing third-party hooks
|
|
32
|
+
(incl. ones sharing a block) and unrelated keys, and writes the
|
|
33
|
+
`~/.controlzero/policy.yaml` Antigravity template. `uninstall antigravity`
|
|
34
|
+
removes only the Control Zero entries (cleaning both paths) and preserves
|
|
35
|
+
your policy + audit log. Supports `--force` / `--merge` and `--api-key` /
|
|
36
|
+
`--email` for dashboard sync.
|
|
37
|
+
|
|
38
|
+
- **Human-in-the-loop on the wire.** A Control Zero approval gate (a deny
|
|
39
|
+
flagged `requires_approval`) maps to Antigravity's `ask` decision by
|
|
40
|
+
default. A plain `ask` can be auto-satisfied by a cached "Always Allow",
|
|
41
|
+
so set `CZ_ANTIGRAVITY_HITL_DECISION=force_ask` (a guaranteed prompt, once
|
|
42
|
+
verified on your pinned agy build) or `=deny` for a hard block. An
|
|
43
|
+
unrecognized value safely falls back to `ask`.
|
|
44
|
+
|
|
45
|
+
- **Agent fingerprint.** `detect_client_name()` now recognises Antigravity
|
|
46
|
+
via the `ANTIGRAVITY_` / `AGY_` env prefixes (checked BEFORE the Gemini CLI
|
|
47
|
+
branch, since Antigravity shares the `~/.gemini/` config lineage and may
|
|
48
|
+
also carry `GEMINI_*` vars). A stray `GEMINI_API_KEY` is never mislabeled
|
|
49
|
+
as Antigravity.
|
|
50
|
+
|
|
51
|
+
- **Docs.** New `docs/integrations/antigravity.md` install guide alongside
|
|
52
|
+
the existing `antigravity-hook-payloads.md` reference; `antigravity` added
|
|
53
|
+
to the SDK README template list, tagged **BETA**.
|
|
54
|
+
|
|
55
|
+
## 1.8.2 -- 2026-06-15 (Kiro IDE hook contract hotfix, epic gh#877)
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
The shipped Kiro IDE `.kiro.hook` templates used several **Kiro CLI** schema
|
|
60
|
+
conventions instead of the **Kiro IDE** ones (the two hook systems have
|
|
61
|
+
different event names and field keys). The most visible symptom was a
|
|
62
|
+
customer-reported P0; auditing the templates against Kiro's IDE schema
|
|
63
|
+
surfaced three more latent defects. All four are fixed, and a new regression
|
|
64
|
+
test (`tests/test_kiro_hook_templates.py`) pins each so they cannot ship again.
|
|
65
|
+
|
|
66
|
+
- **(P0) Prompt-submit hook rejected by the IDE.** `ide-prompt-submit.kiro.hook`
|
|
67
|
+
declared `"when": {"type": "userPromptSubmit"}`. `userPromptSubmit` is the
|
|
68
|
+
Kiro **CLI** event name; the IDE event is **`promptSubmit`**. The invalid
|
|
69
|
+
value made the IDE reject the entire hook file ("Hook file has invalid data
|
|
70
|
+
structure"), so the only active data-carrying IDE hook (prompt-submit, while
|
|
71
|
+
preToolUse ships disabled) never loaded and the IDE integration silently did
|
|
72
|
+
nothing. Now `promptSubmit`, with the matching `cz-kiro-adapter promptSubmit`
|
|
73
|
+
command. (The shim already accepts both spellings, so no behavior change.)
|
|
74
|
+
|
|
75
|
+
- **Pre-tool-use tool filter used the wrong key.** `ide-pre-tool-use.kiro.hook`
|
|
76
|
+
filtered tools with `"patterns": ["*"]`; the Kiro IDE tool filter key for
|
|
77
|
+
`preToolUse`/`postToolUse` is **`toolTypes`** (array). Now `"toolTypes":
|
|
78
|
+
["*"]`, so the hook is schema-correct if/when it is enabled (it still ships
|
|
79
|
+
`enabled: false` pending the upstream empty-payload bug).
|
|
80
|
+
|
|
81
|
+
- **File-save hook passed a tool-event arg + could block saves.**
|
|
82
|
+
`ide-file-save.kiro.hook` ran `cz-kiro-adapter postToolUse`, but a
|
|
83
|
+
`fileEdited` runCommand does not deliver tool context; the adapter would then
|
|
84
|
+
fail closed (block) on the empty payload -- i.e. it could block file saves.
|
|
85
|
+
The command is now `cz-kiro-adapter fileEdited`, and the shim treats file
|
|
86
|
+
events as a post-write **observation**: it synthesizes a `fileSave` tool
|
|
87
|
+
call from whatever `USER_PROMPT` carries, still runs `hook-check` for the
|
|
88
|
+
audit row + secret scan, but **always exits 0** on a file event -- a deny is
|
|
89
|
+
surfaced on stderr as a warning rather than blocking. The file is already
|
|
90
|
+
written, so blocking is pointless; only tool events (preToolUse) gate.
|
|
91
|
+
`when.type` stays the valid `fileEdited` with `patterns`.
|
|
92
|
+
|
|
93
|
+
- **Timeouts were ~83 minutes, not 5 seconds.** All three templates used
|
|
94
|
+
`"timeout": 5000`. Kiro IDE `then.timeout` is in **seconds** (default 60,
|
|
95
|
+
`0` disables), so `5000` meant ~83 minutes. Now `5` (seconds) on all three.
|
|
96
|
+
|
|
97
|
+
The regression test parses every shipped `*.kiro.hook` and asserts: valid
|
|
98
|
+
JSON; `when.type` in Kiro's 11-value IDE event set; tool events use
|
|
99
|
+
`toolTypes` (not `patterns`) and file events use `patterns` (not `toolTypes`);
|
|
100
|
+
`then.timeout` is a small integer (seconds); and the `runCommand` event
|
|
101
|
+
argument is one the `cz-kiro-adapter` shim recognizes.
|
|
102
|
+
|
|
103
|
+
### Known upstream Kiro limits (not fixed here -- tracked upstream)
|
|
104
|
+
|
|
105
|
+
- IDE `preToolUse` delivers `USER_PROMPT={}` (no tool context): kirodotdev/Kiro
|
|
106
|
+
#7375 / #7408 / #7500. Our pre-tool-use IDE hook stays disabled.
|
|
107
|
+
- IDE `postToolUse` delivers `toolArgs: {}` (kirodotdev/Kiro #6188), so
|
|
108
|
+
argument-level post-tool policy is bounded on the IDE surface regardless of
|
|
109
|
+
schema correctness.
|
|
110
|
+
|
|
3
111
|
## 1.8.0 -- 2026-06-15 (Kiro, epic gh#877)
|
|
4
112
|
|
|
5
113
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup.
|
|
5
5
|
Project-URL: Homepage, https://controlzero.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.controlzero.ai
|
|
@@ -130,6 +130,7 @@ Templates available (`controlzero init -t <name>`):
|
|
|
130
130
|
- `codex-cli` — Codex CLI hook starter
|
|
131
131
|
- `gemini-cli` — Gemini CLI hook starter
|
|
132
132
|
- `kiro` — Kiro (AWS IDE) hook starter (Beta)
|
|
133
|
+
- `antigravity` — Google Antigravity (IDE + `agy` CLI) hook starter (Beta)
|
|
133
134
|
|
|
134
135
|
## Loading a policy
|
|
135
136
|
|
|
@@ -76,6 +76,7 @@ Templates available (`controlzero init -t <name>`):
|
|
|
76
76
|
- `codex-cli` — Codex CLI hook starter
|
|
77
77
|
- `gemini-cli` — Gemini CLI hook starter
|
|
78
78
|
- `kiro` — Kiro (AWS IDE) hook starter (Beta)
|
|
79
|
+
- `antigravity` — Google Antigravity (IDE + `agy` CLI) hook starter (Beta)
|
|
79
80
|
|
|
80
81
|
## Loading a policy
|
|
81
82
|
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Google Antigravity (agy) external file-based hook adapter.
|
|
2
|
+
|
|
3
|
+
Antigravity's EXTERNAL hook contract (the one wired through ``hooks.json``
|
|
4
|
+
read by the IDE and the ``agy`` CLI) is distinct from the in-process
|
|
5
|
+
Python-SDK hook *classes* (``PreToolCallDecideHook`` / ``PostToolCallHook``,
|
|
6
|
+
registered via ``HookRunner``). Control Zero hooks via the FILE contract, so
|
|
7
|
+
this adapter speaks that protocol -- not the SDK class names.
|
|
8
|
+
|
|
9
|
+
External contract (verified against antigravity.google/docs/hooks and four
|
|
10
|
+
real integrators -- antigravity-cli#49, cmux#5358, munder-difflin#52/#54,
|
|
11
|
+
danicat.dev):
|
|
12
|
+
|
|
13
|
+
* Events: ``PreToolUse`` (the deciding gate -- CAN deny), ``PostToolUse``
|
|
14
|
+
(observe only), plus ``PreInvocation`` / ``PostInvocation`` / ``Stop``
|
|
15
|
+
(model-call lifecycle; ``PreInvocation`` cannot deny). Control Zero's
|
|
16
|
+
governance gate is ``PreToolUse``; the post scan is ``PostToolUse``.
|
|
17
|
+
There is NO prompt-submit / UserPromptSubmit event.
|
|
18
|
+
* Delivery: stdin = JSON in, stdout = JSON out, camelCase. NOT exit-code
|
|
19
|
+
based. (A crashing / non-zero / timed-out hook is read as a deny --
|
|
20
|
+
fail-closed -- so the renderer must never raise.)
|
|
21
|
+
* PreToolUse INPUT: ``{toolCall:{name,args}, stepIdx, conversationId,
|
|
22
|
+
workspacePaths[], transcriptPath}`` (``artifactDirectoryPath`` may also
|
|
23
|
+
appear). ``normalize_payload`` flattens ``toolCall`` -> ``tool_name`` /
|
|
24
|
+
``tool_input`` so the shared extractor + globs work unchanged.
|
|
25
|
+
* PreToolUse OUTPUT: ``{"decision": "allow"|"deny"|"ask", "reason": "..."}``.
|
|
26
|
+
CRITICAL FAIL-CLOSED LANDMINE (cmux#5358): an empty / decision-less object
|
|
27
|
+
``{}`` on stdout is read by agy as ``invalid_args`` and DENIES the tool.
|
|
28
|
+
We therefore ALWAYS emit an explicit ``{"decision": ...}`` -- the allow
|
|
29
|
+
path is ``{"decision":"allow"}``, never silence-that-still-prints-{}.
|
|
30
|
+
|
|
31
|
+
HITL mapping: a Control Zero approval gate maps to ``ask`` by default. A
|
|
32
|
+
plain ``ask`` CAN be auto-satisfied by a cached "Always Allow" (so it is not
|
|
33
|
+
a guaranteed prompt); some agy builds also accept ``force_ask`` (always
|
|
34
|
+
prompts). Because ``force_ask`` is not confirmed across all builds and an
|
|
35
|
+
unrecognized decision risks the same ``invalid_args`` -> deny trap, the HITL
|
|
36
|
+
decision token is configurable via ``CZ_ANTIGRAVITY_HITL_DECISION`` (default
|
|
37
|
+
``ask``; set to ``force_ask`` once verified against your pinned agy, or
|
|
38
|
+
``deny`` for a hard block-with-reason).
|
|
39
|
+
|
|
40
|
+
Subagent tool calls fire the parent ``PreToolUse`` hook, so one adapter
|
|
41
|
+
governs parent + subagents.
|
|
42
|
+
|
|
43
|
+
Reference (pinned): see docs/integrations/antigravity-hook-payloads.md
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
from __future__ import annotations
|
|
47
|
+
|
|
48
|
+
import os
|
|
49
|
+
from typing import Mapping
|
|
50
|
+
|
|
51
|
+
from controlzero.cli.hosts.base import CZDecision, HostAdapter
|
|
52
|
+
|
|
53
|
+
# File-based EVENT names (NOT the in-process SDK class names). PreToolUse is
|
|
54
|
+
# the deciding gate; PostToolUse is observe-only.
|
|
55
|
+
_ANTIGRAVITY_HOOK_EVENTS = frozenset(
|
|
56
|
+
{"PreToolUse", "PostToolUse", "PreInvocation", "PostInvocation", "Stop"}
|
|
57
|
+
)
|
|
58
|
+
# Events SAFE to claim by name alone. PreToolUse / PostToolUse / Stop collide
|
|
59
|
+
# with Claude Code's PascalCase event names, so a payload carrying ONLY one of
|
|
60
|
+
# those (no toolCall envelope, no env marker) is ambiguous and must fall to
|
|
61
|
+
# Claude Code (which sits ahead in the registry). PreInvocation / PostInvocation
|
|
62
|
+
# are Antigravity-distinct -- no other host emits them -- so they are safe
|
|
63
|
+
# disambiguators. Antigravity's unambiguous signal is the ``toolCall`` envelope,
|
|
64
|
+
# which the claim below keys on first.
|
|
65
|
+
_ANTIGRAVITY_CLAIM_EVENTS = frozenset({"PreInvocation", "PostInvocation"})
|
|
66
|
+
_CLIENT_ALIASES = frozenset({"antigravity", "agy", "antigravity-cli"})
|
|
67
|
+
_ENV_PREFIXES = ("ANTIGRAVITY_", "AGY_")
|
|
68
|
+
|
|
69
|
+
# Source-confirmed decision tokens for PreToolUse output.
|
|
70
|
+
_VALID_DECISIONS = frozenset({"allow", "deny", "ask", "force_ask"})
|
|
71
|
+
_DEFAULT_HITL_DECISION = "ask"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _resolve_hitl_decision(env: Mapping[str, str] | None = None) -> str:
|
|
75
|
+
"""Resolve the decision token used for a HITL approval gate.
|
|
76
|
+
|
|
77
|
+
Default ``ask`` (source-confirmed valid on every agy build). Operators
|
|
78
|
+
can opt into ``force_ask`` (guaranteed prompt) or ``deny`` once verified
|
|
79
|
+
against their pinned agy via ``CZ_ANTIGRAVITY_HITL_DECISION``. An
|
|
80
|
+
unrecognized value falls back to ``ask`` so we never emit a token that
|
|
81
|
+
could trip the ``invalid_args`` -> deny trap.
|
|
82
|
+
"""
|
|
83
|
+
src = env if env is not None else os.environ
|
|
84
|
+
raw = (src.get("CZ_ANTIGRAVITY_HITL_DECISION") or "").strip().lower()
|
|
85
|
+
return raw if raw in _VALID_DECISIONS else _DEFAULT_HITL_DECISION
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class AntigravityAdapter(HostAdapter):
|
|
89
|
+
name = "antigravity"
|
|
90
|
+
canonical_source = "antigravity"
|
|
91
|
+
# Antigravity decides from the stdout JSON, NOT the exit code. A non-zero
|
|
92
|
+
# exit is read as a fail-closed deny that would override an ``ask`` /
|
|
93
|
+
# ``allow`` verdict (turning a HITL prompt into a hard block), so the
|
|
94
|
+
# hook-check command must exit 0 for this host and let the JSON decide.
|
|
95
|
+
decision_via_exit_code = False
|
|
96
|
+
|
|
97
|
+
def claim(self, payload: dict, env: Mapping[str, str]) -> bool:
|
|
98
|
+
if (env.get("CONTROLZERO_CLIENT") or "").strip().lower() in _CLIENT_ALIASES:
|
|
99
|
+
return True
|
|
100
|
+
for key in env:
|
|
101
|
+
if key.upper().startswith(_ENV_PREFIXES):
|
|
102
|
+
return True
|
|
103
|
+
if isinstance(payload, dict):
|
|
104
|
+
# Unambiguous Antigravity signal: the toolCall envelope.
|
|
105
|
+
if "toolCall" in payload:
|
|
106
|
+
return True
|
|
107
|
+
# Only the Antigravity-distinct lifecycle events are safe to claim
|
|
108
|
+
# by name; PreToolUse / PostToolUse / Stop collide with Claude Code
|
|
109
|
+
# and are disambiguated by toolCall / env instead.
|
|
110
|
+
if payload.get("hook_event_name") in _ANTIGRAVITY_CLAIM_EVENTS:
|
|
111
|
+
return True
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
def normalize_payload(self, payload: dict) -> dict:
|
|
115
|
+
if not isinstance(payload, dict):
|
|
116
|
+
return payload
|
|
117
|
+
tool_call = payload.get("toolCall")
|
|
118
|
+
if not isinstance(tool_call, dict):
|
|
119
|
+
return payload
|
|
120
|
+
normalized = dict(payload) # preserves conversationId / workspacePaths
|
|
121
|
+
name = tool_call.get("name")
|
|
122
|
+
args = tool_call.get("args")
|
|
123
|
+
if name is not None and not normalized.get("tool_name"):
|
|
124
|
+
normalized["tool_name"] = name
|
|
125
|
+
if args is not None and not normalized.get("tool_input"):
|
|
126
|
+
normalized["tool_input"] = args if isinstance(args, dict) else {}
|
|
127
|
+
return normalized
|
|
128
|
+
|
|
129
|
+
def render(self, decision: CZDecision) -> dict:
|
|
130
|
+
"""Render to Antigravity's PreToolUse stdout-JSON contract.
|
|
131
|
+
|
|
132
|
+
ALWAYS emits an explicit ``decision`` key. An empty / decision-less
|
|
133
|
+
object would be read by agy as ``invalid_args`` and DENY the tool
|
|
134
|
+
(cmux#5358), so silence-that-still-prints-{} is never produced here.
|
|
135
|
+
camelCase per the external contract; Control Zero diagnostic
|
|
136
|
+
metadata is namespaced under ``controlzero`` so it cannot collide
|
|
137
|
+
with agy's reserved output keys.
|
|
138
|
+
"""
|
|
139
|
+
if decision.is_hitl:
|
|
140
|
+
verdict = _resolve_hitl_decision()
|
|
141
|
+
elif decision.is_deny:
|
|
142
|
+
verdict = "deny"
|
|
143
|
+
else:
|
|
144
|
+
verdict = "allow"
|
|
145
|
+
|
|
146
|
+
envelope: dict = {
|
|
147
|
+
"decision": verdict,
|
|
148
|
+
"reason": decision.reason,
|
|
149
|
+
# Diagnostic metadata, namespaced so it never shadows a reserved
|
|
150
|
+
# agy key (decision / reason / permissionOverrides).
|
|
151
|
+
"controlzero": {
|
|
152
|
+
"reasonCode": decision.reason_code,
|
|
153
|
+
"tool": decision.tool,
|
|
154
|
+
"extractedMethod": decision.method,
|
|
155
|
+
"action": decision.extracted_action,
|
|
156
|
+
"actionSemanticClass": decision.semantic_class,
|
|
157
|
+
"tamperDetected": decision.tamper_detected,
|
|
158
|
+
"auditChainBroken": decision.audit_chain_broken,
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
return envelope
|
|
@@ -127,6 +127,15 @@ class HostAdapter:
|
|
|
127
127
|
#: Default is ``"unknown"`` -- subclasses MUST override.
|
|
128
128
|
canonical_source: str = "unknown"
|
|
129
129
|
|
|
130
|
+
#: Whether the host signals a deny via the PROCESS EXIT CODE
|
|
131
|
+
#: (non-zero = block, the Claude Code / Gemini / Codex / Kiro
|
|
132
|
+
#: convention) or purely via the stdout JSON ``decision`` field.
|
|
133
|
+
#: Antigravity is stdout-JSON based: a non-zero exit is read as a
|
|
134
|
+
#: fail-closed deny that would OVERRIDE an ``ask``/``allow`` JSON
|
|
135
|
+
#: verdict, so its hook must exit 0 and let the JSON decide. The
|
|
136
|
+
#: hook-check command consults this to pick the deny/HITL exit code.
|
|
137
|
+
decision_via_exit_code: bool = True
|
|
138
|
+
|
|
130
139
|
# -- detection --------------------------------------------------
|
|
131
140
|
|
|
132
141
|
def claim(self, payload: dict, env: Mapping[str, str]) -> bool:
|
|
@@ -61,6 +61,20 @@ class ClaudeCodeAdapter(HostAdapter):
|
|
|
61
61
|
):
|
|
62
62
|
return True
|
|
63
63
|
|
|
64
|
+
# Disambiguation: Google Antigravity's external hooks use the SAME
|
|
65
|
+
# PascalCase event names (PreToolUse / PostToolUse / Stop) but pack
|
|
66
|
+
# the call under a top-level ``toolCall`` envelope, which Claude Code
|
|
67
|
+
# never sends. If ``toolCall`` is present this is an Antigravity
|
|
68
|
+
# payload -- do NOT claim it (let the AntigravityAdapter claim it).
|
|
69
|
+
# This runs BEFORE the Anthropic env-hint check: an Antigravity hook
|
|
70
|
+
# subprocess can inherit CLAUDECODE / CLAUDE_CODE from a Claude-spawned
|
|
71
|
+
# parent shell, and claiming on that stale env hint would emit Claude's
|
|
72
|
+
# approve/block tokens -- invalid for Antigravity, read as a deny.
|
|
73
|
+
# Only the explicit CONTROLZERO_CLIENT=claude_code override above
|
|
74
|
+
# outranks the toolCall signal.
|
|
75
|
+
if isinstance(payload, dict) and "toolCall" in payload:
|
|
76
|
+
return False
|
|
77
|
+
|
|
64
78
|
# Env-var hint: any of the Anthropic-family vars present.
|
|
65
79
|
for hint in self._ENV_HINTS:
|
|
66
80
|
if env.get(hint):
|
|
@@ -7,8 +7,9 @@ on stdin like the Kiro CLI. This shim normalises that into the snake_case JSON
|
|
|
7
7
|
shape ``controlzero hook-check`` consumes, marks the surface
|
|
8
8
|
(``CZ_KIRO_SURFACE=ide`` so KiroIDEAdapter claims it), and pipes it through.
|
|
9
9
|
|
|
10
|
-
Wire it as the IDE's preToolUse /
|
|
11
|
-
|
|
10
|
+
Wire it as the IDE's preToolUse / promptSubmit / fileEdited hook command (the
|
|
11
|
+
IDE `when.type` event names -- note prompt-submit is ``promptSubmit`` on the
|
|
12
|
+
IDE surface, NOT the Kiro CLI's ``userPromptSubmit``), e.g.::
|
|
12
13
|
|
|
13
14
|
cz-kiro-adapter preToolUse
|
|
14
15
|
|
|
@@ -37,6 +38,12 @@ import subprocess
|
|
|
37
38
|
import sys
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
# Kiro IDE file events. These are a post-write OBSERVATION surface (scan the
|
|
42
|
+
# saved file / audit it), never a gate, so the adapter must not fail closed on
|
|
43
|
+
# them -- a file save can't be blocked by missing context.
|
|
44
|
+
_FILE_EVENTS = frozenset({"fileEdited", "fileCreated", "fileDeleted"})
|
|
45
|
+
|
|
46
|
+
|
|
40
47
|
def _event_from_argv(argv: list[str]) -> str:
|
|
41
48
|
# First non-flag arg is the event name; default to preToolUse.
|
|
42
49
|
for a in argv[1:]:
|
|
@@ -66,6 +73,25 @@ def _normalize(event: str, raw: str) -> dict | None:
|
|
|
66
73
|
base["tool_input"] = {"prompt": raw}
|
|
67
74
|
return base
|
|
68
75
|
|
|
76
|
+
if event in _FILE_EVENTS:
|
|
77
|
+
# File events are a POST-WRITE observation, not a gate: a save must
|
|
78
|
+
# never be blocked by missing context, so this branch NEVER returns
|
|
79
|
+
# None (no fail-closed). Kiro IDE does not document a guaranteed
|
|
80
|
+
# structured payload for a file-event runCommand (USER_PROMPT may be a
|
|
81
|
+
# path, a JSON object, or empty), so we synthesize a write-class tool
|
|
82
|
+
# call from whatever arrived and let hook-check scan/audit it.
|
|
83
|
+
base["tool_name"] = "fileSave"
|
|
84
|
+
payload: dict = {}
|
|
85
|
+
text = (raw or "").strip()
|
|
86
|
+
if text:
|
|
87
|
+
try:
|
|
88
|
+
parsed = json.loads(text)
|
|
89
|
+
payload = parsed if isinstance(parsed, dict) else {"raw": text}
|
|
90
|
+
except json.JSONDecodeError:
|
|
91
|
+
payload = {"file_path": text} # most likely a bare path
|
|
92
|
+
base["tool_input"] = payload
|
|
93
|
+
return base
|
|
94
|
+
|
|
69
95
|
raw = (raw or "").strip()
|
|
70
96
|
if not raw:
|
|
71
97
|
return None
|
|
@@ -122,6 +148,20 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
122
148
|
# controlzero not on PATH: never brick the IDE.
|
|
123
149
|
print("controlzero(kiro): controlzero CLI not found; allowing", file=sys.stderr)
|
|
124
150
|
return 0
|
|
151
|
+
|
|
152
|
+
if event in _FILE_EVENTS:
|
|
153
|
+
# OBSERVE-ONLY: a file event is post-write -- the file is already
|
|
154
|
+
# saved, so blocking is pointless and only disrupts the editor. We
|
|
155
|
+
# still run hook-check above for the audit row + secret scan, but a
|
|
156
|
+
# deny is surfaced as a warning and the save is allowed (exit 0).
|
|
157
|
+
if proc.returncode == 2:
|
|
158
|
+
print(
|
|
159
|
+
"controlzero(kiro): file-save policy flagged this write "
|
|
160
|
+
"(post-write, observe-only; not blocking the save)",
|
|
161
|
+
file=sys.stderr,
|
|
162
|
+
)
|
|
163
|
+
return 0
|
|
164
|
+
|
|
125
165
|
return proc.returncode
|
|
126
166
|
|
|
127
167
|
|