controlzero 1.8.1__tar.gz → 1.8.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. {controlzero-1.8.1 → controlzero-1.8.2}/CHANGELOG.md +56 -0
  2. {controlzero-1.8.1 → controlzero-1.8.2}/PKG-INFO +1 -1
  3. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/__init__.py +1 -1
  4. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/kiro_adapter.py +42 -2
  5. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/main.py +2 -2
  6. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +2 -2
  7. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +2 -2
  8. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +3 -3
  9. {controlzero-1.8.1 → controlzero-1.8.2}/pyproject.toml +1 -1
  10. controlzero-1.8.2/tests/test_kiro_hook_templates.py +324 -0
  11. {controlzero-1.8.1 → controlzero-1.8.2}/.gitignore +0 -0
  12. {controlzero-1.8.1 → controlzero-1.8.2}/Dockerfile.test +0 -0
  13. {controlzero-1.8.1 → controlzero-1.8.2}/LICENSE +0 -0
  14. {controlzero-1.8.1 → controlzero-1.8.2}/README.md +0 -0
  15. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/__init__.py +0 -0
  16. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/action_aliases.py +0 -0
  17. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/action_validator.py +0 -0
  18. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/bundle.py +0 -0
  19. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/credential_hook.py +0 -0
  20. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/credential_scanner.py +0 -0
  21. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/credentials_data/__init__.py +0 -0
  22. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
  23. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/dlp_scanner.py +0 -0
  24. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/enforcer.py +0 -0
  25. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/hook_extractors.py +0 -0
  26. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/tool_extractors.json +0 -0
  27. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/_internal/types.py +0 -0
  28. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/audit_local.py +0 -0
  29. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/audit_remote.py +0 -0
  30. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/canonical.py +0 -0
  31. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/__init__.py +0 -0
  32. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/_secrets.py +0 -0
  33. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/console.py +0 -0
  34. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/debug_bundle.py +0 -0
  35. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/doctor.py +0 -0
  36. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/__init__.py +0 -0
  37. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/antigravity.py +0 -0
  38. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/base.py +0 -0
  39. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/claude_code.py +0 -0
  40. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/codex_cli.py +0 -0
  41. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/gemini_cli.py +0 -0
  42. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/kiro.py +0 -0
  43. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/hosts/unknown.py +0 -0
  44. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/migrate.py +0 -0
  45. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/spool_cmd.py +0 -0
  46. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/telemetry_consent.py +0 -0
  47. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/autogen.yaml +0 -0
  48. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/claude-code.yaml +0 -0
  49. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/codex-cli.yaml +0 -0
  50. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/cost-cap.yaml +0 -0
  51. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/crewai.yaml +0 -0
  52. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/cursor.yaml +0 -0
  53. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/gemini-cli.yaml +0 -0
  54. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/generic.yaml +0 -0
  55. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
  56. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/langchain.yaml +0 -0
  57. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/mcp.yaml +0 -0
  58. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/cli/templates/rag.yaml +0 -0
  59. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/client.py +0 -0
  60. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/device.py +0 -0
  61. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/enrollment.py +0 -0
  62. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/error_codes.py +0 -0
  63. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/error_codes.yaml +0 -0
  64. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/errors.py +0 -0
  65. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/__init__.py +0 -0
  66. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/grant_protocol.py +0 -0
  67. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/mock.py +0 -0
  68. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/pending_approval.py +0 -0
  69. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/secret_leak_guard.py +0 -0
  70. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hitl/status.py +0 -0
  71. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hooks/__init__.py +0 -0
  72. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hooks/tool_output_handler.py +0 -0
  73. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/hosted_policy.py +0 -0
  74. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/__init__.py +0 -0
  75. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/anthropic.py +0 -0
  76. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/autogen.py +0 -0
  77. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/braintrust.py +0 -0
  78. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/crewai/__init__.py +0 -0
  79. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/crewai/agent.py +0 -0
  80. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/crewai/crew.py +0 -0
  81. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/crewai/task.py +0 -0
  82. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/crewai/tool.py +0 -0
  83. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/google.py +0 -0
  84. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/google_adk/__init__.py +0 -0
  85. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/google_adk/agent.py +0 -0
  86. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/google_adk/tool.py +0 -0
  87. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/__init__.py +0 -0
  88. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/agent.py +0 -0
  89. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/callbacks.py +0 -0
  90. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/chain.py +0 -0
  91. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/graph.py +0 -0
  92. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/modern.py +0 -0
  93. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langchain/tool.py +0 -0
  94. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/langfuse.py +0 -0
  95. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/litellm.py +0 -0
  96. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/openai.py +0 -0
  97. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/pydantic_ai.py +0 -0
  98. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/integrations/vercel_ai.py +0 -0
  99. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/layout_migration.py +0 -0
  100. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/policy_loader.py +0 -0
  101. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/__init__.py +0 -0
  102. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_compress.py +0 -0
  103. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_constants.py +0 -0
  104. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_crc32c.py +0 -0
  105. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_crypto.py +0 -0
  106. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_frame.py +0 -0
  107. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_metrics.py +0 -0
  108. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_spool.py +0 -0
  109. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_state.py +0 -0
  110. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/_uploader.py +0 -0
  111. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/spool/cz-audit-v1.dict +0 -0
  112. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/tamper.py +0 -0
  113. {controlzero-1.8.1 → controlzero-1.8.2}/controlzero/tracecontext.py +0 -0
  114. {controlzero-1.8.1 → controlzero-1.8.2}/examples/hello_world.py +0 -0
  115. {controlzero-1.8.1 → controlzero-1.8.2}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
  116. {controlzero-1.8.1 → controlzero-1.8.2}/tests/conftest.py +0 -0
  117. {controlzero-1.8.1 → controlzero-1.8.2}/tests/integrations/__init__.py +0 -0
  118. {controlzero-1.8.1 → controlzero-1.8.2}/tests/integrations/test_google.py +0 -0
  119. {controlzero-1.8.1 → controlzero-1.8.2}/tests/parity/action_aliases.json +0 -0
  120. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/__init__.py +0 -0
  121. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/conftest.py +0 -0
  122. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_cli.py +0 -0
  123. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_concurrency.py +0 -0
  124. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_conformance.py +0 -0
  125. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_core.py +0 -0
  126. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_crash.py +0 -0
  127. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_diskfull.py +0 -0
  128. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_sink_wiring.py +0 -0
  129. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_transcript_localack.py +0 -0
  130. {controlzero-1.8.1 → controlzero-1.8.2}/tests/spool/test_spool_uploader.py +0 -0
  131. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_action_aliases.py +0 -0
  132. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_action_canonicalization.py +0 -0
  133. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_action_validator_t86.py +0 -0
  134. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_agent_name_env.py +0 -0
  135. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_api_key_mask.py +0 -0
  136. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_audit_remote.py +0 -0
  137. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_audit_remote_sdk_version.py +0 -0
  138. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_audit_sink_isolation.py +0 -0
  139. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_bundle_parser.py +0 -0
  140. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_bundle_translate.py +0 -0
  141. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_canonical_phase1a.py +0 -0
  142. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_carve_out.py +0 -0
  143. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_debug_bundle.py +0 -0
  144. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_extractor_integration.py +0 -0
  145. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_hook.py +0 -0
  146. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_hosted_refresh.py +0 -0
  147. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_init.py +0 -0
  148. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_init_templates.py +0 -0
  149. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_tail.py +0 -0
  150. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_test.py +0 -0
  151. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_cli_validate.py +0 -0
  152. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_coding_agent_hooks.py +0 -0
  153. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_conditions.py +0 -0
  154. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_conformance.py +0 -0
  155. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_console.py +0 -0
  156. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_credential_hook.py +0 -0
  157. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_default_action.py +0 -0
  158. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_device.py +0 -0
  159. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_dlp_scanner.py +0 -0
  160. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_doctor.py +0 -0
  161. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_engine_version_consistency.py +0 -0
  162. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_enrollment.py +0 -0
  163. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_env_dump_438.py +0 -0
  164. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_error_codes.py +0 -0
  165. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_errors_e_codes.py +0 -0
  166. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_fail_closed_eval.py +0 -0
  167. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_glob_matching.py +0 -0
  168. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_5d_email_install.py +0 -0
  169. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_cli_flag.py +0 -0
  170. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_exceptions.py +0 -0
  171. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
  172. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_mock_backend.py +0 -0
  173. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_pending_approval.py +0 -0
  174. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_request_approval.py +0 -0
  175. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
  176. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_6a_wait.py +0 -0
  177. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_conformance.py +0 -0
  178. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_phase2b_protocol.py +0 -0
  179. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_reason_codes.py +0 -0
  180. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hitl_validator_keys.py +0 -0
  181. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hook_extractors.py +0 -0
  182. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hosted_policy_e2e.py +0 -0
  183. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hosts_adapter.py +0 -0
  184. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hybrid_mode_strict.py +0 -0
  185. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_hybrid_mode_warn.py +0 -0
  186. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_install_hook_command.py +0 -0
  187. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_install_hooks.py +0 -0
  188. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_kiro_adapter.py +0 -0
  189. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_kiro_install.py +0 -0
  190. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_layout_migration_t101.py +0 -0
  191. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_layout_parity_t102.py +0 -0
  192. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_local_mode_dict.py +0 -0
  193. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_local_mode_file_json.py +0 -0
  194. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_local_mode_file_yaml.py +0 -0
  195. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_log_fallback_stderr.py +0 -0
  196. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_log_options_ignored_hosted.py +0 -0
  197. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_log_rotation.py +0 -0
  198. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_migrate.py +0 -0
  199. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_min_sdk_version_gate.py +0 -0
  200. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_multi_client_per_project_175.py +0 -0
  201. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_no_policy_no_key.py +0 -0
  202. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_package_rename_shim.py +0 -0
  203. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_policy_engine_version_phase1b.py +0 -0
  204. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_policy_freshness.py +0 -0
  205. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_policy_settings.py +0 -0
  206. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_policy_source_audit.py +0 -0
  207. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_quarantine.py +0 -0
  208. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_reason_code.py +0 -0
  209. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_refresh.py +0 -0
  210. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_secrets.py +0 -0
  211. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_sql_semantic_class.py +0 -0
  212. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_synthetic_policy_id_t79.py +0 -0
  213. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_t103_precedence.py +0 -0
  214. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_t104_cache_gc.py +0 -0
  215. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_t108_local_override_audit.py +0 -0
  216. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_t96_single_audit_log.py +0 -0
  217. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_t99_install_prefetch_bundle.py +0 -0
  218. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_tamper.py +0 -0
  219. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_tamper_behavior.py +0 -0
  220. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_tamper_hook.py +0 -0
  221. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_telemetry_consent.py +0 -0
  222. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_tracecontext.py +0 -0
  223. {controlzero-1.8.1 → controlzero-1.8.2}/tests/test_unsafe_int_boundary.py +0 -0
  224. {controlzero-1.8.1 → controlzero-1.8.2}/tools/cz-kiro-adapter +0 -0
