zikaron 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. {zikaron-0.3.0 → zikaron-0.3.1}/PKG-INFO +69 -6
  2. zikaron-0.3.0/zikaron.egg-info/PKG-INFO → zikaron-0.3.1/README.md +63 -29
  3. {zikaron-0.3.0 → zikaron-0.3.1}/pyproject.toml +14 -12
  4. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_check_gate.py +2 -2
  5. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_cli_front_door.py +12 -4
  6. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_distribution.py +5 -2
  7. zikaron-0.3.1/tests/test_guard_claude_live.py +128 -0
  8. zikaron-0.3.1/tests/test_guard_docstring_examples.py +22 -0
  9. zikaron-0.3.1/tests/test_guard_main.py +248 -0
  10. zikaron-0.3.1/tests/test_guard_malformed.py +69 -0
  11. zikaron-0.3.1/tests/test_guard_nudge.py +82 -0
  12. zikaron-0.3.1/tests/test_guard_properties.py +78 -0
  13. zikaron-0.3.1/tests/test_guard_rule_table.py +132 -0
  14. zikaron-0.3.1/tests/test_guard_script_file.py +49 -0
  15. zikaron-0.3.1/tests/test_guard_texts.py +121 -0
  16. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_harness_table.py +17 -0
  17. zikaron-0.3.1/tests/test_hook_components.py +157 -0
  18. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_stdlib_only.py +7 -6
  19. zikaron-0.3.1/tests/test_install_components.py +362 -0
  20. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_diagnostics.py +16 -9
  21. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_diagnostics_integration.py +14 -6
  22. zikaron-0.3.1/zikaron/guard/__init__.py +1 -0
  23. zikaron-0.3.1/zikaron/guard/chars.py +60 -0
  24. zikaron-0.3.1/zikaron/guard/decision.py +135 -0
  25. zikaron-0.3.1/zikaron/guard/expression.py +416 -0
  26. zikaron-0.3.1/zikaron/guard/forms.py +161 -0
  27. zikaron-0.3.1/zikaron/guard/grammar.py +292 -0
  28. zikaron-0.3.1/zikaron/guard/heredoc.py +209 -0
  29. zikaron-0.3.1/zikaron/guard/inplace.py +136 -0
  30. zikaron-0.3.1/zikaron/guard/interpreter.py +124 -0
  31. zikaron-0.3.1/zikaron/guard/lexer.py +141 -0
  32. zikaron-0.3.1/zikaron/guard/limits.py +8 -0
  33. zikaron-0.3.1/zikaron/guard/literal_text.py +74 -0
  34. zikaron-0.3.1/zikaron/guard/main.py +126 -0
  35. zikaron-0.3.1/zikaron/guard/messages.py +105 -0
  36. zikaron-0.3.1/zikaron/guard/nudge.py +69 -0
  37. zikaron-0.3.1/zikaron/guard/override.py +84 -0
  38. zikaron-0.3.1/zikaron/guard/position.py +166 -0
  39. zikaron-0.3.1/zikaron/guard/programs.py +39 -0
  40. zikaron-0.3.1/zikaron/guard/quoting.py +83 -0
  41. zikaron-0.3.1/zikaron/guard/redirects.py +72 -0
  42. zikaron-0.3.1/zikaron/guard/scratch.py +155 -0
  43. zikaron-0.3.1/zikaron/guard/script_file.py +238 -0
  44. zikaron-0.3.1/zikaron/guard/start_text.py +19 -0
  45. zikaron-0.3.1/zikaron/guard/writes.py +236 -0
  46. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/harness/spec.py +33 -0
  47. zikaron-0.3.1/zikaron/hook/components.py +60 -0
  48. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/main.py +58 -25
  49. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/subagent_policy.py +6 -1
  50. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/entries.py +68 -31
  51. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/main.py +39 -3
  52. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/targets.py +173 -37
  53. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/writer.py +6 -3
  54. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/diagnostics.py +21 -5
  55. zikaron-0.3.0/README.md → zikaron-0.3.1/zikaron.egg-info/PKG-INFO +92 -0
  56. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron.egg-info/SOURCES.txt +36 -0
  57. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron.egg-info/entry_points.txt +1 -0
  58. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron.egg-info/requires.txt +5 -5
  59. {zikaron-0.3.0 → zikaron-0.3.1}/LICENSE +0 -0
  60. {zikaron-0.3.0 → zikaron-0.3.1}/setup.cfg +0 -0
  61. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_access_log.py +0 -0
  62. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_agent_scan.py +0 -0
  63. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_asyncio_compat.py +0 -0
  64. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_background_encoder.py +0 -0
  65. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_ci_workflow.py +0 -0
  66. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_clock.py +0 -0
  67. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_config_keys.py +0 -0
  68. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_config_resolution.py +0 -0
  69. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_grouping.py +0 -0
  70. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_invariants.py +0 -0
  71. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_observability.py +0 -0
  72. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_planning.py +0 -0
  73. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_refusals.py +0 -0
  74. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_serving.py +0 -0
  75. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_snapshot_planning.py +0 -0
  76. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_verbs.py +0 -0
  77. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_ddl.py +0 -0
  78. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_definition_of_done_sites.py +0 -0
  79. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_design_pointers_resolve.py +0 -0
  80. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_design_tables.py +0 -0
  81. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_doctor.py +0 -0
  82. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_embedder.py +0 -0
  83. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_error_codes.py +0 -0
  84. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_event_kinds.py +0 -0
  85. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_harness_detect.py +0 -0
  86. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_harness_store_scope.py +0 -0
  87. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_harness_tier_guard.py +0 -0
  88. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_connect.py +0 -0
  89. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_connect_edge_cases.py +0 -0
  90. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_connect_integration.py +0 -0
  91. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_connect_race.py +0 -0
  92. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_connect_real_service_integration.py +0 -0
  93. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_envelope.py +0 -0
  94. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_failure.py +0 -0
  95. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_main.py +0 -0
  96. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_push.py +0 -0
  97. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_rpc.py +0 -0
  98. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_spawn_warm.py +0 -0
  99. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_subagent_policy.py +0 -0
  100. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_tripwire.py +0 -0
  101. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_warm_helper.py +0 -0
  102. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_warm_helper_integration.py +0 -0
  103. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_write_policy.py +0 -0
  104. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_acquisition.py +0 -0
  105. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_chunking.py +0 -0
  106. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_encoder.py +0 -0
  107. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_integration.py +0 -0
  108. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_model_cache.py +0 -0
  109. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_writes.py +0 -0
  110. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_injected_prose_log.py +0 -0
  111. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_assets.py +0 -0
  112. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_claude_live.py +0 -0
  113. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_e2e.py +0 -0
  114. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_harness.py +0 -0
  115. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_limits.py +0 -0
  116. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_main.py +0 -0
  117. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_takeover.py +0 -0
  118. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_targets.py +0 -0
  119. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_writer.py +0 -0
  120. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_build_invariants.py +0 -0
  121. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_build_log.py +0 -0
  122. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_build_row_ordering.py +0 -0
  123. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_builds.py +0 -0
  124. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_chunking.py +0 -0
  125. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_cli.py +0 -0
  126. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_cli_integration.py +0 -0
  127. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_counters.py +0 -0
  128. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_database.py +0 -0
  129. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_ddl.py +0 -0
  130. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_detach.py +0 -0
  131. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_files.py +0 -0
  132. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_git.py +0 -0
  133. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_groups.py +0 -0
  134. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_indexer.py +0 -0
  135. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_integration.py +0 -0
  136. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_invariants.py +0 -0
  137. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_lifecycle.py +0 -0
  138. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_lock.py +0 -0
  139. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_lock_race.py +0 -0
  140. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_meta.py +0 -0
  141. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_registry.py +0 -0
  142. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_repair.py +0 -0
  143. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_reporting.py +0 -0
  144. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_roots.py +0 -0
  145. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_scan.py +0 -0
  146. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_scan_git.py +0 -0
  147. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_search.py +0 -0
  148. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_state.py +0 -0
  149. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_text.py +0 -0
  150. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_vectors.py +0 -0
  151. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_walk.py +0 -0
  152. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_writes.py +0 -0
  153. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_markdown_renders_as_written.py +0 -0
  154. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_connection_ambiguous_failure.py +0 -0
  155. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_connection_cancellation.py +0 -0
  156. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_connection_concurrency.py +0 -0
  157. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_connection_integration.py +0 -0
  158. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_connection_labels.py +0 -0
  159. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_consolidator_bridge.py +0 -0
  160. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_errors.py +0 -0
  161. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_main.py +0 -0
  162. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_server.py +0 -0
  163. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_spill.py +0 -0
  164. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_tool_descriptions.py +0 -0
  165. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_wire_contracts.py +0 -0
  166. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_meta.py +0 -0
  167. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_permissions.py +0 -0
  168. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_project_scope.py +0 -0
  169. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_publication_hygiene.py +0 -0
  170. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_quoted_design_prose_is_verbatim.py +0 -0
  171. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_readme_config_table.py +0 -0
  172. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_readme_internal_links.py +0 -0
  173. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_records_memory.py +0 -0
  174. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_records_receipts.py +0 -0
  175. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_records_supersession.py +0 -0
  176. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_arms.py +0 -0
  177. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_eligibility.py +0 -0
  178. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_integration.py +0 -0
  179. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_query.py +0 -0
  180. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_ranking.py +0 -0
  181. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_reads.py +0 -0
  182. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_context.py +0 -0
  183. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_context_assemble.py +0 -0
  184. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_dispatch.py +0 -0
  185. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_dispatch_consolidation.py +0 -0
  186. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_dispatch_knowledge.py +0 -0
  187. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_encoder_failure_stop.py +0 -0
  188. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_envelope.py +0 -0
  189. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_knowledge_management.py +0 -0
  190. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_lifecycle.py +0 -0
  191. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_lifecycle_integration.py +0 -0
  192. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_log.py +0 -0
  193. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_main.py +0 -0
  194. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_params.py +0 -0
  195. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_paths.py +0 -0
  196. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_read_contention.py +0 -0
  197. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_rpc.py +0 -0
  198. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_security.py +0 -0
  199. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_server.py +0 -0
  200. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_surface_deadline.py +0 -0
  201. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_write_contention.py +0 -0
  202. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_writer.py +0 -0
  203. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_contention.py +0 -0
  204. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_dedup.py +0 -0
  205. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_horizon.py +0 -0
  206. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_repair.py +0 -0
  207. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_retirement.py +0 -0
  208. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_sessions.py +0 -0
  209. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_writes.py +0 -0
  210. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_socket_path_budget.py +0 -0
  211. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_store.py +0 -0
  212. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_store_connection.py +0 -0
  213. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_store_migration.py +0 -0
  214. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_store_pool.py +0 -0
  215. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_transaction_begin_sites.py +0 -0
  216. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_version_seam.py +0 -0
  217. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_write_dedup.py +0 -0
  218. {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_write_tools.py +0 -0
  219. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/__init__.py +0 -0
  220. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/cli/__init__.py +0 -0
  221. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/cli/main.py +0 -0
  222. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/__init__.py +0 -0
  223. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/clock.py +0 -0
  224. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/config/__init__.py +0 -0
  225. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/config/keys.py +0 -0
  226. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/config/resolution.py +0 -0
  227. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/__init__.py +0 -0
  228. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/authorization.py +0 -0
  229. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/candidates.py +0 -0
  230. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/context.py +0 -0
  231. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/grouping.py +0 -0
  232. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/groups.py +0 -0
  233. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/payload.py +0 -0
  234. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/planning.py +0 -0
  235. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/rowstate.py +0 -0
  236. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/runs.py +0 -0
  237. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/serving.py +0 -0
  238. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/verbs.py +0 -0
  239. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/errors.py +0 -0
  240. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/events.py +0 -0
  241. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/__init__.py +0 -0
  242. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/acquisition.py +0 -0
  243. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/chunking.py +0 -0
  244. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/encoder.py +0 -0
  245. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/lexical.py +0 -0
  246. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/model_cache.py +0 -0
  247. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/model_pin.py +0 -0
  248. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/vectors.py +0 -0
  249. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/writes.py +0 -0
  250. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/__init__.py +0 -0
  251. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/arms.py +0 -0
  252. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/builds.py +0 -0
  253. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/candidates.py +0 -0
  254. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/changes.py +0 -0
  255. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/chunking.py +0 -0
  256. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/counters.py +0 -0
  257. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/database.py +0 -0
  258. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/ddl.py +0 -0
  259. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/disposal.py +0 -0
  260. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/errors.py +0 -0
  261. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/files.py +0 -0
  262. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/git.py +0 -0
  263. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/groups.py +0 -0
  264. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/lexical.py +0 -0
  265. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/lifecycle.py +0 -0
  266. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/lock.py +0 -0
  267. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/meta.py +0 -0
  268. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/paths.py +0 -0
  269. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/pending.py +0 -0
  270. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/registry.py +0 -0
  271. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/repair.py +0 -0
  272. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/reporting.py +0 -0
  273. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/roots.py +0 -0
  274. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/scan.py +0 -0
  275. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/search.py +0 -0
  276. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/state.py +0 -0
  277. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/text.py +0 -0
  278. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/vectors.py +0 -0
  279. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/walk.py +0 -0
  280. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/writes.py +0 -0
  281. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/records/__init__.py +0 -0
  282. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/records/memory.py +0 -0
  283. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/records/receipts.py +0 -0
  284. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/records/supersession.py +0 -0
  285. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/__init__.py +0 -0
  286. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/arms.py +0 -0
  287. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/block.py +0 -0
  288. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/eligibility.py +0 -0
  289. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/query.py +0 -0
  290. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/ranking.py +0 -0
  291. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/reads.py +0 -0
  292. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/retrieve.py +0 -0
  293. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/similarity.py +0 -0
  294. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/__init__.py +0 -0
  295. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/contention.py +0 -0
  296. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/dedup.py +0 -0
  297. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/horizon.py +0 -0
  298. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/repair.py +0 -0
  299. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/retirement.py +0 -0
  300. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/sessions.py +0 -0
  301. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/writes.py +0 -0
  302. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/__init__.py +0 -0
  303. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/connection.py +0 -0
  304. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/ddl.py +0 -0
  305. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/deadline.py +0 -0
  306. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/embedder.py +0 -0
  307. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/holders.py +0 -0
  308. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/meta.py +0 -0
  309. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/migration.py +0 -0
  310. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/permissions.py +0 -0
  311. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/pool.py +0 -0
  312. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/store.py +0 -0
  313. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/transactions.py +0 -0
  314. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/write/__init__.py +0 -0
  315. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/write/dedup.py +0 -0
  316. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/write/tools.py +0 -0
  317. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/doctor/__init__.py +0 -0
  318. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/doctor/checks.py +0 -0
  319. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/doctor/main.py +0 -0
  320. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/harness/__init__.py +0 -0
  321. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/harness/detect.py +0 -0
  322. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/__init__.py +0 -0
  323. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/connect.py +0 -0
  324. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/envelope.py +0 -0
  325. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/failure.py +0 -0
  326. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/limits.py +0 -0
  327. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/push.py +0 -0
  328. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/rpc.py +0 -0
  329. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/spawn_warm.py +0 -0
  330. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/tripwire.py +0 -0
  331. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/warm_helper.py +0 -0
  332. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/write_policy.py +0 -0
  333. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/__init__.py +0 -0
  334. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/__main__.py +0 -0
  335. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/agent_scan.py +0 -0
  336. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/assets.py +0 -0
  337. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/harness.py +0 -0
  338. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/ownership.py +0 -0
  339. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/__init__.py +0 -0
  340. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/__main__.py +0 -0
  341. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/client.py +0 -0
  342. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/indexer/__init__.py +0 -0
  343. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/indexer/__main__.py +0 -0
  344. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/indexer/build_log.py +0 -0
  345. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/indexer/detach.py +0 -0
  346. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/indexer/main.py +0 -0
  347. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/main.py +0 -0
  348. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/scope.py +0 -0
  349. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/__init__.py +0 -0
  350. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/connection.py +0 -0
  351. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/consolidator.py +0 -0
  352. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/errors.py +0 -0
  353. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/main.py +0 -0
  354. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/primary.py +0 -0
  355. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/server.py +0 -0
  356. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/spill.py +0 -0
  357. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/tool_names.py +0 -0
  358. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/project/__init__.py +0 -0
  359. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/project/__main__.py +0 -0
  360. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/project/initialize.py +0 -0
  361. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/project/report.py +0 -0
  362. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/project/resolve.py +0 -0
  363. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/py.typed +0 -0
  364. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/__init__.py +0 -0
  365. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/access_log.py +0 -0
  366. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/asyncio_compat.py +0 -0
  367. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/context.py +0 -0
  368. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/dispatch.py +0 -0
  369. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/dispatch_consolidation.py +0 -0
  370. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/dispatch_knowledge.py +0 -0
  371. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/envelope.py +0 -0
  372. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/lifecycle.py +0 -0
  373. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/log.py +0 -0
  374. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/main.py +0 -0
  375. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/methods.py +0 -0
  376. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/params.py +0 -0
  377. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/paths.py +0 -0
  378. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/rpc.py +0 -0
  379. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/security.py +0 -0
  380. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/serialize.py +0 -0
  381. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/serialize_knowledge.py +0 -0
  382. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/server.py +0 -0
  383. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron.egg-info/dependency_links.txt +0 -0
  384. {zikaron-0.3.0 → zikaron-0.3.1}/zikaron.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zikaron
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Memory and searchable project knowledge for coding agents
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/nathan-shapiro/Zikaron
@@ -11,16 +11,16 @@ Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: aiosqlite==0.22.1
13
13
  Requires-Dist: sqlite-vec==0.1.9
14
- Requires-Dist: fastembed==0.8.0
15
- Requires-Dist: huggingface_hub==1.26.0
16
- Requires-Dist: fastmcp==3.4.7
14
+ Requires-Dist: fastembed==0.8.1
15
+ Requires-Dist: huggingface_hub==1.33.0
16
+ Requires-Dist: fastmcp==4.0.10
17
17
  Provides-Extra: dev
18
- Requires-Dist: ruff==0.16.7; extra == "dev"
18
+ Requires-Dist: ruff==0.16.9; extra == "dev"
19
19
  Requires-Dist: mypy==2.3.1; extra == "dev"
20
20
  Requires-Dist: pytest==9.1.1; extra == "dev"
21
21
  Requires-Dist: pytest-cov==7.1.0; extra == "dev"
22
22
  Requires-Dist: pytest-asyncio==1.4.0; extra == "dev"
23
- Requires-Dist: hypothesis==6.168.0; extra == "dev"
23
+ Requires-Dist: hypothesis==6.168.3; extra == "dev"
24
24
  Requires-Dist: pyyaml==6.0.3; extra == "dev"
25
25
  Requires-Dist: types-PyYAML==6.0.12.20260906; extra == "dev"
26
26
  Requires-Dist: build==1.3.0; extra == "dev"
@@ -61,6 +61,12 @@ yes, it is out of scope. That store is for what cost somebody time to discover,
61
61
  be discovered again by the next agent, at full price. A knowledge base has no such test — it is
62
62
  whatever corpus you point it at.
63
63
 
64
+ **And, on request, two edit guards for Claude Code** — independent of both stores. One refuses a
65
+ find-replace edit to an authored file (`sed -i`, an inline script, a heredoc into the file) and
66
+ points the agent at `Read` and `Edit`; the other reminds it, after each edit, to re-read what it
67
+ changed. See [Edit guards](#edit-guards-claude-code-on-request); a project may install the memory
68
+ store, the guards, or both.
69
+
64
70
  It runs entirely on your machine: SQLite databases under `.zikaron/`, a local embedding model, and a
65
71
  small background service on a Unix socket. **Zikaron reaches the network for exactly one thing** —
66
72
  the embedding-model download described under [Install](#install), pinned to one revision and
@@ -124,6 +130,7 @@ reads which one it is addressing before it reads a description. Consolidation ne
124
130
  | **service** | A long-running process per project. Holds the embedding model in memory and the databases open, and answers requests over a Unix socket. Starts itself when needed and stops itself when idle. |
125
131
  | **MCP server** | Translates the agent's tool calls into requests to the service. One process per agent instance; loads no model. |
126
132
  | **hook** | A single-shot executable your agent runs on start, before each message, and — under Claude Code — when it spawns a subagent. Deliberately tiny; loads no model and never touches the databases directly. |
133
+ | **guard** | Claude Code only, installed on request: a single-shot executable the harness runs before each Bash call and after each edit. Regular expressions over the call, plus one bounded read of a script the call runs from a scratch directory — no model, no service, no store. |
127
134
  | **indexer** | A detached process started per knowledge-base build — by the service on every `add`/`refresh`, whether an agent asked or you did, or by you running the foreground command a build result prints. It outlives the call that started it, loads a model, saturates a core for a minute or more, and exits when the build finishes. If you see one in `ps`, that is a corpus building, not a runaway. |
128
135
 
129
136
  The service exists for one measured reason: loading the embedding model costs about **780 ms**, and
@@ -235,6 +242,16 @@ cd /path/to/your/project
235
242
  zikaron install --project . --harness claude-code # or: --harness kiro
236
243
  ```
237
244
 
245
+ Under Claude Code, `--components` adds the [edit guards](#edit-guards-claude-code-on-request), or
246
+ installs them alone; without it you get the memory store only, exactly as before:
247
+
248
+ ```bash
249
+ zikaron install --project . --harness claude-code --components both # memory and guards
250
+ zikaron install --project . --harness claude-code --components guards # guards only
251
+ ```
252
+
253
+ Installing one later keeps the other: an install touches only what it was asked for.
254
+
238
255
  **Name it on a first install, rather than leaving it to be detected.** Detection looks for a
239
256
  `.kiro/` or `.claude/` directory in the project and for `CLAUDECODE` in the environment, and it
240
257
  refuses rather than guessing when neither says. Running from an ordinary terminal, a first install
@@ -334,6 +351,12 @@ and under **Claude Code**:
334
351
  | `.claude/settings.local.json` | three hooks — session start, per-message, and per-subagent — plus both approval keys, `enabledMcpjsonServers` and `permissions.allow` |
335
352
  | `.mcp.json` | both servers: `zikaron` for your own tools, `zikaron-consolidator` for the consolidation verbs — each marked `alwaysLoad` so their descriptions are in context from the start rather than fetched per tool |
336
353
 
354
+ With `--components guards` the install writes `.claude/settings.local.json` alone, and in it only
355
+ hooks: the session-start and per-subagent hooks, which carry a short note about the guards, and the
356
+ two guard hooks — before each Bash call, after each edit. No server, no approval key, no
357
+ consolidator. With `--components both` you get all of the above together, and the start hooks carry
358
+ the write policy and the guards' note.
359
+
337
360
  **`settings.local.json`, not `settings.json`**, and it matters if you commit your settings: the hook
338
361
  entries name absolute paths inside *your* virtualenv, so they are meaningless in anyone else's clone.
339
362
  The third hook is the one with no kiro counterpart — it hands the write policy to each subagent you
@@ -451,6 +474,39 @@ guard, the receipts and the lease are untouched — but an unauthorized consolid
451
474
  tokens and could write a poorly-judged long-term record. `permissions.deny` is **not** the fix: it is
452
475
  global and unregisters the tool, after which the consolidator itself refuses to start.
453
476
 
477
+ ### Edit guards (Claude Code, on request)
478
+
479
+ `--components guards` (or `both`) installs two deterministic hooks that hold an agent to two rules,
480
+ with no model and no store behind them (`design/edit-guards.md`):
481
+
482
+ - **A find-replace edit to an authored file is refused** — `sed -i`, `perl -pi`, an inline Python
483
+ or node script that writes a file, a heredoc or `echo` redirected into one, and a script under
484
+ `/tmp` or `$TMPDIR` that the command runs and that does any of these. The refusal reaches the
485
+ agent as the tool's result and tells it to use `Read` and `Edit`, or `Write` for a new file.
486
+ Paths under `/tmp`, `/var/tmp` and `$TMPDIR` are exempt, and so is a command carrying the
487
+ override the refusal itself describes, for the files whose bytes a program owns.
488
+ - **After each edit, the agent is reminded** that the file has not been re-read, and asked for one
489
+ re-read of the edited material once that file's editing is done. A Bash command gets no such
490
+ reminder, so when the agent overrides a refusal, the guard's acknowledgement names what the
491
+ command changes and asks for that re-read instead.
492
+
493
+ They are not a security boundary: a script the guard reads is one run from a scratch directory,
494
+ and a script anywhere else — one of the project's own programs — is never read, by design. A guard
495
+ that fails for any reason allows the call.
496
+
497
+ They write no log and no store record: a refusal, an override and its reason are in the session's
498
+ own transcript. `zikaron doctor` does not report on them. To see the refusal without a model in
499
+ the loop, hand the guard the payload Claude Code would send it, from the project directory — run
500
+ from a scratch directory such as `/tmp`, the same command is exempt and prints nothing:
501
+
502
+ ```bash
503
+ printf '%s' '{"hook_event_name":"PreToolUse","tool_name":"Bash","cwd":"'"$PWD"'","tool_input":{"command":"sed -i s/a/b/ README.md"}}' | zikaron-guard
504
+ ```
505
+
506
+ It prints the deny the agent would read as the command's result. Asking an agent to run `sed -i`
507
+ usually shows something else: told the rule when its session starts, it reaches for `Read` and
508
+ `Edit` without trying.
509
+
454
510
  ### Options
455
511
 
456
512
  | Flag | Effect |
@@ -462,6 +518,7 @@ global and unregisters the tool, after which the consolidator itself refuses to
462
518
  | `--model <id>` | the consolidator's model (default: `claude-sonnet-5` under kiro, `sonnet` under Claude Code) |
463
519
  | `--format {object,array}` | **kiro only.** Which hook format to write when the target config has none yet |
464
520
  | `--no-trust-tools` | do not pre-approve Zikaron's own tools, so every Zikaron tool call asks permission |
521
+ | `--components {memory,guards,both}` | what to install: the memory store (the default), the edit guards below, or both. **Claude Code only** for `guards` and `both`; `--model` is refused with `guards`, which ships no consolidator |
465
522
  | `--force` | replace a symlink at a shipped path, and overwrite entries wired to a different Zikaron install that would otherwise be refused. It also stops merging: a server entry — `.mcp.json`'s, or kiro's `mcpServers` — is replaced **whole**, so a key you added to Zikaron's own entry goes, and under Claude Code a Zikaron hook group is replaced whole, so a hook of your own inside it goes too. Both are named in the output |
466
523
 
467
524
  Both hook formats kiro accepts are supported, and a config that already uses one keeps it: kiro
@@ -864,6 +921,12 @@ than deleted, because both hold settings that are not Zikaron's. **All three set
864
921
  the install writes three** — leaving `enabledMcpjsonServers` behind names two servers that no longer
865
922
  exist, which loads nothing and breaks nothing but is not an uninstall.
866
923
 
924
+ **The edit guards** live only in `.claude/settings.local.json`: remove the `PreToolUse` and
925
+ `PostToolUse` hook groups whose command is `zikaron-guard`, and the `SessionStart` and
926
+ `SubagentStart` groups whose command is `zikaron-hook`. To keep the memory store and drop only the
927
+ guards, re-running the installer will not do it — an install never removes a selection — so delete
928
+ the two guard groups and the `--components …` arguments from the two start groups' command.
929
+
867
930
  Note that `rm -rf .zikaron` takes the knowledge bases with it. They are rebuildable — an index is a
868
931
  view onto files you still have — but rebuilding one takes minutes per corpus.
869
932
 
@@ -1,32 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: zikaron
3
- Version: 0.3.0
4
- Summary: Memory and searchable project knowledge for coding agents
5
- License-Expression: MIT
6
- Project-URL: Homepage, https://github.com/nathan-shapiro/Zikaron
7
- Project-URL: Repository, https://github.com/nathan-shapiro/Zikaron
8
- Project-URL: Issues, https://github.com/nathan-shapiro/Zikaron/issues
9
- Requires-Python: >=3.12
10
- Description-Content-Type: text/markdown
11
- License-File: LICENSE
12
- Requires-Dist: aiosqlite==0.22.1
13
- Requires-Dist: sqlite-vec==0.1.9
14
- Requires-Dist: fastembed==0.8.0
15
- Requires-Dist: huggingface_hub==1.26.0
16
- Requires-Dist: fastmcp==3.4.7
17
- Provides-Extra: dev
18
- Requires-Dist: ruff==0.16.7; extra == "dev"
19
- Requires-Dist: mypy==2.3.1; extra == "dev"
20
- Requires-Dist: pytest==9.1.1; extra == "dev"
21
- Requires-Dist: pytest-cov==7.1.0; extra == "dev"
22
- Requires-Dist: pytest-asyncio==1.4.0; extra == "dev"
23
- Requires-Dist: hypothesis==6.168.0; extra == "dev"
24
- Requires-Dist: pyyaml==6.0.3; extra == "dev"
25
- Requires-Dist: types-PyYAML==6.0.12.20260906; extra == "dev"
26
- Requires-Dist: build==1.3.0; extra == "dev"
27
- Requires-Dist: setuptools==84.0.0; extra == "dev"
28
- Dynamic: license-file
29
-
30
1
  # Zikaron
31
2
 
32
3
  [![PyPI](https://img.shields.io/pypi/v/zikaron)](https://pypi.org/project/zikaron/)
@@ -61,6 +32,12 @@ yes, it is out of scope. That store is for what cost somebody time to discover,
61
32
  be discovered again by the next agent, at full price. A knowledge base has no such test — it is
62
33
  whatever corpus you point it at.
63
34
 
35
+ **And, on request, two edit guards for Claude Code** — independent of both stores. One refuses a
36
+ find-replace edit to an authored file (`sed -i`, an inline script, a heredoc into the file) and
37
+ points the agent at `Read` and `Edit`; the other reminds it, after each edit, to re-read what it
38
+ changed. See [Edit guards](#edit-guards-claude-code-on-request); a project may install the memory
39
+ store, the guards, or both.
40
+
64
41
  It runs entirely on your machine: SQLite databases under `.zikaron/`, a local embedding model, and a
65
42
  small background service on a Unix socket. **Zikaron reaches the network for exactly one thing** —
66
43
  the embedding-model download described under [Install](#install), pinned to one revision and
@@ -124,6 +101,7 @@ reads which one it is addressing before it reads a description. Consolidation ne
124
101
  | **service** | A long-running process per project. Holds the embedding model in memory and the databases open, and answers requests over a Unix socket. Starts itself when needed and stops itself when idle. |
125
102
  | **MCP server** | Translates the agent's tool calls into requests to the service. One process per agent instance; loads no model. |
126
103
  | **hook** | A single-shot executable your agent runs on start, before each message, and — under Claude Code — when it spawns a subagent. Deliberately tiny; loads no model and never touches the databases directly. |
104
+ | **guard** | Claude Code only, installed on request: a single-shot executable the harness runs before each Bash call and after each edit. Regular expressions over the call, plus one bounded read of a script the call runs from a scratch directory — no model, no service, no store. |
127
105
  | **indexer** | A detached process started per knowledge-base build — by the service on every `add`/`refresh`, whether an agent asked or you did, or by you running the foreground command a build result prints. It outlives the call that started it, loads a model, saturates a core for a minute or more, and exits when the build finishes. If you see one in `ps`, that is a corpus building, not a runaway. |
128
106
 
129
107
  The service exists for one measured reason: loading the embedding model costs about **780 ms**, and
@@ -235,6 +213,16 @@ cd /path/to/your/project
235
213
  zikaron install --project . --harness claude-code # or: --harness kiro
236
214
  ```
237
215
 
216
+ Under Claude Code, `--components` adds the [edit guards](#edit-guards-claude-code-on-request), or
217
+ installs them alone; without it you get the memory store only, exactly as before:
218
+
219
+ ```bash
220
+ zikaron install --project . --harness claude-code --components both # memory and guards
221
+ zikaron install --project . --harness claude-code --components guards # guards only
222
+ ```
223
+
224
+ Installing one later keeps the other: an install touches only what it was asked for.
225
+
238
226
  **Name it on a first install, rather than leaving it to be detected.** Detection looks for a
239
227
  `.kiro/` or `.claude/` directory in the project and for `CLAUDECODE` in the environment, and it
240
228
  refuses rather than guessing when neither says. Running from an ordinary terminal, a first install
@@ -334,6 +322,12 @@ and under **Claude Code**:
334
322
  | `.claude/settings.local.json` | three hooks — session start, per-message, and per-subagent — plus both approval keys, `enabledMcpjsonServers` and `permissions.allow` |
335
323
  | `.mcp.json` | both servers: `zikaron` for your own tools, `zikaron-consolidator` for the consolidation verbs — each marked `alwaysLoad` so their descriptions are in context from the start rather than fetched per tool |
336
324
 
325
+ With `--components guards` the install writes `.claude/settings.local.json` alone, and in it only
326
+ hooks: the session-start and per-subagent hooks, which carry a short note about the guards, and the
327
+ two guard hooks — before each Bash call, after each edit. No server, no approval key, no
328
+ consolidator. With `--components both` you get all of the above together, and the start hooks carry
329
+ the write policy and the guards' note.
330
+
337
331
  **`settings.local.json`, not `settings.json`**, and it matters if you commit your settings: the hook
338
332
  entries name absolute paths inside *your* virtualenv, so they are meaningless in anyone else's clone.
339
333
  The third hook is the one with no kiro counterpart — it hands the write policy to each subagent you
@@ -451,6 +445,39 @@ guard, the receipts and the lease are untouched — but an unauthorized consolid
451
445
  tokens and could write a poorly-judged long-term record. `permissions.deny` is **not** the fix: it is
452
446
  global and unregisters the tool, after which the consolidator itself refuses to start.
453
447
 
448
+ ### Edit guards (Claude Code, on request)
449
+
450
+ `--components guards` (or `both`) installs two deterministic hooks that hold an agent to two rules,
451
+ with no model and no store behind them (`design/edit-guards.md`):
452
+
453
+ - **A find-replace edit to an authored file is refused** — `sed -i`, `perl -pi`, an inline Python
454
+ or node script that writes a file, a heredoc or `echo` redirected into one, and a script under
455
+ `/tmp` or `$TMPDIR` that the command runs and that does any of these. The refusal reaches the
456
+ agent as the tool's result and tells it to use `Read` and `Edit`, or `Write` for a new file.
457
+ Paths under `/tmp`, `/var/tmp` and `$TMPDIR` are exempt, and so is a command carrying the
458
+ override the refusal itself describes, for the files whose bytes a program owns.
459
+ - **After each edit, the agent is reminded** that the file has not been re-read, and asked for one
460
+ re-read of the edited material once that file's editing is done. A Bash command gets no such
461
+ reminder, so when the agent overrides a refusal, the guard's acknowledgement names what the
462
+ command changes and asks for that re-read instead.
463
+
464
+ They are not a security boundary: a script the guard reads is one run from a scratch directory,
465
+ and a script anywhere else — one of the project's own programs — is never read, by design. A guard
466
+ that fails for any reason allows the call.
467
+
468
+ They write no log and no store record: a refusal, an override and its reason are in the session's
469
+ own transcript. `zikaron doctor` does not report on them. To see the refusal without a model in
470
+ the loop, hand the guard the payload Claude Code would send it, from the project directory — run
471
+ from a scratch directory such as `/tmp`, the same command is exempt and prints nothing:
472
+
473
+ ```bash
474
+ printf '%s' '{"hook_event_name":"PreToolUse","tool_name":"Bash","cwd":"'"$PWD"'","tool_input":{"command":"sed -i s/a/b/ README.md"}}' | zikaron-guard
475
+ ```
476
+
477
+ It prints the deny the agent would read as the command's result. Asking an agent to run `sed -i`
478
+ usually shows something else: told the rule when its session starts, it reaches for `Read` and
479
+ `Edit` without trying.
480
+
454
481
  ### Options
455
482
 
456
483
  | Flag | Effect |
@@ -462,6 +489,7 @@ global and unregisters the tool, after which the consolidator itself refuses to
462
489
  | `--model <id>` | the consolidator's model (default: `claude-sonnet-5` under kiro, `sonnet` under Claude Code) |
463
490
  | `--format {object,array}` | **kiro only.** Which hook format to write when the target config has none yet |
464
491
  | `--no-trust-tools` | do not pre-approve Zikaron's own tools, so every Zikaron tool call asks permission |
492
+ | `--components {memory,guards,both}` | what to install: the memory store (the default), the edit guards below, or both. **Claude Code only** for `guards` and `both`; `--model` is refused with `guards`, which ships no consolidator |
465
493
  | `--force` | replace a symlink at a shipped path, and overwrite entries wired to a different Zikaron install that would otherwise be refused. It also stops merging: a server entry — `.mcp.json`'s, or kiro's `mcpServers` — is replaced **whole**, so a key you added to Zikaron's own entry goes, and under Claude Code a Zikaron hook group is replaced whole, so a hook of your own inside it goes too. Both are named in the output |
466
494
 
467
495
  Both hook formats kiro accepts are supported, and a config that already uses one keeps it: kiro
@@ -864,6 +892,12 @@ than deleted, because both hold settings that are not Zikaron's. **All three set
864
892
  the install writes three** — leaving `enabledMcpjsonServers` behind names two servers that no longer
865
893
  exist, which loads nothing and breaks nothing but is not an uninstall.
866
894
 
895
+ **The edit guards** live only in `.claude/settings.local.json`: remove the `PreToolUse` and
896
+ `PostToolUse` hook groups whose command is `zikaron-guard`, and the `SessionStart` and
897
+ `SubagentStart` groups whose command is `zikaron-hook`. To keep the memory store and drop only the
898
+ guards, re-running the installer will not do it — an install never removes a selection — so delete
899
+ the two guard groups and the `--components …` arguments from the two start groups' command.
900
+
867
901
  Note that `rm -rf .zikaron` takes the knowledge bases with it. They are rebuildable — an index is a
868
902
  view onto files you still have — but rebuilding one takes minutes per corpus.
869
903
 
@@ -10,7 +10,7 @@ name = "zikaron"
10
10
  # one is a minor bump, never a patch**, because those paths are written into harness config that an
11
11
  # upgrade has to be able to refresh, and a patch bump tells a reader nothing needs refreshing.
12
12
  # `design/distribution.md` §"The version scheme" is normative.
13
- version = "0.3.0"
13
+ version = "0.3.1"
14
14
  # What a package index renders as the one-line summary, so it must name both stores: the memory
15
15
  # store, for what was learned by working here, and the knowledge index, for what the project already
16
16
  # wrote down.
@@ -32,11 +32,11 @@ requires-python = ">=3.12"
32
32
  dependencies = [
33
33
  "aiosqlite==0.22.1",
34
34
  "sqlite-vec==0.1.9",
35
- "fastembed==0.8.0",
35
+ "fastembed==0.8.1",
36
36
  # Direct, not transitive: `core/indexing/acquisition.py` calls `snapshot_download` itself, so
37
37
  # that the revision and the digests are Zikaron's rather than whatever upstream head fastembed
38
38
  # would resolve. §6 pins direct dependencies exactly.
39
- "huggingface_hub==1.26.0",
39
+ "huggingface_hub==1.33.0",
40
40
  # `zikaron-mcp` only. Deliberately not stdlib-only, unlike `zikaron-hook` — see
41
41
  # coding-standards.md §6 for the cost-model reasoning (once per agent spawn, not once per
42
42
  # message) and architecture.md §Components for the measured import cost.
@@ -45,7 +45,7 @@ dependencies = [
45
45
  # changes how a client's connection — and therefore the session label this client adopts and
46
46
  # reuses across calls — is carried. That is a transport change worth its own measurement, so
47
47
  # the bump is deliberate rather than absorbed into an ordinary dependency refresh.
48
- "fastmcp==3.4.7",
48
+ "fastmcp==4.0.10",
49
49
  ]
50
50
 
51
51
  # Absent until now, which is why the PyPI page M30 will publish would have rendered blank. The
@@ -64,12 +64,13 @@ Homepage = "https://github.com/nathan-shapiro/Zikaron"
64
64
  Repository = "https://github.com/nathan-shapiro/Zikaron"
65
65
  Issues = "https://github.com/nathan-shapiro/Zikaron/issues"
66
66
 
67
- # The two commands a shipped harness config names — either harness — and the reason they are console
68
- # scripts rather than `python -m` command lines: a hook entry's `command` and an `mcpServers` entry's
69
- # `command` each need one absolute path, and a console script's shebang pins the interpreter Zikaron
70
- # is installed into — which is also the interpreter start-if-absent will spawn the service with, via
71
- # `sys.executable`. Measured, so the choice costs nothing: the two forms differ by about 1.4 ms
72
- # median over a full hook process (architecture.md §"The install contract").
67
+ # The commands a shipped harness config names — `zikaron-hook` and `zikaron-mcp` for either harness,
68
+ # `zikaron-guard` for Claude Code's edit guards — and the reason they are console scripts rather than
69
+ # `python -m` command lines: a hook entry's `command` and an `mcpServers` entry's `command` each need
70
+ # one absolute path, and a console script's shebang pins the interpreter Zikaron is installed into —
71
+ # which is also the interpreter start-if-absent will spawn the service with, via `sys.executable`.
72
+ # Measured, so the choice costs nothing: the two forms differ by about 1.4 ms median over a full hook
73
+ # process (architecture.md §"The install contract").
73
74
  #
74
75
  # `zikaron-service` is deliberately *not* here. No config names it: it is spawned by
75
76
  # `sys.executable -m zikaron.service.main` from whichever client finds the socket absent, which is
@@ -84,17 +85,18 @@ Issues = "https://github.com/nathan-shapiro/Zikaron/issues"
84
85
  zikaron = "zikaron.cli.main:main"
85
86
  zikaron-hook = "zikaron.hook.main:main"
86
87
  zikaron-mcp = "zikaron.mcp.main:main"
88
+ zikaron-guard = "zikaron.guard.main:main"
87
89
 
88
90
  [project.optional-dependencies]
89
91
  dev = [
90
- "ruff==0.16.7",
92
+ "ruff==0.16.9",
91
93
  "mypy==2.3.1",
92
94
  "pytest==9.1.1",
93
95
  "pytest-cov==7.1.0",
94
96
  "pytest-asyncio==1.4.0",
95
97
  # Property-based tests, for the places the design states a property rather than a case:
96
98
  # chunking never loses content, and no chunk exceeds the budget it was cut to.
97
- "hypothesis==6.168.0",
99
+ "hypothesis==6.168.3",
98
100
  # Tests only, and never imported by shipped code. Two of Claude Code's four installed
99
101
  # artefacts are YAML-frontmatter Markdown, and the installer emits that frontmatter by string
100
102
  # formatting — so the only way to assert it is *valid* is to parse it with something that did
@@ -140,7 +140,7 @@ def test_the_project_table_still_holds_the_keys_a_later_table_can_swallow() -> N
140
140
  still *in `[project]`*, which is the property an inserted table destroys.
141
141
  """
142
142
  project = tomllib.loads((_ROOT / "pyproject.toml").read_text(encoding="utf-8"))["project"]
143
- for key in ("name", "version", "readme", "license", "requires-python", "dependencies"):
143
+ keys = ("name", "version", "readme", "license", "requires-python", "dependencies", "scripts")
144
+ for key in keys:
144
145
  assert key in project, f"`{key}` has left [project] — a table header was inserted above it"
145
146
  assert len(project["dependencies"]) == 5, "the runtime dependency list changed size"
146
- assert set(project["scripts"]) == {"zikaron", "zikaron-hook", "zikaron-mcp"}
@@ -25,10 +25,18 @@ _ROOT: Final = Path(__file__).resolve().parent.parent
25
25
  _SUBCOMMANDS: Final = ("init", "install", "knowledge", "doctor")
26
26
 
27
27
 
28
- def test_the_console_script_resolves_to_something_callable() -> None:
29
- declared = tomllib.loads((_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
30
- target = declared["project"]["scripts"][NAME]
31
- module_path, _, attribute = target.partition(":")
28
+ _SCRIPTS: Final[dict[str, str]] = tomllib.loads(
29
+ (_ROOT / "pyproject.toml").read_text(encoding="utf-8")
30
+ )["project"]["scripts"]
31
+
32
+
33
+ def test_the_front_door_is_a_declared_console_script() -> None:
34
+ assert NAME in _SCRIPTS
35
+
36
+
37
+ @pytest.mark.parametrize("script", sorted(_SCRIPTS))
38
+ def test_every_console_script_resolves_to_something_callable(script: str) -> None:
39
+ module_path, _, attribute = _SCRIPTS[script].partition(":")
32
40
  entry = getattr(importlib.import_module(module_path), attribute)
33
41
  assert callable(entry)
34
42
 
@@ -143,8 +143,11 @@ def test_the_wheel_declares_every_console_script(built: tuple[Path, Path]) -> No
143
143
  with zipfile.ZipFile(wheel) as archive:
144
144
  (entry_points,) = [n for n in archive.namelist() if n.endswith("entry_points.txt")]
145
145
  declared = archive.read(entry_points).decode()
146
- for script in ("zikaron =", "zikaron-hook =", "zikaron-mcp ="):
147
- assert script in declared, f"{script.rstrip(' =')} is not in the built wheel"
146
+ scripts = tomllib.loads((_ROOT / "pyproject.toml").read_text(encoding="utf-8"))["project"][
147
+ "scripts"
148
+ ]
149
+ for script in scripts:
150
+ assert f"{script} =" in declared, f"{script} is not in the built wheel"
148
151
 
149
152
 
150
153
  def test_nothing_published_names_this_machine(built: tuple[Path, Path]) -> None:
@@ -0,0 +1,128 @@
1
+ """The edit guards in a real Claude Code session (`design/build-plan.md` §M37, "A live test").
2
+
3
+ One headless session under `--permission-mode bypassPermissions` is given exact commands, so that
4
+ neither the edit nor the override is the model's choice: a `sed -i` the guard must deny, the same
5
+ form with a valid override, and an `Edit`. What is asserted is what the harness recorded, never what
6
+ the model says:
7
+
8
+ - the deny is the `sed -i` call's `tool_result` in the `stream-json` output, and the file is
9
+ unchanged by it — under `bypassPermissions`, which is itself one of §2's rows;
10
+ - the override's command ran (the file changed), and its acknowledgement reached the model, which is
11
+ recorded only in the session's transcript, as an attachment;
12
+ - the `Edit` was followed by the nudge, also an attachment.
13
+
14
+ **The project lives under `~/.cache/zikaron-live-tests/`, never `tmp_path`**: pytest's temporary
15
+ directories lie under `/tmp` or `$TMPDIR`, where the guard exempts the commands and suppresses the
16
+ nudge, so a project there would test nothing. **The override's withdrawing rather than granting is
17
+ not observable here** — under `bypassPermissions` no decision and `allow` look the same — so the
18
+ hermetic `tests/test_guard_main.py` holds it, on the hook's JSON.
19
+
20
+ .venv/bin/pytest -m integration_claude tests/test_guard_claude_live.py
21
+ """
22
+
23
+ import json
24
+ import os
25
+ import shutil
26
+ import subprocess
27
+ import uuid
28
+ from collections.abc import Iterator
29
+ from pathlib import Path
30
+ from typing import Any, Final
31
+
32
+ import pytest
33
+
34
+ from zikaron.harness.spec import CLAUDE_CODE
35
+ from zikaron.install import harness
36
+ from zikaron.install import main as install_main
37
+
38
+ pytestmark = pytest.mark.integration_claude
39
+
40
+ _TURN_TIMEOUT: Final = 300.0
41
+ _DENIED: Final = "sed -i 's/beta/BETA/' target.txt"
42
+ _OVERRIDDEN: Final = "sed -i 's/gamma/GAMMA/' target.txt #ZIKARON-FORCE #Reason: live guard test"
43
+ #: The model is told to keep the trailing comment because, measured, a small model otherwise drops
44
+ #: it as noise — and the override is then the model's choice, which this test must not depend on.
45
+ _PROMPT: Final = (
46
+ "Do these three steps in order, each exactly as written, and carry on to the next even if one "
47
+ f"fails. Step 1: run this Bash command, character for character: `{_DENIED}`. Step 2: run this "
48
+ f"Bash command, character for character, including its trailing # comment: `{_OVERRIDDEN}`. "
49
+ "Step 3: use the Edit tool to replace the word alpha with ALPHA in target.txt. Then reply with "
50
+ "the single word: done."
51
+ )
52
+
53
+
54
+ @pytest.fixture
55
+ def project() -> Iterator[Path]:
56
+ root = Path.home() / ".cache" / "zikaron-live-tests" / uuid.uuid4().hex
57
+ root.mkdir(parents=True)
58
+ (root / "target.txt").write_text("alpha beta gamma\n", encoding="utf-8")
59
+ try:
60
+ yield root
61
+ finally:
62
+ shutil.rmtree(root)
63
+
64
+
65
+ def _session(project: Path) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
66
+ """One headless turn: the `stream-json` records, and the session transcript's records."""
67
+ assert harness.binary_is_available(CLAUDE_CODE.harness_binary), (
68
+ f"{CLAUDE_CODE.harness_binary!r} is not on PATH: do not run -m integration_claude here, "
69
+ "or `HarnessSpec.harness_binary` names the wrong binary."
70
+ )
71
+ result = subprocess.run( # noqa: S603 — the argv is this file's own constants.
72
+ [
73
+ CLAUDE_CODE.harness_binary,
74
+ "-p",
75
+ _PROMPT,
76
+ "--model",
77
+ "haiku",
78
+ "--permission-mode",
79
+ "bypassPermissions",
80
+ "--output-format",
81
+ "stream-json",
82
+ "--verbose",
83
+ ],
84
+ cwd=project,
85
+ env=os.environ.copy(),
86
+ capture_output=True,
87
+ text=True,
88
+ timeout=_TURN_TIMEOUT,
89
+ check=False,
90
+ stdin=subprocess.DEVNULL,
91
+ )
92
+ stream = [json.loads(line) for line in result.stdout.splitlines() if line.startswith("{")]
93
+ sessions = {record["session_id"] for record in stream if "session_id" in record}
94
+ assert len(sessions) == 1, f"no session in the stream; stderr: {result.stderr[-800:]!r}"
95
+ (session,) = sessions
96
+ (transcript,) = (Path.home() / ".claude" / "projects").glob(f"*/{session}.jsonl")
97
+ lines = transcript.read_text(encoding="utf-8").splitlines()
98
+ return stream, [json.loads(line) for line in lines if line.strip()]
99
+
100
+
101
+ def _tool_results(stream: list[dict[str, Any]]) -> list[str]:
102
+ results = []
103
+ for record in stream:
104
+ for block in (record.get("message") or {}).get("content") or []:
105
+ if isinstance(block, dict) and block.get("type") == "tool_result":
106
+ content = block.get("content")
107
+ results.append(content if isinstance(content, str) else json.dumps(content))
108
+ return results
109
+
110
+
111
+ def test_the_guards_deny_acknowledge_and_nudge_in_a_real_session(project: Path) -> None:
112
+ status = install_main.main(
113
+ ["--project", str(project), "--harness", "claude-code", "--components", "guards"]
114
+ )
115
+ assert status == 0
116
+ stream, transcript = _session(project)
117
+ text = (project / "target.txt").read_text(encoding="utf-8")
118
+
119
+ denials = [r for r in _tool_results(stream) if "PreToolUse:Bash hook error" in r]
120
+ assert any("Zikaron's find-replace guard refused this command" in r for r in denials), denials
121
+ assert "BETA" not in text, "the denied sed ran"
122
+ assert "GAMMA" in text, "the overridden sed did not run"
123
+ assert "ALPHA" in text, "the Edit did not happen, so no nudge could follow it"
124
+
125
+ recorded = "\n".join(json.dumps(record) for record in transcript)
126
+ assert "#ZIKARON-FORCE accepted by Zikaron's find-replace guard" in recorded
127
+ assert "PostToolUse:Edit hook additional context" in recorded
128
+ assert "target.txt` was edited" in recorded
@@ -0,0 +1,22 @@
1
+ """Every example in `zikaron.guard`'s docstrings runs and gives the output it shows.
2
+
3
+ The rule modules lead with examples so a reader meets the rule before the code; run as doctests,
4
+ an example cannot drift from the code beneath it.
5
+ """
6
+
7
+ import doctest
8
+ import importlib
9
+ import pkgutil
10
+
11
+ import pytest
12
+
13
+ import zikaron.guard
14
+
15
+ MODULES = sorted(info.name for info in pkgutil.iter_modules(zikaron.guard.__path__))
16
+
17
+
18
+ @pytest.mark.parametrize("name", MODULES)
19
+ def test_docstring_examples(name: str) -> None:
20
+ module = importlib.import_module(f"zikaron.guard.{name}")
21
+ result = doctest.testmod(module, optionflags=doctest.ELLIPSIS)
22
+ assert result.failed == 0