xrefkit 0.7.0__tar.gz → 0.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. {xrefkit-0.7.0/xrefkit.egg-info → xrefkit-0.7.2}/PKG-INFO +1 -1
  2. {xrefkit-0.7.0 → xrefkit-0.7.2}/pyproject.toml +1 -1
  3. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_ci_workflow_invariants.py +32 -0
  4. xrefkit-0.7.2/tests/test_contribution_adoption_bytes.py +240 -0
  5. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_contribution_returns.py +148 -0
  6. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_embedded_startup_pack.py +3 -0
  7. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_skill_runtime_audit.py +435 -0
  8. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/__init__.py +1 -1
  9. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/contribution_returns.py +12 -8
  10. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/startup_contract_pack.py +54 -1
  11. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/operations_cli.py +13 -0
  12. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/startup_sources/000_agent_entry.md +53 -0
  13. xrefkit-0.7.2/xrefkit/resources/base/startup_sources/yomiyasu_LICENSE.txt +21 -0
  14. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/skillrun.py +338 -3
  15. {xrefkit-0.7.0 → xrefkit-0.7.2/xrefkit.egg-info}/PKG-INFO +1 -1
  16. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit.egg-info/SOURCES.txt +2 -0
  17. {xrefkit-0.7.0 → xrefkit-0.7.2}/LICENSE +0 -0
  18. {xrefkit-0.7.0 → xrefkit-0.7.2}/README.md +0 -0
  19. {xrefkit-0.7.0 → xrefkit-0.7.2}/setup.cfg +0 -0
  20. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_artifact_selected_family.py +0 -0
  21. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_attention_pet.py +0 -0
  22. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_attention_pet_client_protocol.py +0 -0
  23. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_attention_pet_codex_session.py +0 -0
  24. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_attention_pet_fit.py +0 -0
  25. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_azure_connection.py +0 -0
  26. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_azure_import.py +0 -0
  27. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_azure_recovery.py +0 -0
  28. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_azure_update_candidate.py +0 -0
  29. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_azure_writer.py +0 -0
  30. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_boundary_analysis.py +0 -0
  31. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_brownfield_csharp_structure_definitions.py +0 -0
  32. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_brownfield_file_editing_protocol.py +0 -0
  33. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_business_intake_conversation_definitions.py +0 -0
  34. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_business_intake_definition.py +0 -0
  35. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_calibration_lint.py +0 -0
  36. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_catalog_preparation_definition.py +0 -0
  37. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_check_skill_knowledge_xids.py +0 -0
  38. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_cli.py +0 -0
  39. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_code_constraint_definition.py +0 -0
  40. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_collect_analyzer_sarif.py +0 -0
  41. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_constraint_derivation_family_definition.py +0 -0
  42. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_constraint_derivation_selected_family.py +0 -0
  43. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_convert_to_xrefkit_skill.py +0 -0
  44. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_cs_scope_probe.py +0 -0
  45. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_csharp_commonality.py +0 -0
  46. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_csharp_naming_profile.py +0 -0
  47. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_ctx.py +0 -0
  48. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_cutover_readiness.py +0 -0
  49. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_dashboard.py +0 -0
  50. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_database_batch_definition_migration.py +0 -0
  51. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_decision_trace.py +0 -0
  52. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_design_business_intake_selected_family.py +0 -0
  53. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_design_planning_definition_migration.py +0 -0
  54. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_dotnet_definition_candidate.py +0 -0
  55. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_editorial_family_definition.py +0 -0
  56. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_editorial_intake_definition.py +0 -0
  57. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_editorial_selected_family.py +0 -0
  58. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_error_policy_audit.py +0 -0
  59. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_error_policy_locator.py +0 -0
  60. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_execution_binding.py +0 -0
  61. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_existing_skill_definition_batch.py +0 -0
  62. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_final_skilldefinition_batch.py +0 -0
  63. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_flow_selected_family.py +0 -0
  64. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_fm_multiroot.py +0 -0
  65. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_gate.py +0 -0
  66. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_gateway.py +0 -0
  67. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_gateway_mcp.py +0 -0
  68. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_gateway_skill_adapter.py +0 -0
  69. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_gateway_work_items.py +0 -0
  70. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_goal_desired_state.py +0 -0
  71. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_governance_entry.py +0 -0
  72. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_host_precheck.py +0 -0
  73. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_human_evaluation.py +0 -0
  74. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_implementation_flow_definition.py +0 -0
  75. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_implementation_review_selected_family.py +0 -0
  76. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_inbound_uploads.py +0 -0
  77. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_instruction_workflow.py +0 -0
  78. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_knowledge_relations_validator.py +0 -0
  79. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_language_review_definition.py +0 -0
  80. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_legacy_guidance.py +0 -0
  81. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_legacy_migration.py +0 -0
  82. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_legacy_resources.py +0 -0
  83. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_mcp_package_routing.py +0 -0
  84. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_mcp_setup.py +0 -0
  85. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_mcp_subagent_startup.py +0 -0
  86. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_mcp_subagent_startup_integration.py +0 -0
  87. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_ontology_assessment_validator.py +0 -0
  88. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_os_family_definition.py +0 -0
  89. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_os_knowledge_family_definition.py +0 -0
  90. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_os_selected_family.py +0 -0
  91. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_ownership.py +0 -0
  92. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_packmeta.py +0 -0
  93. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_plan_markdown.py +0 -0
  94. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_plan_observation.py +0 -0
  95. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_project_quality_baseline.py +0 -0
  96. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_qa_report_definition.py +0 -0
  97. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_reporting_profiles.py +0 -0
  98. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_reporting_quality.py +0 -0
  99. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_repository_skill_adoption.py +0 -0
  100. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_repository_startup_pack.py +0 -0
  101. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_resource_provider.py +0 -0
  102. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_runtime_contracts.py +0 -0
  103. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_sarif_to_locator.py +0 -0
  104. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_security_definition_migration.py +0 -0
  105. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_site_release_metadata.py +0 -0
  106. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_skill_definition.py +0 -0
  107. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_skill_definition_governance.py +0 -0
  108. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_skill_definition_mcp.py +0 -0
  109. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_skill_definition_runtime.py +0 -0
  110. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_skill_edits.py +0 -0
  111. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_skill_maturity_return.py +0 -0
  112. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_skill_release_compatibility.py +0 -0
  113. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_skillmeta.py +0 -0
  114. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_skills_sync.py +0 -0
  115. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_source_analysis_fragments.py +0 -0
  116. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_structure_catalog.py +0 -0
  117. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_subagent_startup.py +0 -0
  118. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_subagent_startup_boundaries.py +0 -0
  119. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_wbs.py +0 -0
  120. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_work_management.py +0 -0
  121. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_work_packages.py +0 -0
  122. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_workflow_family_definition.py +0 -0
  123. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_xref.py +0 -0
  124. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_xrefkit_instance.py +0 -0
  125. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_xrefkit_tools.py +0 -0
  126. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_xrefkit_v2_discovery.py +0 -0
  127. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_xrefkit_v2_models.py +0 -0
  128. {xrefkit-0.7.0 → xrefkit-0.7.2}/tests/test_xrefkit_v2_pipeline.py +0 -0
  129. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/__main__.py +0 -0
  130. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/__init__.py +0 -0
  131. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/__main__.py +0 -0
  132. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/client.py +0 -0
  133. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/client_protocol.py +0 -0
  134. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/codex_session.py +0 -0
  135. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/evaluator.py +0 -0
  136. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/model.py +0 -0
  137. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/presentation.py +0 -0
  138. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/profiles.py +0 -0
  139. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/scenarios.py +0 -0
  140. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/server.py +0 -0
  141. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/attention_pet/store.py +0 -0
  142. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/azure_connection.py +0 -0
  143. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/azure_import.py +0 -0
  144. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/azure_recovery.py +0 -0
  145. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/azure_update_candidate.py +0 -0
  146. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/azure_writer.py +0 -0
  147. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/boundary_analysis.py +0 -0
  148. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/catalog_cli.py +0 -0
  149. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/cli.py +0 -0
  150. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/contracts.py +0 -0
  151. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/ctx.py +0 -0
  152. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/dashboard.py +0 -0
  153. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/decision_trace.py +0 -0
  154. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/discovery.py +0 -0
  155. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/execution_binding.py +0 -0
  156. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/execution_binding_cli.py +0 -0
  157. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/gate.py +0 -0
  158. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/gateway.py +0 -0
  159. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/goalstate.py +0 -0
  160. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/governance_entry.py +0 -0
  161. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/governance_entry_cli.py +0 -0
  162. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/hashing.py +0 -0
  163. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/host_precheck.py +0 -0
  164. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/import_skill.py +0 -0
  165. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/instance.py +0 -0
  166. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/loaders.py +0 -0
  167. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/__init__.py +0 -0
  168. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/audit.py +0 -0
  169. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/bootstrap.py +0 -0
  170. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/catalog.py +0 -0
  171. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/cli.py +0 -0
  172. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/client_cache.py +0 -0
  173. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/client_flow.py +0 -0
  174. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/context_registry.py +0 -0
  175. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/context_token.py +0 -0
  176. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/contracts.py +0 -0
  177. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/contribution_adoption.py +0 -0
  178. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/dist.py +0 -0
  179. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/gateway.py +0 -0
  180. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/governed_overlays.py +0 -0
  181. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/inbound_uploads.py +0 -0
  182. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/knowledge_edits.py +0 -0
  183. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/legacy_guidance.py +0 -0
  184. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/legacy_migration.py +0 -0
  185. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/legacy_resources.py +0 -0
  186. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/ownership.py +0 -0
  187. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/repository.py +0 -0
  188. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/schemas.py +0 -0
  189. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/server.py +0 -0
  190. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/setup.py +0 -0
  191. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/skill_edits.py +0 -0
  192. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/skill_maturity.py +0 -0
  193. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/subagent_startup.py +0 -0
  194. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp/update_gate_intake.py +0 -0
  195. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/mcp_tools.py +0 -0
  196. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/models/__init__.py +0 -0
  197. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/models/common.py +0 -0
  198. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/models/effective_bundle.py +0 -0
  199. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/models/human_evaluation.py +0 -0
  200. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/models/local_manifest.py +0 -0
  201. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/models/package_manifest.py +0 -0
  202. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/models/run_log.py +0 -0
  203. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/models/server_config.py +0 -0
  204. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/models/skill_definition.py +0 -0
  205. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/ownership.py +0 -0
  206. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/packmeta.py +0 -0
  207. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/plan_markdown.py +0 -0
  208. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/plan_observation.py +0 -0
  209. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/registry.py +0 -0
  210. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/reporting.py +0 -0
  211. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/reporting_profiles.py +0 -0
  212. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/repository_skills.py +0 -0
  213. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resolver.py +0 -0
  214. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resource_provider.py +0 -0
  215. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/attention_pet/client-state.schema.json +0 -0
  216. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/attention_pet/pet.css +0 -0
  217. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/attention_pet/pet.html +0 -0
  218. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/attention_pet/pet.js +0 -0
  219. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/contracts.json +0 -0
  220. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/current.json +0 -0
  221. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
  222. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
  223. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
  224. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
  225. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/model_body.md +0 -0
  226. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/startup_sources/011_startup_xref_routing.md +0 -0
  227. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/startup_sources/015_shared_memory_operations.md +0 -0
  228. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/startup_sources/016_uncertainty_protocol.md +0 -0
  229. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/startup_sources/017_base_and_xref_layering.md +0 -0
  230. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/base/startup_sources/053_context_direction_security_guard.md +0 -0
  231. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/migration/docs/guides/062_legacy_flow_skill_migration_guide.md +0 -0
  232. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/migration/knowledge/operations/130_legacy_flow_skill_migration_rules.md +0 -0
  233. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/migration/knowledge/organization/160_context_direction_guard_rules.md +0 -0
  234. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/migration/skills/os/legacy_flow_skill_migration/SKILL.v1.md +0 -0
  235. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/migration/skills/os/legacy_flow_skill_migration/references/legacy_flow_skill_migration_template.md +0 -0
  236. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/migration/skills/repository_adoption.json +0 -0
  237. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/resources/migration/tools/migrate_legacy_flow_skill.py +0 -0
  238. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/runlog.py +0 -0
  239. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/skill_definition.py +0 -0
  240. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/skill_definition_catalog.py +0 -0
  241. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/skill_definition_governance.py +0 -0
  242. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/skillmeta.py +0 -0
  243. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/skills_sync.py +0 -0
  244. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/structure_catalog.py +0 -0
  245. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/subagent_startup.py +0 -0
  246. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/tools/__init__.py +0 -0
  247. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/tools/__main__.py +0 -0
  248. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/v2_cli.py +0 -0
  249. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/wbs.py +0 -0
  250. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/work_management.py +0 -0
  251. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/work_management_view.py +0 -0
  252. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/workspace.py +0 -0
  253. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit/xref.py +0 -0
  254. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit.egg-info/dependency_links.txt +0 -0
  255. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit.egg-info/entry_points.txt +0 -0
  256. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit.egg-info/requires.txt +0 -0
  257. {xrefkit-0.7.0 → xrefkit-0.7.2}/xrefkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xrefkit
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit
5
5
  Author: synthaicode
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "xrefkit"
7
- version = "0.7.0"
7
+ version = "0.7.2"
8
8
  description = "Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,5 +1,7 @@