@@ -1,5 +1,61 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.8.2 -- 2026-06-15 (Kiro IDE hook contract hotfix, epic gh#877)
4
+
5
+ ### Fixed
6
+
7
+ The shipped Kiro IDE `.kiro.hook` templates used several **Kiro CLI** schema
8
+ conventions instead of the **Kiro IDE** ones (the two hook systems have
9
+ different event names and field keys). The most visible symptom was a
10
+ customer-reported P0; auditing the templates against Kiro's IDE schema
11
+ surfaced three more latent defects. All four are fixed, and a new regression
12
+ test (`tests/test_kiro_hook_templates.py`) pins each so they cannot ship again.
13
+
14
+ - **(P0) Prompt-submit hook rejected by the IDE.** `ide-prompt-submit.kiro.hook`
15
+ declared `"when": {"type": "userPromptSubmit"}`. `userPromptSubmit` is the
16
+ Kiro **CLI** event name; the IDE event is **`promptSubmit`**. The invalid
17
+ value made the IDE reject the entire hook file ("Hook file has invalid data
18
+ structure"), so the only active data-carrying IDE hook (prompt-submit, while
19
+ preToolUse ships disabled) never loaded and the IDE integration silently did
20
+ nothing. Now `promptSubmit`, with the matching `cz-kiro-adapter promptSubmit`
21
+ command. (The shim already accepts both spellings, so no behavior change.)
22
+
23
+ - **Pre-tool-use tool filter used the wrong key.** `ide-pre-tool-use.kiro.hook`
24
+ filtered tools with `"patterns": ["*"]`; the Kiro IDE tool filter key for
25
+ `preToolUse`/`postToolUse` is **`toolTypes`** (array). Now `"toolTypes":
26
+ ["*"]`, so the hook is schema-correct if/when it is enabled (it still ships
27
+ `enabled: false` pending the upstream empty-payload bug).
28
+
29
+ - **File-save hook passed a tool-event arg + could block saves.**
30
+ `ide-file-save.kiro.hook` ran `cz-kiro-adapter postToolUse`, but a
31
+ `fileEdited` runCommand does not deliver tool context; the adapter would then
32
+ fail closed (block) on the empty payload -- i.e. it could block file saves.
33
+ The command is now `cz-kiro-adapter fileEdited`, and the shim treats file
34
+ events as a post-write **observation**: it synthesizes a `fileSave` tool
35
+ call from whatever `USER_PROMPT` carries, still runs `hook-check` for the
36
+ audit row + secret scan, but **always exits 0** on a file event -- a deny is
37
+ surfaced on stderr as a warning rather than blocking. The file is already
38
+ written, so blocking is pointless; only tool events (preToolUse) gate.
39
+ `when.type` stays the valid `fileEdited` with `patterns`.
40
+
41
+ - **Timeouts were ~83 minutes, not 5 seconds.** All three templates used
42
+ `"timeout": 5000`. Kiro IDE `then.timeout` is in **seconds** (default 60,
43
+ `0` disables), so `5000` meant ~83 minutes. Now `5` (seconds) on all three.
44
+
45
+ The regression test parses every shipped `*.kiro.hook` and asserts: valid
46
+ JSON; `when.type` in Kiro's 11-value IDE event set; tool events use
47
+ `toolTypes` (not `patterns`) and file events use `patterns` (not `toolTypes`);
48
+ `then.timeout` is a small integer (seconds); and the `runCommand` event
49
+ argument is one the `cz-kiro-adapter` shim recognizes.
50
+
51
+ ### Known upstream Kiro limits (not fixed here -- tracked upstream)
52
+
53
+ - IDE `preToolUse` delivers `USER_PROMPT={}` (no tool context): kirodotdev/Kiro
54
+ #7375 / #7408 / #7500. Our pre-tool-use IDE hook stays disabled.
55
+ - IDE `postToolUse` delivers `toolArgs: {}` (kirodotdev/Kiro #6188), so
56
+ argument-level post-tool policy is bounded on the IDE surface regardless of
57
+ schema correctness.
58
+
3
59
  ## 1.8.0 -- 2026-06-15 (Kiro, epic gh#877)
4
60
 
5
61
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: controlzero
3
- Version: 1.8.1
3
+ Version: 1.8.2
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
@@ -40,7 +40,7 @@ from controlzero.hitl.grant_protocol import (
40
40
  )
41
41
  from controlzero.policy_loader import load_policy
42
42
 
43
- __version__ = "1.8.1"
43
+ __version__ = "1.8.2"
44
44
 
45
45
  __all__ = [
46
46
  "Client",
@@ -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 / postToolUse / userPromptSubmit hook command,
11
- e.g.::
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
 
@@ -2699,8 +2699,8 @@ def uninstall_codex_cli(config: Optional[str]):
2699
2699
  # is the block point; Prompt Submit scans the prompt; File Save scans writes.
2700
2700
  _KIRO_IDE_HOOKS = (
2701
2701
  ("preToolUse", "controlzero-pre-tool-use", "ide-pre-tool-use.kiro.hook"),
2702
- ("userPromptSubmit", "controlzero-prompt-submit", "ide-prompt-submit.kiro.hook"),
2703
- ("fileSave", "controlzero-file-save", "ide-file-save.kiro.hook"),
2702
+ ("promptSubmit", "controlzero-prompt-submit", "ide-prompt-submit.kiro.hook"),
2703
+ ("fileEdited", "controlzero-file-save", "ide-file-save.kiro.hook"),
2704
2704
  )
2705
2705
 
2706
2706
  # Kiro CLI hook events: command is `controlzero hook-check` directly. Pre/post
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "then": {
11
11
  "type": "runCommand",
12
- "command": "cz-kiro-adapter postToolUse",
13
- "timeout": 5000
12
+ "command": "cz-kiro-adapter fileEdited",
13
+ "timeout": 5
14
14
  }
15
15
  }
@@ -5,11 +5,11 @@
5
5
  "version": "1",
6
6
  "when": {
7
7
  "type": "preToolUse",
8
- "patterns": ["*"]
8
+ "toolTypes": ["*"]
9
9
  },
10
10
  "then": {
11
11
  "type": "runCommand",
12
12
  "command": "cz-kiro-adapter preToolUse",
13
- "timeout": 5000
13
+ "timeout": 5
14
14
  }
15
15
  }
@@ -4,11 +4,11 @@
4
4
  "description": "Control Zero governance: scan each user prompt submitted to Kiro against your policy. Carries data even where the Pre Tool Use payload is bounded by the upstream empty-payload bug.",
5
5
  "version": "1",
6
6
  "when": {
7
- "type": "userPromptSubmit"
7
+ "type": "promptSubmit"
8
8
  },
9
9
  "then": {
10
10
  "type": "runCommand",
11
- "command": "cz-kiro-adapter userPromptSubmit",
12
- "timeout": 5000
11
+ "command": "cz-kiro-adapter promptSubmit",
12
+ "timeout": 5
13
13
  }
14
14
  }
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "controlzero"
7
- version = "1.8.1"
7
+ version = "1.8.2"
8
8
  description = "AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup."
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -0,0 +1,324 @@
1
+ """Regression guard for the shipped Kiro IDE hook templates (epic gh#877).
2
+
3
+ Customer P0 (1.8.0): `ide-prompt-submit.kiro.hook` shipped
4
+ `"when": {"type": "userPromptSubmit"}`. `userPromptSubmit` is NOT a valid
5
+ Kiro IDE hook event -- it is the Kiro *CLI* `hook_event_name` and was copied
6
+ across by mistake. Kiro's IDE hook validator rejects the entire hook file with
7
+ "Hook file has invalid data structure: Hook when.type must be one of: ...",
8
+ so the prompt-submit hook (the only active data-carrying IDE hook while
9
+ preToolUse ships disabled) never loaded and the IDE integration was broken.
10
+
11
+ Schema research (kiro.dev first-party + kirodotdev/Kiro issues #6188/#7375/
12
+ #7408/#7500) surfaced THREE more latent IDE-schema defects beyond the
13
+ prompt-submit event name, all fixed in 1.8.2 and pinned here:
14
+
15
+ A. when.type must be one of the 11 valid Kiro IDE events (the customer bug).
16
+ B. tool events (preToolUse/postToolUse) filter on `toolTypes` (array), NOT
17
+ `patterns`. We had `patterns: ["*"]` on the pre-tool-use hook.
18
+ C. file events (fileEdited/fileCreated/fileDeleted) filter on `patterns`
19
+ (array of globs).
20
+ D. `then.timeout` is SECONDS (default 60, 0 = disabled), so the `5000` we
21
+ shipped meant ~83 minutes, not 5 seconds.
22
+
23
+ Plus the `then.command` (`cz-kiro-adapter <event>`) must name an event the
24
+ shim recognizes.
25
+
26
+ If Kiro adds or renames IDE events/fields upstream, update the constants here
27
+ in lockstep -- that is intentional: this file is the single pin for the IDE
28
+ hook contract.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import json
34
+ from pathlib import Path
35
+
36
+ import pytest
37
+
38
+ from controlzero.cli import kiro_adapter
39
+
40
+ # Kiro IDE hook `when.type` allowed set, verbatim from the IDE validator error:
41
+ # "Hook when.type must be one of: fileEdited, fileCreated, fileDeleted,
42
+ # userTriggered, promptSubmit, agentStop, preToolUse, postToolUse,
43
+ # preTaskExecution, postTaskExecution, sessionStart."
44
+ KIRO_IDE_EVENTS = frozenset(
45
+ {
46
+ "fileEdited",
47
+ "fileCreated",
48
+ "fileDeleted",
49
+ "userTriggered",
50
+ "promptSubmit",
51
+ "agentStop",
52
+ "preToolUse",
53
+ "postToolUse",
54
+ "preTaskExecution",
55
+ "postTaskExecution",
56
+ "sessionStart",
57
+ }
58
+ )
59
+
60
+ # Tool events filter on `toolTypes`; file events filter on `patterns`. (Kiro
61
+ # first-party docs + real committed .kiro.hook files + issues #7408/#7500.)
62
+ TOOL_EVENTS = frozenset({"preToolUse", "postToolUse"})
63
+ FILE_EVENTS = frozenset({"fileEdited", "fileCreated", "fileDeleted"})
64
+
65
+ # then.timeout is in SECONDS (default 60, 0 = disabled). A sane upper bound for
66
+ # a synchronous governance hook; the catastrophic value we shipped was 5000
67
+ # (~83 min). 120s is generous headroom and still rejects the ms-as-seconds bug.
68
+ MAX_TIMEOUT_SECONDS = 120
69
+
70
+ # Events the `cz-kiro-adapter` shim handles end to end. promptSubmit ->
71
+ # raw-prompt branch; file events -> observe-only synthetic fileSave; pre/post
72
+ # tool -> JSON tool-payload branch. userPromptSubmit is kept ONLY as a
73
+ # back-compat alias for the Kiro CLI event name and must never be an IDE
74
+ # when.type or an IDE command arg.
75
+ ADAPTER_RECOGNIZED_EVENTS = frozenset(
76
+ {"preToolUse", "postToolUse", "promptSubmit", "userPromptSubmit"}
77
+ | set(FILE_EVENTS)
78
+ )
79
+
80
+
81
+ def _templates_dir() -> Path:
82
+ return Path(kiro_adapter.__file__).resolve().parent / "templates" / "kiro"
83
+
84
+
85
+ def _shipped_hook_files() -> list[Path]:
86
+ files = sorted(_templates_dir().glob("*.kiro.hook"))
87
+ assert files, "expected at least one shipped *.kiro.hook template"
88
+ return files
89
+
90
+
91
+ _HOOK_FILES = _shipped_hook_files()
92
+ _HOOK_IDS = [p.name for p in _HOOK_FILES]
93
+
94
+
95
+ def _load(hook_path: Path) -> dict:
96
+ return json.loads(hook_path.read_text(encoding="utf-8"))
97
+
98
+
99
+ def _adapter_event_arg(command: str) -> str:
100
+ """Extract the event argument from a `cz-kiro-adapter <event> [flags]`
101
+ runCommand string, mirroring kiro_adapter._event_from_argv (first
102
+ non-flag token after the program name)."""
103
+ tokens = command.split()
104
+ assert tokens, f"empty runCommand: {command!r}"
105
+ assert tokens[0] == "cz-kiro-adapter", (
106
+ f"IDE hook command must invoke the cz-kiro-adapter shim, got {command!r}"
107
+ )
108
+ for tok in tokens[1:]:
109
+ if not tok.startswith("-"):
110
+ return tok
111
+ raise AssertionError(f"no event argument in runCommand: {command!r}")
112
+
113
+
114
+ @pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
115
+ def test_hook_template_is_valid_json(hook_path: Path):
116
+ """Every shipped .kiro.hook must be a parseable JSON object."""
117
+ try:
118
+ data = _load(hook_path)
119
+ except json.JSONDecodeError as e: # pragma: no cover - failure path
120
+ pytest.fail(f"{hook_path.name} is not valid JSON: {e}")
121
+ assert isinstance(data, dict), f"{hook_path.name} must be a JSON object"
122
+
123
+
124
+ @pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
125
+ def test_hook_when_type_is_a_valid_kiro_ide_event(hook_path: Path):
126
+ """FIX A / customer P0: `when.type` MUST be in Kiro's allowed IDE event
127
+ set, or the IDE rejects the whole hook file as invalid."""
128
+ data = _load(hook_path)
129
+ when = data.get("when")
130
+ assert isinstance(when, dict), f"{hook_path.name} missing object `when`"
131
+ when_type = when.get("type")
132
+ assert when_type in KIRO_IDE_EVENTS, (
133
+ f"{hook_path.name}: when.type={when_type!r} is NOT a valid Kiro IDE "
134
+ f"event. Kiro rejects the hook file. Allowed: {sorted(KIRO_IDE_EVENTS)}"
135
+ )
136
+
137
+
138
+ @pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
139
+ def test_tool_events_use_toolTypes_not_patterns(hook_path: Path):
140
+ """FIX B: preToolUse/postToolUse filter on `toolTypes` (array), never
141
+ `patterns`. `patterns` on a tool event is a silent schema error."""
142
+ data = _load(hook_path)
143
+ when = data["when"]
144
+ if when.get("type") not in TOOL_EVENTS:
145
+ pytest.skip("not a tool event")
146
+ assert "patterns" not in when, (
147
+ f"{hook_path.name}: tool event {when['type']!r} must NOT use "
148
+ f"`patterns` -- the tool filter key is `toolTypes`"
149
+ )
150
+ tool_types = when.get("toolTypes")
151
+ assert isinstance(tool_types, list) and tool_types, (
152
+ f"{hook_path.name}: tool event {when['type']!r} must declare a "
153
+ f"non-empty `toolTypes` array, got {tool_types!r}"
154
+ )
155
+
156
+
157
+ @pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
158
+ def test_file_events_use_patterns_not_toolTypes(hook_path: Path):
159
+ """FIX C: file events filter on `patterns` (array of globs), not
160
+ `toolTypes`."""
161
+ data = _load(hook_path)
162
+ when = data["when"]
163
+ if when.get("type") not in FILE_EVENTS:
164
+ pytest.skip("not a file event")
165
+ assert "toolTypes" not in when, (
166
+ f"{hook_path.name}: file event {when['type']!r} must NOT use "
167
+ f"`toolTypes` -- the file filter key is `patterns`"
168
+ )
169
+ patterns = when.get("patterns")
170
+ assert isinstance(patterns, list) and patterns, (
171
+ f"{hook_path.name}: file event {when['type']!r} must declare a "
172
+ f"non-empty `patterns` array, got {patterns!r}"
173
+ )
174
+
175
+
176
+ @pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
177
+ def test_timeout_is_small_integer_seconds(hook_path: Path):
178
+ """FIX D: `then.timeout` is SECONDS (default 60, 0 = disabled). The 1.8.0
179
+ templates shipped 5000 (~83 minutes). Pin it to a small integer."""
180
+ then = _load(hook_path)["then"]
181
+ timeout = then.get("timeout")
182
+ assert isinstance(timeout, int) and not isinstance(timeout, bool), (
183
+ f"{hook_path.name}: then.timeout must be an integer (seconds), "
184
+ f"got {timeout!r}"
185
+ )
186
+ assert 0 <= timeout <= MAX_TIMEOUT_SECONDS, (
187
+ f"{hook_path.name}: then.timeout={timeout} is out of range for a "
188
+ f"seconds value (0..{MAX_TIMEOUT_SECONDS}). 5000 was the ms-as-seconds "
189
+ f"bug (~83 min)."
190
+ )
191
+
192
+
193
+ @pytest.mark.parametrize("hook_path", _HOOK_FILES, ids=_HOOK_IDS)
194
+ def test_hook_runcommand_event_arg_is_recognized(hook_path: Path):
195
+ """The `cz-kiro-adapter <event>` argument must be an event the shim
196
+ recognizes AND a valid Kiro event (so the IDE actually fires it)."""
197
+ then = _load(hook_path)["then"]
198
+ assert then.get("type") == "runCommand", (
199
+ f"{hook_path.name}: then.type must be 'runCommand', got {then.get('type')!r}"
200
+ )
201
+ command = then.get("command")
202
+ assert isinstance(command, str) and command, (
203
+ f"{hook_path.name}: then.command must be a non-empty string"
204
+ )
205
+ event_arg = _adapter_event_arg(command)
206
+ assert event_arg in ADAPTER_RECOGNIZED_EVENTS, (
207
+ f"{hook_path.name}: cz-kiro-adapter event {event_arg!r} is not "
208
+ f"recognized by the shim. Recognized: {sorted(ADAPTER_RECOGNIZED_EVENTS)}"
209
+ )
210
+ # The command arg is the event the IDE fires the hook for, so it must also
211
+ # be a valid Kiro IDE event (rules out the CLI-only userPromptSubmit alias
212
+ # leaking into an IDE template's command).
213
+ assert event_arg in KIRO_IDE_EVENTS, (
214
+ f"{hook_path.name}: cz-kiro-adapter event {event_arg!r} is not a valid "
215
+ f"Kiro IDE event. Allowed: {sorted(KIRO_IDE_EVENTS)}"
216
+ )
217
+
218
+
219
+ # --- per-hook end-to-end behavior pins -------------------------------------
220
+
221
+ def test_prompt_submit_hook_uses_promptSubmit_not_userPromptSubmit():
222
+ """Pin the EXACT customer bug: the prompt-submit template must declare the
223
+ IDE `promptSubmit` event, never the CLI's `userPromptSubmit`, and the shim
224
+ must normalize it into a prompt payload (end-to-end, not just the string)."""
225
+ data = _load(_templates_dir() / "ide-prompt-submit.kiro.hook")
226
+ assert data["when"]["type"] == "promptSubmit", (
227
+ "ide-prompt-submit.kiro.hook when.type must be 'promptSubmit' "
228
+ "(was the invalid 'userPromptSubmit' in 1.8.0 -> 1.8.1)"
229
+ )
230
+ assert _adapter_event_arg(data["then"]["command"]) == "promptSubmit"
231
+ normalized = kiro_adapter._normalize("promptSubmit", "scan this prompt text")
232
+ assert normalized is not None
233
+ assert normalized["tool_name"] == "prompt"
234
+ assert normalized["tool_input"]["prompt"] == "scan this prompt text"
235
+ assert normalized["hook_event_name"] == "promptSubmit"
236
+
237
+
238
+ def test_file_save_hook_observes_and_never_fails_closed():
239
+ """file-save uses the valid `fileEdited` trigger + `patterns`, runs the
240
+ adapter as `fileEdited`, and the shim treats it as observe-only: it must
241
+ NEVER return None (which would fail-closed and block the save), even when
242
+ the IDE delivers no structured payload."""
243
+ data = _load(_templates_dir() / "ide-file-save.kiro.hook")
244
+ assert data["when"]["type"] == "fileEdited"
245
+ assert _adapter_event_arg(data["then"]["command"]) == "fileEdited"
246
+
247
+ # Empty payload (the realistic file-event case) -> still a usable tool call,
248
+ # never None -> the shim allows + audits rather than blocking the save.
249
+ for raw in ("", " ", "{}"):
250
+ normalized = kiro_adapter._normalize("fileEdited", raw)
251
+ assert normalized is not None, (
252
+ f"file event with payload {raw!r} must not fail-closed"
253
+ )
254
+ assert normalized["tool_name"] == "fileSave"
255
+
256
+ # A bare file path -> captured under file_path.
257
+ n_path = kiro_adapter._normalize("fileEdited", "/etc/secrets.env")
258
+ assert n_path["tool_input"]["file_path"] == "/etc/secrets.env"
259
+
260
+ # A JSON object payload (filePath/content if Kiro ever provides it) is kept.
261
+ n_json = kiro_adapter._normalize(
262
+ "fileEdited", json.dumps({"filePath": "a.py", "content": "x=1"})
263
+ )
264
+ assert n_json["tool_input"]["filePath"] == "a.py"
265
+
266
+
267
+ def test_file_save_runCommand_never_blocks_the_editor(monkeypatch):
268
+ """End-to-end (observe-only): a fileEdited hook must exit 0 (allow) even
269
+ when (a) USER_PROMPT is empty AND (b) hook-check itself returns a DENY
270
+ (exit 2). A file save is post-write -- blocking it is pointless and must
271
+ never happen. We still run hook-check (for the audit row + secret scan)
272
+ but never propagate its exit code on a file event."""
273
+
274
+ class _DenyProc:
275
+ # hook-check returns 2 (deny) -- the adapter MUST still allow the save.
276
+ returncode = 2
277
+
278
+ captured = {}
279
+
280
+ def _fake_run(cmd, input, text, env): # noqa: A002
281
+ captured["payload"] = json.loads(input)
282
+ return _DenyProc()
283
+
284
+ monkeypatch.setattr(kiro_adapter.subprocess, "run", _fake_run)
285
+ monkeypatch.delenv("CZ_KIRO_UNKNOWN_PAYLOAD", raising=False)
286
+ monkeypatch.setenv("USER_PROMPT", "")
287
+ rc = kiro_adapter.main(["cz-kiro-adapter", "fileEdited"])
288
+ assert rc == 0, (
289
+ "file save must never be blocked -- not by missing context AND not "
290
+ "even by a hook-check deny (post-write, observe-only)"
291
+ )
292
+ # The audit/scan call still ran with the synthesized fileSave payload.
293
+ assert captured["payload"]["tool_name"] == "fileSave"
294
+ assert captured["payload"]["hook_event_name"] == "fileEdited"
295
+
296
+
297
+ def test_tool_event_still_propagates_block(monkeypatch):
298
+ """Contrast guard: a real TOOL event (preToolUse) MUST still propagate a
299
+ hook-check deny (exit 2). The observe-only relaxation is file-events-only;
300
+ it must not have weakened tool-call gating."""
301
+
302
+ class _DenyProc:
303
+ returncode = 2
304
+
305
+ monkeypatch.setattr(
306
+ kiro_adapter.subprocess, "run", lambda *a, **k: _DenyProc()
307
+ )
308
+ monkeypatch.setenv(
309
+ "USER_PROMPT", json.dumps({"toolName": "execute_bash", "toolArgs": {}})
310
+ )
311
+ rc = kiro_adapter.main(["cz-kiro-adapter", "preToolUse"])
312
+ assert rc == 2, "tool-event deny must still block (only file events are observe-only)"
313
+
314
+
315
+ def test_pre_tool_use_ships_disabled_with_toolTypes():
316
+ """The IDE preToolUse hook ships disabled (upstream empty-payload bug) and,
317
+ when someone does enable it, must already be schema-correct: toolTypes not
318
+ patterns, timeout in seconds."""
319
+ data = _load(_templates_dir() / "ide-pre-tool-use.kiro.hook")
320
+ assert data["enabled"] is False
321
+ assert data["when"]["type"] == "preToolUse"
322
+ assert data["when"]["toolTypes"] == ["*"]
323
+ assert "patterns" not in data["when"]
324
+ assert data["then"]["timeout"] == 5
File without changes
File without changes
File without changes
File without changes