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.
- {zikaron-0.3.0 → zikaron-0.3.1}/PKG-INFO +69 -6
- zikaron-0.3.0/zikaron.egg-info/PKG-INFO → zikaron-0.3.1/README.md +63 -29
- {zikaron-0.3.0 → zikaron-0.3.1}/pyproject.toml +14 -12
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_check_gate.py +2 -2
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_cli_front_door.py +12 -4
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_distribution.py +5 -2
- zikaron-0.3.1/tests/test_guard_claude_live.py +128 -0
- zikaron-0.3.1/tests/test_guard_docstring_examples.py +22 -0
- zikaron-0.3.1/tests/test_guard_main.py +248 -0
- zikaron-0.3.1/tests/test_guard_malformed.py +69 -0
- zikaron-0.3.1/tests/test_guard_nudge.py +82 -0
- zikaron-0.3.1/tests/test_guard_properties.py +78 -0
- zikaron-0.3.1/tests/test_guard_rule_table.py +132 -0
- zikaron-0.3.1/tests/test_guard_script_file.py +49 -0
- zikaron-0.3.1/tests/test_guard_texts.py +121 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_harness_table.py +17 -0
- zikaron-0.3.1/tests/test_hook_components.py +157 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_stdlib_only.py +7 -6
- zikaron-0.3.1/tests/test_install_components.py +362 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_diagnostics.py +16 -9
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_diagnostics_integration.py +14 -6
- zikaron-0.3.1/zikaron/guard/__init__.py +1 -0
- zikaron-0.3.1/zikaron/guard/chars.py +60 -0
- zikaron-0.3.1/zikaron/guard/decision.py +135 -0
- zikaron-0.3.1/zikaron/guard/expression.py +416 -0
- zikaron-0.3.1/zikaron/guard/forms.py +161 -0
- zikaron-0.3.1/zikaron/guard/grammar.py +292 -0
- zikaron-0.3.1/zikaron/guard/heredoc.py +209 -0
- zikaron-0.3.1/zikaron/guard/inplace.py +136 -0
- zikaron-0.3.1/zikaron/guard/interpreter.py +124 -0
- zikaron-0.3.1/zikaron/guard/lexer.py +141 -0
- zikaron-0.3.1/zikaron/guard/limits.py +8 -0
- zikaron-0.3.1/zikaron/guard/literal_text.py +74 -0
- zikaron-0.3.1/zikaron/guard/main.py +126 -0
- zikaron-0.3.1/zikaron/guard/messages.py +105 -0
- zikaron-0.3.1/zikaron/guard/nudge.py +69 -0
- zikaron-0.3.1/zikaron/guard/override.py +84 -0
- zikaron-0.3.1/zikaron/guard/position.py +166 -0
- zikaron-0.3.1/zikaron/guard/programs.py +39 -0
- zikaron-0.3.1/zikaron/guard/quoting.py +83 -0
- zikaron-0.3.1/zikaron/guard/redirects.py +72 -0
- zikaron-0.3.1/zikaron/guard/scratch.py +155 -0
- zikaron-0.3.1/zikaron/guard/script_file.py +238 -0
- zikaron-0.3.1/zikaron/guard/start_text.py +19 -0
- zikaron-0.3.1/zikaron/guard/writes.py +236 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/harness/spec.py +33 -0
- zikaron-0.3.1/zikaron/hook/components.py +60 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/main.py +58 -25
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/subagent_policy.py +6 -1
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/entries.py +68 -31
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/main.py +39 -3
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/targets.py +173 -37
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/writer.py +6 -3
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/diagnostics.py +21 -5
- zikaron-0.3.0/README.md → zikaron-0.3.1/zikaron.egg-info/PKG-INFO +92 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron.egg-info/SOURCES.txt +36 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron.egg-info/entry_points.txt +1 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron.egg-info/requires.txt +5 -5
- {zikaron-0.3.0 → zikaron-0.3.1}/LICENSE +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/setup.cfg +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_access_log.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_agent_scan.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_asyncio_compat.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_background_encoder.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_ci_workflow.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_clock.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_config_keys.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_config_resolution.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_grouping.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_invariants.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_observability.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_planning.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_refusals.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_serving.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_snapshot_planning.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_consolidation_verbs.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_ddl.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_definition_of_done_sites.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_design_pointers_resolve.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_design_tables.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_doctor.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_embedder.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_error_codes.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_event_kinds.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_harness_detect.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_harness_store_scope.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_harness_tier_guard.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_connect.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_connect_edge_cases.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_connect_integration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_connect_race.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_connect_real_service_integration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_envelope.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_failure.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_main.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_push.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_rpc.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_spawn_warm.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_subagent_policy.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_tripwire.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_warm_helper.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_warm_helper_integration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_hook_write_policy.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_acquisition.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_chunking.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_encoder.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_integration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_model_cache.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_indexing_writes.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_injected_prose_log.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_assets.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_claude_live.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_e2e.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_harness.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_limits.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_main.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_takeover.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_targets.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_install_writer.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_build_invariants.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_build_log.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_build_row_ordering.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_builds.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_chunking.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_cli.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_cli_integration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_counters.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_database.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_ddl.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_detach.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_files.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_git.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_groups.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_indexer.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_integration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_invariants.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_lifecycle.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_lock.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_lock_race.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_meta.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_registry.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_repair.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_reporting.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_roots.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_scan.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_scan_git.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_search.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_state.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_text.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_vectors.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_walk.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_knowledge_writes.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_markdown_renders_as_written.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_connection_ambiguous_failure.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_connection_cancellation.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_connection_concurrency.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_connection_integration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_connection_labels.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_consolidator_bridge.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_errors.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_main.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_server.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_spill.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_tool_descriptions.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_mcp_wire_contracts.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_meta.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_permissions.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_project_scope.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_publication_hygiene.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_quoted_design_prose_is_verbatim.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_readme_config_table.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_readme_internal_links.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_records_memory.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_records_receipts.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_records_supersession.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_arms.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_eligibility.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_integration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_query.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_ranking.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_retrieval_reads.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_context.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_context_assemble.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_dispatch.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_dispatch_consolidation.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_dispatch_knowledge.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_encoder_failure_stop.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_envelope.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_knowledge_management.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_lifecycle.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_lifecycle_integration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_log.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_main.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_params.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_paths.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_read_contention.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_rpc.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_security.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_server.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_surface_deadline.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_write_contention.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_service_writer.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_contention.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_dedup.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_horizon.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_repair.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_retirement.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_sessions.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_signals_writes.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_socket_path_budget.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_store.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_store_connection.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_store_migration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_store_pool.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_transaction_begin_sites.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_version_seam.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_write_dedup.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/tests/test_write_tools.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/cli/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/cli/main.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/clock.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/config/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/config/keys.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/config/resolution.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/authorization.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/candidates.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/context.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/grouping.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/groups.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/payload.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/planning.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/rowstate.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/runs.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/serving.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/consolidation/verbs.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/errors.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/events.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/acquisition.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/chunking.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/encoder.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/lexical.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/model_cache.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/model_pin.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/vectors.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/indexing/writes.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/arms.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/builds.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/candidates.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/changes.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/chunking.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/counters.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/database.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/ddl.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/disposal.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/errors.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/files.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/git.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/groups.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/lexical.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/lifecycle.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/lock.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/meta.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/paths.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/pending.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/registry.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/repair.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/reporting.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/roots.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/scan.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/search.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/state.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/text.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/vectors.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/walk.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/knowledge/writes.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/records/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/records/memory.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/records/receipts.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/records/supersession.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/arms.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/block.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/eligibility.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/query.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/ranking.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/reads.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/retrieve.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/retrieval/similarity.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/contention.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/dedup.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/horizon.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/repair.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/retirement.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/sessions.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/signals/writes.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/connection.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/ddl.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/deadline.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/embedder.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/holders.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/meta.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/migration.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/permissions.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/pool.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/store.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/store/transactions.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/write/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/write/dedup.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/core/write/tools.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/doctor/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/doctor/checks.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/doctor/main.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/harness/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/harness/detect.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/connect.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/envelope.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/failure.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/limits.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/push.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/rpc.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/spawn_warm.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/tripwire.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/warm_helper.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/hook/write_policy.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/__main__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/agent_scan.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/assets.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/harness.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/install/ownership.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/__main__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/client.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/indexer/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/indexer/__main__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/indexer/build_log.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/indexer/detach.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/indexer/main.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/main.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/knowledge/scope.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/connection.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/consolidator.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/errors.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/main.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/primary.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/server.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/spill.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/mcp/tool_names.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/project/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/project/__main__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/project/initialize.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/project/report.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/project/resolve.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/py.typed +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/__init__.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/access_log.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/asyncio_compat.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/context.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/dispatch.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/dispatch_consolidation.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/dispatch_knowledge.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/envelope.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/lifecycle.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/log.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/main.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/methods.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/params.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/paths.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/rpc.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/security.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/serialize.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/serialize_knowledge.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron/service/server.py +0 -0
- {zikaron-0.3.0 → zikaron-0.3.1}/zikaron.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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.
|
|
15
|
-
Requires-Dist: huggingface_hub==1.
|
|
16
|
-
Requires-Dist: fastmcp==
|
|
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.
|
|
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.
|
|
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
|
[](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.
|
|
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.
|
|
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.
|
|
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==
|
|
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
|
|
68
|
-
#
|
|
69
|
-
# `
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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
|