1
1
  """CI cleanup must retain release coverage and only cancel obsolete PR runs."""
2
2
  from pathlib import Path
3
+ import shutil
4
+ import subprocess
3
5
 
4
6
  import pytest
5
7
  import yaml
@@ -16,6 +18,36 @@ PACKAGES = {
16
18
  }
17
19
 
18
20
 
21
+ @pytest.mark.parametrize("stop_status,remains,expected", [
22
+ (0, False, 0), (1, False, 0), (1, True, 1), (2, False, 2),
23
+ ])
24
+ def test_core_updater_stop_handles_exit_race_without_hiding_errors(stop_status, remains, expected):
25
+ bash = shutil.which("bash")
26
+ if Path("C:/Program Files/Git/bin/bash.exe").is_file():
27
+ bash = "C:/Program Files/Git/bin/bash.exe"
28
+ if not bash:
29
+ pytest.skip("Bash is required to execute the workflow stop block")
30
+ steps = workflow("python-package.yml")["jobs"]["artifact-inspection"]["steps"]
31
+ update = next(s["run"] for s in steps if "sudo apt-get install --yes clamav" in s.get("run", ""))
32
+ block = update.split("# Package installation", 1)[1].split("# A failed definition update", 1)[0]
33
+ block = "# Package installation" + block
34
+ mocks = f"""
35
+ set -euo pipefail
36
+ calls=0
37
+ pgrep() {{
38
+ calls=$((calls + 1))
39
+ if [ "$calls" -eq 1 ]; then return 0; fi
40
+ return {0 if remains else 1}
41
+ }}
42
+ sudo() {{ return {stop_status}; }}
43
+ sleep() {{ :; }}
44
+ """
45
+ result = subprocess.run([bash, "-c", mocks + block + "\necho stop-complete\n"],
46
+ capture_output=True, text=True)
47
+ assert result.returncode == expected, result.stdout + result.stderr
48
+ assert ("stop-complete" in result.stdout) == (expected == 0)
49
+
50
+
19
51
  def workflow(filename):
20
52
  # BaseLoader preserves GitHub's `on` key instead of YAML 1.1's boolean True.
21
53
  return yaml.load((WORKFLOWS / filename).read_text(encoding="utf-8"), Loader=yaml.BaseLoader)
@@ -0,0 +1,240 @@
1
+ """Raw-byte preparation checks without human review, gate dispatch or adoption."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ import uuid
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ from test_subagent_startup import command
12
+ from xrefkit import governance_entry
13
+ from xrefkit.mcp import contribution_returns as cr
14
+ from xrefkit.mcp.audit import SessionRunBinding
15
+ from xrefkit.mcp.catalog import XRefCatalog
16
+ from xrefkit.mcp.update_gate_intake import prepare_contribution_gate
17
+ from xrefkit.ownership import Ownership, Zone
18
+
19
+
20
+ VARIANTS = [
21
+ pytest.param(b"# Rule\nalpha\nbeta\n", id="LF"),
22
+ pytest.param(b"# Rule\r\nalpha\r\nbeta\r\n", id="CRLF"),
23
+ pytest.param(b"# Rule\ralpha\rbeta\r", id="CR"),
24
+ pytest.param(b"# Rule\r\nalpha\nbeta\rgamma\r\n", id="mixed"),
25
+ pytest.param("# 検証\n条件:日本語\n".encode(), id="nonASCII-LF"),
26
+ pytest.param("# 検証\r\n条件:日本語\r\n".encode(), id="nonASCII-CRLF"),
27
+ ]
28
+
29
+
30
+ def _pending(root: Path, raw: bytes) -> tuple[Path, dict]:
31
+ content = "<!-- xid: BUG3TEST01 -->\n" + raw.decode("utf-8", errors="strict")
32
+ identity = str(uuid.uuid4())
33
+ cr.submit_contribution_return(
34
+ root,
35
+ binding=SessionRunBinding(str(uuid.uuid4()), str(uuid.uuid4()), "a" * 32, "sample"),
36
+ source_snapshot={"provider_id": "xrefkit-mcp", "provider_version": "test",
37
+ "package_id": None, "package_version": None, "skill_id": "sample",
38
+ "skill_content_hash": "b" * 64, "knowledge_versions": [],
39
+ "verification": "matched_current_catalog"},
40
+ contribution_id=identity, kind="knowledge", title="Byte fixture",
41
+ summary="Unapproved preparation fixture", files=[{"path": "rule.md", "content": content,
42
+ "content_hash": hashlib.sha256(content.encode()).hexdigest()}],
43
+ knowledge={"xid": "BUG3TEST01"}, proposed_target_path="knowledge/byte_fixture.md",
44
+ )
45
+ record = root / cr.STORE_RELATIVE / identity
46
+ return record, json.loads((record / "manifest.json").read_bytes())
47
+
48
+
49
+ def _inventory(root: Path) -> dict[str, bytes]:
50
+ return {p.relative_to(root).as_posix(): p.read_bytes()
51
+ for p in root.rglob("*") if p.is_file()}
52
+
53
+
54
+ @pytest.mark.parametrize("raw", VARIANTS)
55
+ def test_stored_files_preserve_original_bytes_and_repeated_reads(tmp_path: Path, raw: bytes) -> None:
56
+ record, manifest = _pending(tmp_path, raw)
57
+ original = (record / "files/rule.md").read_bytes()
58
+ before = _inventory(tmp_path)
59
+ assert cr.list_contribution_returns(tmp_path)[0]["status"] == "pending_review"
60
+ exported = cr.export_contribution_return(tmp_path, manifest["contribution_id"])
61
+ assert exported["review_bundle"]["files"][0]["content"].encode() == original
62
+ cr._verify_manifest_payload(record, manifest)
63
+ first = cr._stored_files(record, manifest)
64
+ assert first == cr._stored_files(record, manifest)
65
+ assert first[0].content.encode("utf-8") == original
66
+ assert first[0].content_hash == hashlib.sha256(original).hexdigest()
67
+ assert manifest["files"][0]["byte_count"] == len(original)
68
+ # This is the same encode used by gate intake, without bypassing its approval gate.
69
+ staged = tmp_path / "scratch/unapproved-candidate.md"
70
+ staged.parent.mkdir()
71
+ staged.write_bytes(first[0].content.encode("utf-8"))
72
+ assert staged.read_bytes() == original
73
+ staged.unlink()
74
+ assert _inventory(tmp_path) == before
75
+ assert not (record / "events").exists()
76
+ assert not (tmp_path / "knowledge").exists()
77
+
78
+
79
+ @pytest.mark.parametrize("corruption", ["hash", "byte_count", "invalid_utf8", "changed_bytes", "missing", "newline_only"])
80
+ def test_stored_files_reject_corrupt_candidate(tmp_path: Path, corruption: str) -> None:
81
+ record, manifest = _pending(tmp_path, b"# Rule\nbody\n")
82
+ stored = record / "files/rule.md"
83
+ if corruption == "hash":
84
+ manifest["files"][0]["content_hash"] = "0" * 64
85
+ elif corruption == "byte_count":
86
+ manifest["files"][0]["byte_count"] += 1
87
+ elif corruption == "invalid_utf8":
88
+ invalid = stored.read_bytes() + b"\xff"
89
+ stored.write_bytes(invalid)
90
+ manifest["files"][0].update(content_hash=hashlib.sha256(invalid).hexdigest(), byte_count=len(invalid))
91
+ elif corruption == "changed_bytes":
92
+ stored.write_bytes(stored.read_bytes() + b"tamper")
93
+ elif corruption == "newline_only":
94
+ stored.write_bytes(stored.read_bytes().replace(b"\n", b"\r\n"))
95
+ else:
96
+ stored.unlink()
97
+ before = _inventory(tmp_path)
98
+ with pytest.raises((ValueError, FileNotFoundError)):
99
+ cr._stored_files(record, manifest)
100
+ assert _inventory(tmp_path) == before
101
+
102
+
103
+ @pytest.mark.parametrize("corruption", ["payload", "extra_file", "deleted_file", "byte_count", "metadata_xid_tamper"])
104
+ def test_preparation_verifier_retains_manifest_and_tree_rejections(tmp_path: Path, corruption: str) -> None:
105
+ record, manifest = _pending(tmp_path, b"# Rule\nbody\n")
106
+ if corruption == "payload":
107
+ manifest["summary"] += " tampered"
108
+ elif corruption == "extra_file":
109
+ (record / "files/extra.md").write_bytes(b"extra")
110
+ elif corruption == "deleted_file":
111
+ (record / "files/rule.md").unlink()
112
+ elif corruption == "byte_count":
113
+ manifest["files"][0]["byte_count"] += 1
114
+ else:
115
+ manifest["kind_metadata"]["xid"] = "UNLISTED01"
116
+ before = _inventory(tmp_path)
117
+ with pytest.raises(ValueError):
118
+ cr._verify_manifest_payload(record, manifest)
119
+ assert _inventory(tmp_path) == before
120
+
121
+
122
+ class _ReadOnlyTargetRoot:
123
+ """Route a logical target to a real scratch file; never create canonical paths."""
124
+ def __init__(self, target: Path):
125
+ self.target = target
126
+
127
+ def joinpath(self, *parts: str) -> Path:
128
+ assert parts == ("knowledge", "byte_fixture.md")
129
+ return self.target
130
+
131
+
132
+ def _target_check(target: Path, raw: bytes, **changes) -> str:
133
+ xid = "BUG3TEST01"
134
+ logical = "knowledge/byte_fixture.md"
135
+ kwargs = dict(ownership=Ownership((Zone("kernel-content", "base", ("knowledge/",), True,
136
+ True, True, False),)),
137
+ existing_knowledge_xids={xid}, existing_knowledge_xid_locations={xid: {logical}},
138
+ repository_xid_paths={xid: {logical}}, allow_same_xid_after_prepare=True)
139
+ kwargs.update(changes)
140
+ return cr._validate_canonical_target(
141
+ _ReadOnlyTargetRoot(target),
142
+ {"kind": "knowledge", "kind_metadata": {"xid": xid},
143
+ "files": [{"content_hash": hashlib.sha256(raw).hexdigest(), "byte_count": len(raw)}]},
144
+ logical, **kwargs,
145
+ )
146
+
147
+
148
+ @pytest.mark.parametrize("raw", VARIANTS)
149
+ def test_recovery_target_matches_raw_hash_without_newline_conversion(tmp_path: Path, raw: bytes) -> None:
150
+ target = tmp_path / "scratch-target.md"
151
+ target.write_bytes(raw)
152
+ before = target.read_bytes()
153
+ assert _target_check(target, raw) == "knowledge/byte_fixture.md"
154
+ assert target.read_bytes() == before
155
+ assert not (tmp_path / "knowledge").exists()
156
+
157
+
158
+ @pytest.mark.parametrize("mismatch", ["target_version", "expected_hash", "duplicate_xid", "unlocated_xid", "no_prepare", "missing_target", "newline_only"])
159
+ def test_recovery_retains_collision_and_version_rejections(tmp_path: Path, mismatch: str) -> None:
160
+ original = b"# Original\nbody\n"
161
+ target = tmp_path / "scratch-target.md"
162
+ target.write_bytes(original)
163
+ expected = original
164
+ changes = {}
165
+ if mismatch == "target_version":
166
+ target.write_bytes(b"# Other version\nbody\n")
167
+ elif mismatch == "newline_only":
168
+ target.write_bytes(original.replace(b"\n", b"\r\n"))
169
+ elif mismatch == "expected_hash":
170
+ expected = b"# Different expected base\nbody\n"
171
+ elif mismatch == "duplicate_xid":
172
+ changes["repository_xid_paths"] = {"BUG3TEST01": {"knowledge/byte_fixture.md", "knowledge/other.md"}}
173
+ elif mismatch == "unlocated_xid":
174
+ changes["unlocated_existing_knowledge_xids"] = {"BUG3TEST01"}
175
+ elif mismatch == "no_prepare":
176
+ changes["allow_same_xid_after_prepare"] = False
177
+ else:
178
+ target.unlink()
179
+ before = _inventory(tmp_path)
180
+ with pytest.raises(ValueError, match="Knowledge XID already exists"):
181
+ _target_check(target, expected, **changes)
182
+ assert _inventory(tmp_path) == before
183
+
184
+
185
+ def test_gate_preparation_requires_real_approval_and_leaves_pending_state(tmp_path: Path) -> None:
186
+ record, manifest = _pending(tmp_path, b"# Rule\r\nbody\r\n")
187
+ before = _inventory(tmp_path)
188
+ with pytest.raises(RuntimeError, match="trusted human approval verifier"):
189
+ prepare_contribution_gate(tmp_path, contribution_id=manifest["contribution_id"],
190
+ log="work/not-created.md", binding_request={},
191
+ specialist_evidence=[], approval_verifier=None)
192
+ assert _inventory(tmp_path) == before
193
+ assert not (record / "events").exists()
194
+ assert not (tmp_path / "knowledge").exists()
195
+
196
+
197
+ def test_unsigned_gate_packet_revalidation_rejects_wrong_base_hash(tmp_path: Path) -> None:
198
+ log = tmp_path / "work/run.md"
199
+ code, output = command("workflow", "run", "--root", str(tmp_path), "--task",
200
+ "Unsigned base hash regression fixture", "--out", str(log),
201
+ "--completion-condition", "read-only packet checked", "--json")
202
+ assert code == 0, output
203
+ code, output = command("skill", "workitem", "--log", str(log), "--item", "WI-BASE",
204
+ "--text", "Validate unsigned frozen packet", "--completion-criterion",
205
+ "base mismatch rejected without writing target", "--status", "pending",
206
+ "--role", "instruction:executor", "--json")
207
+ assert code == 0, output
208
+ paths = ["work/candidate.md", *sorted(governance_entry.RULE_SOURCES)]
209
+ for name in paths:
210
+ path = tmp_path / name
211
+ path.parent.mkdir(parents=True, exist_ok=True)
212
+ path.write_bytes(b"# Isolated unsigned packet material\r\n")
213
+ request = {"work_item_id": "WI-BASE", "source_mode": "filesystem", "purpose": "base hash test",
214
+ "capability": "analysis", "tuning": "read-only fixture", "responsibility": "reject changed base",
215
+ "instruction_basis": "Authorized BUG3 regression scope", "scope_in": paths,
216
+ "scope_out": ["approval", "dispatch", "adoption"], "stop_conditions": ["unexpected write"],
217
+ "protocols": ["workflow"], "knowledge_access": {"mode": "on_demand", "catalog": "work/index.md"}}
218
+ packet = governance_entry.prepare(
219
+ tmp_path, kind="asset_update", log="work/run.md", binding_request=request, materials=paths,
220
+ payload={"candidate": "work/candidate.md", "target": "knowledge/not-created.md",
221
+ "rules": sorted(governance_entry.RULE_SOURCES), "checks": sorted(governance_entry.CORE_CHECKS),
222
+ "specialist_evidence": []},
223
+ )
224
+ assert packet["payload"]["expected_target_hash"] is None
225
+ before = _inventory(tmp_path)
226
+ governance_entry.revalidate(tmp_path, packet)
227
+ packet["payload"]["expected_target_hash"] = "0" * 64
228
+ # Packet digest is an unsigned content digest, never human authority or a host receipt.
229
+ packet["packet_hash"] = governance_entry.digest({k: v for k, v in packet.items() if k != "packet_hash"})
230
+ with pytest.raises(ValueError, match="target baseline changed"):
231
+ governance_entry.revalidate(tmp_path, packet)
232
+ assert _inventory(tmp_path) == before
233
+ assert not (tmp_path / "knowledge").exists()
234
+
235
+
236
+ def test_nonexistent_xid_lookup_does_not_invent_a_document(tmp_path: Path) -> None:
237
+ before = _inventory(tmp_path)
238
+ with pytest.raises(KeyError, match="document xid not found: ABCDEF987654"):
239
+ XRefCatalog.build(tmp_path).get_document_by_xid("ABCDEF987654")
240
+ assert _inventory(tmp_path) == before
@@ -1054,3 +1054,151 @@ def test_contribution_return_over_real_mcp_stdio(tmp_path: Path) -> None:
1054
1054
  inbox.parent.rmdir()
1055
1055
  except OSError:
1056
1056
  pass
1057
+
1058
+
1059
+ @pytest.mark.parametrize(
1060
+ "submitted_bytes",
1061
+ [
1062
+ pytest.param(b"<!-- xid: LOCAL123 -->\n\n# Local rule\n\nBody.\n", id="lf"),
1063
+ pytest.param(b"<!-- xid: LOCAL123 -->\r\n\r\n# Local rule\r\n\r\nBody.\r\n", id="crlf"),
1064
+ pytest.param("<!-- xid: LOCAL123 -->\r\r# 日本語のルール\r\r本文。🌱\r".encode("utf-8"), id="bare-cr-utf8"),
1065
+ pytest.param(b"<!-- xid: LOCAL123 -->\r\n\n# Local rule\n\r\nBody.\r\n", id="mixed-lf-crlf"),
1066
+ pytest.param("<!-- xid: LOCAL123 -->\n\n# 日本語のルール\n\n本文。🌱\n".encode("utf-8"), id="non-ascii-utf8"),
1067
+ ],
1068
+ )
1069
+ def test_exact_bytes_roundtrip_and_replay(tmp_path: Path, submitted_bytes: bytes) -> None:
1070
+ contribution_id = str(uuid.uuid4())
1071
+ arguments = dict(
1072
+ binding=binding(), source_snapshot=source(), contribution_id=contribution_id,
1073
+ kind="knowledge", title="Local rule", summary="Exact byte regression",
1074
+ files=[file("rule.md", submitted_bytes.decode("utf-8"))], knowledge={"xid": "LOCAL123"},
1075
+ )
1076
+ created = submit_contribution_return(tmp_path, **arguments)
1077
+ record = tmp_path / ".xrefkit/contribution-returns" / contribution_id
1078
+ stored_bytes = (record / "files/rule.md").read_bytes()
1079
+ manifest_bytes = (record / "manifest.json").read_bytes()
1080
+ manifest = json.loads(manifest_bytes)
1081
+ row = manifest["files"][0]
1082
+ assert stored_bytes == submitted_bytes
1083
+ assert hashlib.sha256(stored_bytes).hexdigest() == row["content_hash"]
1084
+ assert len(stored_bytes) == row["byte_count"]
1085
+ replay = submit_contribution_return(tmp_path, **arguments)
1086
+ assert created["created"] is True
1087
+ assert replay["created"] is False
1088
+ assert replay["idempotent_replay"] is True
1089
+ assert replay["payload_hash"] == created["payload_hash"]
1090
+ assert (record / "manifest.json").read_bytes() == manifest_bytes
1091
+ assert (record / "files/rule.md").read_bytes() == submitted_bytes
1092
+ listed = list_contribution_returns(tmp_path)
1093
+ assert len(listed) == 1
1094
+ assert listed[0]["files"][0] == row
1095
+ exported = export_contribution_return(tmp_path, contribution_id)
1096
+ exported_row = exported["review_bundle"]["files"][0]
1097
+ assert exported_row["content"].encode("utf-8") == submitted_bytes
1098
+ assert exported_row["content_hash"] == hashlib.sha256(submitted_bytes).hexdigest()
1099
+ assert exported_row["byte_count"] == len(submitted_bytes)
1100
+ assert exported["activation_performed"] is False
1101
+ assert (record / "manifest.json").read_bytes() == manifest_bytes
1102
+ assert (record / "files/rule.md").read_bytes() == stored_bytes
1103
+
1104
+
1105
+ @pytest.mark.parametrize("operation", ["list", "export"])
1106
+ @pytest.mark.parametrize(
1107
+ "corruption, error, message",
1108
+ [
1109
+ pytest.param("invalid_utf8", UnicodeDecodeError, "utf-8", id="invalid-utf8"),
1110
+ pytest.param("content_hash", ValueError, "file hash mismatch", id="bad-manifest-contenthash"),
1111
+ pytest.param("byte_count", ValueError, "file byte_count mismatch", id="bad-manifest-byte-count"),
1112
+ pytest.param("saved_bytes", ValueError, "file hash mismatch", id="post-save-byte-tamper"),
1113
+ ],
1114
+ )
1115
+ def test_exact_bytes_corruption_fails_closed(
1116
+ tmp_path: Path, operation: str, corruption: str, error: type[Exception], message: str,
1117
+ ) -> None:
1118
+ contribution_id, _ = pending_knowledge(tmp_path)
1119
+ record = tmp_path / ".xrefkit/contribution-returns" / contribution_id
1120
+ manifest_path = record / "manifest.json"
1121
+ stored = record / "files/rule.md"
1122
+ manifest = json.loads(manifest_path.read_bytes())
1123
+ if corruption == "invalid_utf8":
1124
+ invalid = stored.read_bytes() + b"\xff"
1125
+ stored.write_bytes(invalid)
1126
+ manifest["files"][0]["content_hash"] = hashlib.sha256(invalid).hexdigest()
1127
+ manifest["files"][0]["byte_count"] = len(invalid)
1128
+ elif corruption == "saved_bytes":
1129
+ stored.write_bytes(stored.read_bytes().replace(b"Body.", b"B0dy."))
1130
+ elif corruption == "content_hash":
1131
+ manifest["files"][0]["content_hash"] = "0" * 64
1132
+ else:
1133
+ manifest["files"][0]["byte_count"] += 1
1134
+ manifest_path.write_bytes(json.dumps(manifest).encode("utf-8"))
1135
+ before = {path.relative_to(record): path.read_bytes() for path in record.rglob("*") if path.is_file()}
1136
+ with pytest.raises(error, match=message):
1137
+ if operation == "list":
1138
+ list_contribution_returns(tmp_path)
1139
+ else:
1140
+ export_contribution_return(tmp_path, contribution_id)
1141
+ after = {path.relative_to(record): path.read_bytes() for path in record.rglob("*") if path.is_file()}
1142
+ assert after == before
1143
+
1144
+
1145
+ def test_exact_bytes_existing_receipt_is_verified_without_mutation(tmp_path: Path) -> None:
1146
+ # Fixed existing receipt snapshot: do not regenerate through the submission API.
1147
+ manifest = json.loads(r'''
1148
+ {
1149
+ "binding": {
1150
+ "mcp_session_id": "0bb9163e-91e9-46f4-960a-e55f3ef56f09",
1151
+ "repository_fingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1152
+ "run_id": "f35210cf-c853-40af-9be6-42a205bd5fb1",
1153
+ "skill_id": "sample"
1154
+ },
1155
+ "contribution_id": "00000000-0000-4000-8000-000000000001",
1156
+ "created_at": "2026-10-24T04:38:32+00:00",
1157
+ "files": [
1158
+ {
1159
+ "byte_count": 65,
1160
+ "content_hash": "dee606c610283ee8c609e70869a42cb609a3a60d4116874f5e96caf783405a75",
1161
+ "path": "rule.md"
1162
+ }
1163
+ ],
1164
+ "kind": "knowledge",
1165
+ "kind_metadata": {
1166
+ "xid": "LOCAL123"
1167
+ },
1168
+ "limits_version": 1,
1169
+ "payload_hash": "4bc1d451479786ac0a17622e3360d2454ac62200421b8d96c3d78d18a69a60df",
1170
+ "proposed_target_path": null,
1171
+ "schema": "xrefkit.contribution_return/v1",
1172
+ "source": {
1173
+ "knowledge_versions": [],
1174
+ "package_id": null,
1175
+ "package_version": null,
1176
+ "provider_id": "xrefkit-mcp",
1177
+ "provider_version": "test",
1178
+ "skill_content_hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1179
+ "skill_id": "sample",
1180
+ "verification": "matched_current_catalog"
1181
+ },
1182
+ "status": "pending_review",
1183
+ "summary": "Immutable exact-byte fixture",
1184
+ "title": "Existing receipt"
1185
+ }
1186
+ ''')
1187
+ contribution_id = manifest["contribution_id"]
1188
+ record = tmp_path / ".xrefkit/contribution-returns" / contribution_id
1189
+ (record / "files").mkdir(parents=True)
1190
+ (record / "manifest.json").write_bytes(json.dumps(manifest).encode("utf-8"))
1191
+ (record / "files/rule.md").write_bytes(
1192
+ "<!-- xid: LOCAL123 -->\r\n\r\n# Existing receipt\r\n\r\n保存済み。\r\n".encode("utf-8")
1193
+ )
1194
+ before = {path.relative_to(record): path.read_bytes() for path in record.rglob("*") if path.is_file()}
1195
+ manifest = json.loads(before[Path("manifest.json")])
1196
+ expected = before[Path("files/rule.md")]
1197
+ assert b"\r\n" in expected
1198
+ assert hashlib.sha256(expected).hexdigest() == manifest["files"][0]["content_hash"]
1199
+ assert len(expected) == manifest["files"][0]["byte_count"]
1200
+ assert list_contribution_returns(tmp_path)[0]["payload_hash"] == manifest["payload_hash"]
1201
+ exported = export_contribution_return(tmp_path, contribution_id)
1202
+ assert exported["review_bundle"]["files"][0]["content"].encode("utf-8") == expected
1203
+ after = {path.relative_to(record): path.read_bytes() for path in record.rglob("*") if path.is_file()}
1204
+ assert after == before
@@ -23,6 +23,9 @@ class EmbeddedStartupPackTests(unittest.TestCase):
23
23
  self.assertEqual(pack["stale_sources"], [])
24
24
  self.assertEqual(pack["based_on_hashes"], pack["source_hashes"])
25
25
  self.assertTrue(pack["body"])
26
+ self.assertIn("## Japanese response writing rules", pack["body"])
27
+ self.assertIn("progress updates, questions, explanations, reports, and final answers", pack["body"])
28
+ self.assertIn("no named invocation is required", pack["body"])
26
29
  self.assertFalse(any("is STALE" in text for text in context["client_instructions"]))
27
30
  if excluded:
28
31
  for name in excluded: