xrefkit 0.5.0__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. {xrefkit-0.5.0 → xrefkit-0.5.1}/PKG-INFO +34 -1
  2. {xrefkit-0.5.0 → xrefkit-0.5.1}/README.md +33 -0
  3. {xrefkit-0.5.0 → xrefkit-0.5.1}/pyproject.toml +1 -1
  4. xrefkit-0.5.1/tests/test_attention_pet.py +257 -0
  5. xrefkit-0.5.1/tests/test_attention_pet_client_protocol.py +207 -0
  6. xrefkit-0.5.1/tests/test_attention_pet_codex_session.py +100 -0
  7. xrefkit-0.5.1/tests/test_attention_pet_fit.py +339 -0
  8. xrefkit-0.5.1/tests/test_embedded_startup_pack.py +45 -0
  9. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/__init__.py +1 -1
  10. xrefkit-0.5.1/xrefkit/attention_pet/__init__.py +1 -0
  11. xrefkit-0.5.1/xrefkit/attention_pet/__main__.py +78 -0
  12. xrefkit-0.5.1/xrefkit/attention_pet/client.py +80 -0
  13. xrefkit-0.5.1/xrefkit/attention_pet/client_protocol.py +136 -0
  14. xrefkit-0.5.1/xrefkit/attention_pet/codex_session.py +151 -0
  15. xrefkit-0.5.1/xrefkit/attention_pet/evaluator.py +222 -0
  16. xrefkit-0.5.1/xrefkit/attention_pet/model.py +225 -0
  17. xrefkit-0.5.1/xrefkit/attention_pet/presentation.py +195 -0
  18. xrefkit-0.5.1/xrefkit/attention_pet/profiles.py +31 -0
  19. xrefkit-0.5.1/xrefkit/attention_pet/scenarios.py +42 -0
  20. xrefkit-0.5.1/xrefkit/attention_pet/server.py +144 -0
  21. xrefkit-0.5.1/xrefkit/attention_pet/store.py +118 -0
  22. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/cli.py +5 -0
  23. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/catalog.py +15 -1
  24. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/startup_contract_pack.py +8 -6
  25. xrefkit-0.5.1/xrefkit/resources/attention_pet/client-state.schema.json +306 -0
  26. xrefkit-0.5.1/xrefkit/resources/attention_pet/pet.css +97 -0
  27. xrefkit-0.5.1/xrefkit/resources/attention_pet/pet.html +42 -0
  28. xrefkit-0.5.1/xrefkit/resources/attention_pet/pet.js +207 -0
  29. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit.egg-info/PKG-INFO +34 -1
  30. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit.egg-info/SOURCES.txt +21 -0
  31. {xrefkit-0.5.0 → xrefkit-0.5.1}/LICENSE +0 -0
  32. {xrefkit-0.5.0 → xrefkit-0.5.1}/setup.cfg +0 -0
  33. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_artifact_selected_family.py +0 -0
  34. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_boundary_analysis.py +0 -0
  35. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_brownfield_csharp_structure_definitions.py +0 -0
  36. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_brownfield_file_editing_protocol.py +0 -0
  37. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_business_intake_conversation_definitions.py +0 -0
  38. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_business_intake_definition.py +0 -0
  39. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_calibration_lint.py +0 -0
  40. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_catalog_preparation_definition.py +0 -0
  41. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_check_skill_knowledge_xids.py +0 -0
  42. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_cli.py +0 -0
  43. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_code_constraint_definition.py +0 -0
  44. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_collect_analyzer_sarif.py +0 -0
  45. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_constraint_derivation_family_definition.py +0 -0
  46. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_constraint_derivation_selected_family.py +0 -0
  47. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_contribution_returns.py +0 -0
  48. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_convert_to_xrefkit_skill.py +0 -0
  49. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_cs_scope_probe.py +0 -0
  50. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_csharp_commonality.py +0 -0
  51. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_csharp_naming_profile.py +0 -0
  52. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_ctx.py +0 -0
  53. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_cutover_readiness.py +0 -0
  54. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_dashboard.py +0 -0
  55. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_database_batch_definition_migration.py +0 -0
  56. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_decision_trace.py +0 -0
  57. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_design_business_intake_selected_family.py +0 -0
  58. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_design_planning_definition_migration.py +0 -0
  59. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_dotnet_definition_candidate.py +0 -0
  60. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_editorial_family_definition.py +0 -0
  61. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_editorial_intake_definition.py +0 -0
  62. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_editorial_selected_family.py +0 -0
  63. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_error_policy_audit.py +0 -0
  64. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_error_policy_locator.py +0 -0
  65. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_execution_binding.py +0 -0
  66. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_existing_skill_definition_batch.py +0 -0
  67. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_final_skilldefinition_batch.py +0 -0
  68. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_flow_selected_family.py +0 -0
  69. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_fm_multiroot.py +0 -0
  70. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_gate.py +0 -0
  71. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_gateway.py +0 -0
  72. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_gateway_mcp.py +0 -0
  73. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_gateway_skill_adapter.py +0 -0
  74. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_gateway_work_items.py +0 -0
  75. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_goal_desired_state.py +0 -0
  76. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_host_precheck.py +0 -0
  77. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_human_evaluation.py +0 -0
  78. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_implementation_flow_definition.py +0 -0
  79. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_implementation_review_selected_family.py +0 -0
  80. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_inbound_uploads.py +0 -0
  81. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_instruction_workflow.py +0 -0
  82. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_knowledge_relations_validator.py +0 -0
  83. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_language_review_definition.py +0 -0
  84. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_mcp_package_routing.py +0 -0
  85. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_mcp_setup.py +0 -0
  86. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_mcp_subagent_startup.py +0 -0
  87. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_mcp_subagent_startup_integration.py +0 -0
  88. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_os_family_definition.py +0 -0
  89. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_os_knowledge_family_definition.py +0 -0
  90. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_os_selected_family.py +0 -0
  91. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_ownership.py +0 -0
  92. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_packmeta.py +0 -0
  93. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_project_quality_baseline.py +0 -0
  94. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_qa_report_definition.py +0 -0
  95. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_resource_provider.py +0 -0
  96. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_runtime_contracts.py +0 -0
  97. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_sarif_to_locator.py +0 -0
  98. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_security_definition_migration.py +0 -0
  99. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_skill_definition.py +0 -0
  100. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_skill_definition_governance.py +0 -0
  101. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_skill_definition_mcp.py +0 -0
  102. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_skill_definition_runtime.py +0 -0
  103. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_skill_edits.py +0 -0
  104. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_skill_maturity_return.py +0 -0
  105. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_skill_runtime_audit.py +0 -0
  106. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_skillmeta.py +0 -0
  107. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_skills_sync.py +0 -0
  108. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_structure_catalog.py +0 -0
  109. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_subagent_startup.py +0 -0
  110. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_subagent_startup_boundaries.py +0 -0
  111. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_wbs.py +0 -0
  112. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_workflow_family_definition.py +0 -0
  113. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_xref.py +0 -0
  114. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_xrefkit_instance.py +0 -0
  115. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_xrefkit_tools.py +0 -0
  116. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_xrefkit_v2_discovery.py +0 -0
  117. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_xrefkit_v2_models.py +0 -0
  118. {xrefkit-0.5.0 → xrefkit-0.5.1}/tests/test_xrefkit_v2_pipeline.py +0 -0
  119. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/__main__.py +0 -0
  120. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/boundary_analysis.py +0 -0
  121. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/catalog_cli.py +0 -0
  122. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/contracts.py +0 -0
  123. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/ctx.py +0 -0
  124. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/dashboard.py +0 -0
  125. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/decision_trace.py +0 -0
  126. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/discovery.py +0 -0
  127. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/execution_binding.py +0 -0
  128. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/execution_binding_cli.py +0 -0
  129. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/gate.py +0 -0
  130. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/gateway.py +0 -0
  131. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/goalstate.py +0 -0
  132. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/hashing.py +0 -0
  133. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/host_precheck.py +0 -0
  134. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/import_skill.py +0 -0
  135. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/instance.py +0 -0
  136. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/loaders.py +0 -0
  137. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/__init__.py +0 -0
  138. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/audit.py +0 -0
  139. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/bootstrap.py +0 -0
  140. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/cli.py +0 -0
  141. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/client_cache.py +0 -0
  142. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/client_flow.py +0 -0
  143. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/context_registry.py +0 -0
  144. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/context_token.py +0 -0
  145. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/contracts.py +0 -0
  146. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/contribution_adoption.py +0 -0
  147. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/contribution_returns.py +0 -0
  148. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/dist.py +0 -0
  149. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/gateway.py +0 -0
  150. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/inbound_uploads.py +0 -0
  151. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/knowledge_edits.py +0 -0
  152. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/ownership.py +0 -0
  153. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/repository.py +0 -0
  154. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/schemas.py +0 -0
  155. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/server.py +0 -0
  156. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/setup.py +0 -0
  157. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/skill_edits.py +0 -0
  158. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/skill_maturity.py +0 -0
  159. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp/subagent_startup.py +0 -0
  160. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/mcp_tools.py +0 -0
  161. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/models/__init__.py +0 -0
  162. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/models/common.py +0 -0
  163. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/models/effective_bundle.py +0 -0
  164. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/models/human_evaluation.py +0 -0
  165. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/models/local_manifest.py +0 -0
  166. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/models/package_manifest.py +0 -0
  167. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/models/run_log.py +0 -0
  168. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/models/server_config.py +0 -0
  169. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/models/skill_definition.py +0 -0
  170. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/operations_cli.py +0 -0
  171. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/ownership.py +0 -0
  172. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/packmeta.py +0 -0
  173. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/registry.py +0 -0
  174. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resolver.py +0 -0
  175. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resource_provider.py +0 -0
  176. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/contracts.json +0 -0
  177. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/current.json +0 -0
  178. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
  179. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
  180. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
  181. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
  182. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/model_body.md +0 -0
  183. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/000_agent_entry.md +0 -0
  184. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/011_startup_xref_routing.md +0 -0
  185. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/015_shared_memory_operations.md +0 -0
  186. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/016_uncertainty_protocol.md +0 -0
  187. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/017_base_and_xref_layering.md +0 -0
  188. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/053_context_direction_security_guard.md +0 -0
  189. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/runlog.py +0 -0
  190. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/skill_definition.py +0 -0
  191. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/skill_definition_catalog.py +0 -0
  192. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/skill_definition_governance.py +0 -0
  193. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/skillmeta.py +0 -0
  194. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/skillrun.py +0 -0
  195. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/skills_sync.py +0 -0
  196. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/structure_catalog.py +0 -0
  197. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/subagent_startup.py +0 -0
  198. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/tools/__init__.py +0 -0
  199. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/tools/__main__.py +0 -0
  200. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/v2_cli.py +0 -0
  201. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/wbs.py +0 -0
  202. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/workspace.py +0 -0
  203. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit/xref.py +0 -0
  204. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit.egg-info/dependency_links.txt +0 -0
  205. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit.egg-info/entry_points.txt +0 -0
  206. {xrefkit-0.5.0 → xrefkit-0.5.1}/xrefkit.egg-info/requires.txt +0 -0
  207. {xrefkit-0.5.0 → xrefkit-0.5.1}/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.5.0
3
+ Version: 0.5.1
4
4
  Summary: Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit
5
5
  Author: synthaicode
6
6
  License: MIT License
@@ -47,6 +47,39 @@ record evidence, preserve human judgment, and apply explicit completion checks.
47
47
 
48
48
  ## Why XRefKit?
49
49
 
50
+ Experimental local tool: [Attention Pet](projects/attention-pet/README.md) helps
51
+ people assess capability sufficiency and lower inference-cost candidates separately.
52
+ Its Model Fit / Cost Fit estimates use uncalibrated profiles; it does not measure internal
53
+ attention or switch models automatically.
54
+
55
+ ### Codex-only preview: Attention Pet
56
+
57
+ Attention Pet is a small local companion for Codex work. It reads the current
58
+ Codex chat's local session record, estimates whether the selected model meets
59
+ the visible work requirements, and shows the result through a compact animated
60
+ pet. It also shows lower inference-cost comparison candidates when the
61
+ experimental profiles support one. The estimate is observational and does not
62
+ change the Codex model, reasoning level, or chat.
63
+
64
+ Run it from a Codex terminal in this repository so `CODEX_THREAD_ID` identifies
65
+ the current chat:
66
+
67
+ ```powershell
68
+ python -m xrefkit.attention_pet serve --port 8769
69
+ ```
70
+
71
+ Open the printed `http://127.0.0.1:8769/` address in the Codex browser panel.
72
+ The display follows the browser's preferred language: Japanese is used for a
73
+ Japanese preference, with English as the fallback. Stop the process with
74
+ Ctrl+C.
75
+
76
+ This Codex preview is bound to the chat that launched it. Selecting another
77
+ chat does not move the Pet automatically; launch it again from that chat when
78
+ you want a separate view. The model and reasoning dropdowns only compare
79
+ estimates and do not change the active Codex settings. See the
80
+ [Attention Pet guide](projects/attention-pet/README.md) for interpretation,
81
+ limitations, client integration, and API details.
82
+
50
83
  Using AI for real work creates recurring operating problems:
51
84
 
52
85
  ![Why XRefKit is needed](human-docs/en/assets/why_xrefkit_needed/whatis_xrefkit.png)
@@ -8,6 +8,39 @@ record evidence, preserve human judgment, and apply explicit completion checks.
8
8
 
9
9
  ## Why XRefKit?
10
10
 
11
+ Experimental local tool: [Attention Pet](projects/attention-pet/README.md) helps
12
+ people assess capability sufficiency and lower inference-cost candidates separately.
13
+ Its Model Fit / Cost Fit estimates use uncalibrated profiles; it does not measure internal
14
+ attention or switch models automatically.
15
+
16
+ ### Codex-only preview: Attention Pet
17
+
18
+ Attention Pet is a small local companion for Codex work. It reads the current
19
+ Codex chat's local session record, estimates whether the selected model meets
20
+ the visible work requirements, and shows the result through a compact animated
21
+ pet. It also shows lower inference-cost comparison candidates when the
22
+ experimental profiles support one. The estimate is observational and does not
23
+ change the Codex model, reasoning level, or chat.
24
+
25
+ Run it from a Codex terminal in this repository so `CODEX_THREAD_ID` identifies
26
+ the current chat:
27
+
28
+ ```powershell
29
+ python -m xrefkit.attention_pet serve --port 8769
30
+ ```
31
+
32
+ Open the printed `http://127.0.0.1:8769/` address in the Codex browser panel.
33
+ The display follows the browser's preferred language: Japanese is used for a
34
+ Japanese preference, with English as the fallback. Stop the process with
35
+ Ctrl+C.
36
+
37
+ This Codex preview is bound to the chat that launched it. Selecting another
38
+ chat does not move the Pet automatically; launch it again from that chat when
39
+ you want a separate view. The model and reasoning dropdowns only compare
40
+ estimates and do not change the active Codex settings. See the
41
+ [Attention Pet guide](projects/attention-pet/README.md) for interpretation,
42
+ limitations, client integration, and API details.
43
+
11
44
  Using AI for real work creates recurring operating problems:
12
45
 
13
46
  ![Why XRefKit is needed](human-docs/en/assets/why_xrefkit_needed/whatis_xrefkit.png)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "xrefkit"
7
- version = "0.5.0"
7
+ version = "0.5.1"
8
8
  description = "Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1,257 @@
1
+ import json
2
+ import threading
3
+ from urllib.error import HTTPError
4
+ from urllib.request import Request, urlopen
5
+
6
+ import pytest
7
+ from pydantic import ValidationError
8
+
9
+ from xrefkit.attention_pet.evaluator import Weights, evaluate
10
+ from xrefkit.attention_pet.model import Conversation, Edge, Item, WorkingSet, extract
11
+ from xrefkit.attention_pet.scenarios import scenario
12
+ from xrefkit.attention_pet.server import make_server
13
+ from xrefkit.attention_pet.store import Store
14
+
15
+
16
+ def fixture(name="A", step=1):
17
+ return WorkingSet.model_validate(scenario(name, step)["workingSet"])
18
+
19
+
20
+ def test_scenario_a_independent_requests_grow_gradually():
21
+ scores = [evaluate(fixture("A", s))["ral"] for s in range(1, 6)]
22
+ assert scores == sorted(scores)
23
+ assert max(b - a for a, b in zip(scores, scores[1:])) < 10
24
+
25
+
26
+ def test_scenario_b_relationships_cost_more_than_independent_items():
27
+ a, b = evaluate(fixture("A", 5)), evaluate(fixture("B", 5))
28
+ assert a["features"]["active_items"] == b["features"]["active_items"]
29
+ assert b["ral"] > a["ral"] + 20
30
+ assert b["features"]["dependency_edges"] > 0
31
+
32
+
33
+ def test_scenario_c_low_load_can_have_wrong_trajectory():
34
+ states = [evaluate(fixture("C", s)) for s in range(1, 4)]
35
+ assert len({s["ral"] for s in states}) == 1
36
+ assert states[-1]["ral"] < 40
37
+ assert states[-1]["trajectoryStability"] < states[0]["trajectoryStability"]
38
+ assert states[-1]["state"] == "WRONG TRAJECTORY"
39
+ assert states[-1]["expression"] == "wrong_trajectory"
40
+ assert "restart" in states[-1]["recommendedActions"]
41
+
42
+
43
+ def test_scenario_d_summary_lowers_load_without_erasing_constraints():
44
+ store = Store()
45
+ before = store.submit(fixture("D"))
46
+ result = store.recover("summarize", before["state"]["observedAt"])
47
+ assert before["state"]["ral"] >= 75
48
+ assert result["state"]["ral"] < before["state"]["ral"]
49
+ assert result["state"]["deltaRal"] < 0
50
+ assert result["state"]["features"]["constraint"] == before["state"]["features"]["constraint"]
51
+ assert result["state"]["features"]["dependency_edges"] == before["state"]["features"]["dependency_edges"]
52
+ assert len(result["workingSet"]["items"]) == len(before["workingSet"]["items"])
53
+
54
+
55
+ def test_scenario_e_restart_requires_fresh_validation_to_recover():
56
+ store = Store()
57
+ before = store.submit(fixture("E", 3))
58
+ handoff = store.recover("restart", before["state"]["observedAt"])
59
+ assert handoff["state"] == before["state"] # Download is not a context restart.
60
+ fresh = fixture("E", 4)
61
+ fresh.observations = [o for o in fresh.observations if o.context != fresh.contextId]
62
+ unknown = store.submit(fresh)
63
+ assert unknown["state"]["trajectoryStability"] is None
64
+ recovered = store.submit(fixture("E", 5))
65
+ assert recovered["state"]["trajectoryStability"] > before["state"]["trajectoryStability"]
66
+ assert recovered["state"]["ral"] == before["state"]["ral"]
67
+
68
+
69
+ def test_trend_uses_time_window_and_reports_feature_deltas():
70
+ store = Store()
71
+ a = store.submit(fixture("B", 1))
72
+ b = store.submit(fixture("B", 4))
73
+ assert b["state"]["deltaRal"] == b["state"]["ral"] - a["state"]["ral"]
74
+ assert b["state"]["trend"] == "rapid_rise"
75
+ assert {c["feature"] for c in b["state"]["changes"]} >= {"constraint", "dependency_edges"}
76
+ ws = fixture("B", 5)
77
+ ws.observedAt = 2000.0
78
+ assert store.submit(ws)["state"]["trend"] != "rapid_rise"
79
+
80
+
81
+ def test_unknown_trajectory_does_not_override_load_and_task_switch_does_not_compare():
82
+ ws = fixture()
83
+ ws.observations = []
84
+ assert evaluate(ws)["state"] == "UNKNOWN"
85
+ assert evaluate(ws)["expression"] == "stable"
86
+ store = Store()
87
+ store.submit(ws)
88
+ assert store.submit(fixture("B"))["state"]["deltaRal"] is None
89
+
90
+
91
+ @pytest.mark.parametrize("coverage", ["partial", "reviewed"])
92
+ @pytest.mark.parametrize("step,expression", [(1,"stable"),(3,"loaded"),(5,"strained")])
93
+ def test_structural_load_remains_visible_without_trajectory(coverage, step, expression):
94
+ ws = fixture("B", step)
95
+ ws.coverage = coverage
96
+ ws.observations = []
97
+ state = evaluate(ws)
98
+ assert state["trajectoryStability"] is None
99
+ assert state["state"] == "UNKNOWN"
100
+ assert state["expression"] == expression
101
+
102
+
103
+ def test_fix_retains_relations_and_does_not_fake_lower_load():
104
+ store = Store()
105
+ before = store.submit(fixture("B", 3))
106
+ fixed = store.recover("fix", before["state"]["observedAt"])
107
+ assert fixed["state"]["ral"] == before["state"]["ral"]
108
+ assert all(i["fixed"] for i in fixed["workingSet"]["items"] if i["kind"] == "decision")
109
+
110
+
111
+ def test_summary_preserves_history_referenced_by_active_items():
112
+ ws = fixture("D")
113
+ ws.dependencies.append(Edge(source="d0", target="h0-0", evidence="needed evidence"))
114
+ store = Store()
115
+ store.submit(ws)
116
+ result = store.recover("summarize", ws.observedAt)
117
+ assert next(i for i in result["workingSet"]["items"] if i["id"] == "h0-0")["status"] == "active"
118
+
119
+
120
+ @pytest.mark.parametrize("action", ["split", "externalize", "resolve", "rebase", "restart"])
121
+ def test_handoff_actions_do_not_claim_to_change_live_context(action):
122
+ store = Store()
123
+ before = store.submit(fixture())
124
+ result = store.recover(action, before["state"]["observedAt"])
125
+ assert result["recovery"]["effect"] == "handoff_created"
126
+ assert result["recovery"]["handoff"]["workingSet"] == before["workingSet"]
127
+ assert result["state"] == before["state"]
128
+
129
+
130
+ @pytest.mark.parametrize("mutation", [
131
+ lambda d: d.update(unexpected="field"),
132
+ lambda d: d.update(observedAt=float("nan")),
133
+ lambda d: d.update(items=d["items"] * 2),
134
+ lambda d: d.update(dependencies=[{"source":"absent","target":"d0","evidence":"bad"}]),
135
+ lambda d: d["items"][0].update(depth=-1),
136
+ lambda d: d["items"][0].update(fixed="true"),
137
+ ])
138
+ def test_invalid_boundary_data_is_rejected(mutation):
139
+ data = fixture().model_dump()
140
+ mutation(data)
141
+ with pytest.raises(ValidationError):
142
+ WorkingSet.model_validate(data)
143
+
144
+
145
+ def test_timestamp_replay_and_stale_recovery():
146
+ store = Store()
147
+ ws = fixture()
148
+ first = store.submit(ws)
149
+ assert store.submit(ws) == first
150
+ stale = ws.model_copy(deep=True)
151
+ stale.observedAt = 1.0
152
+ with pytest.raises(ValueError, match="timestamps"):
153
+ store.submit(stale)
154
+ with pytest.raises(ValueError, match="refresh"):
155
+ store.recover("fix", 1.0)
156
+
157
+
158
+ def test_extract_upserts_status_and_marks_unannotated_turn_partial():
159
+ data = scenario("A")["conversation"]
160
+ updated = dict(data["turns"][0]["items"][0], status="done")
161
+ data["turns"].append({"id":"update", "text":"Requirement complete", "items":[updated]})
162
+ ws = extract(Conversation.model_validate(data))
163
+ assert len(ws.items) == 5 and ws.items[0].status == "done"
164
+ data["turns"].append({"id":"unannotated", "text":"A new unknown requirement"})
165
+ assert extract(Conversation.model_validate(data)).coverage == "partial"
166
+ data["turns"][0]["items"][0]["source"] = "missing"
167
+ with pytest.raises(ValueError, match="supplied turn"):
168
+ extract(Conversation.model_validate(data))
169
+
170
+
171
+ def test_persistence_restart_and_failed_write_do_not_mutate_state(tmp_path, monkeypatch):
172
+ from pathlib import Path
173
+ path = tmp_path / "session.json"
174
+ store = Store(path)
175
+ store.submit(fixture("B", 1))
176
+ before = store.submit(fixture("B", 2))
177
+ assert Store(path).view() == before
178
+ def fail(*args):
179
+ raise OSError("disk full")
180
+ monkeypatch.setattr(Path, "replace", fail)
181
+ with pytest.raises(OSError):
182
+ store.submit(fixture("B", 3))
183
+ assert store.view() == before
184
+
185
+
186
+ def test_weights_validation():
187
+ with pytest.raises(ValueError):
188
+ Weights(working_set=-1)
189
+ with pytest.raises(ValueError):
190
+ Weights(working_set=float("nan"))
191
+
192
+
193
+ def test_high_load_alone_never_recommends_restart():
194
+ state = evaluate(fixture("D"))
195
+ assert state["ral"] >= 75 and state["state"] == "HIGH LOAD"
196
+ assert "restart" not in state["recommendedActions"]
197
+
198
+
199
+ @pytest.mark.parametrize("count,band", [(1,"stable"),(16,"loaded"),(24,"strained"),(30,"high_pressure"),(36,"unstable")])
200
+ def test_five_visual_bands_and_critical_override(count, band):
201
+ ws = fixture()
202
+ ws.items = [Item(id=f"g-{i}", kind="goal", text="goal", source="test") for i in range(count)]
203
+ weights = Weights(working_set=100, dependency_complexity=0, constraint_density=0,
204
+ decision_depth=0, conflict_pressure=0, context_dispersion=0)
205
+ assert evaluate(ws, weights=weights)["expression"] == band
206
+ ws.observations = fixture("C", 3).observations
207
+ result = evaluate(ws, weights=weights)
208
+ assert result["state"] == ("CRITICAL" if count >= 30 else "WRONG TRAJECTORY")
209
+
210
+
211
+ def test_empty_partial_snapshot_stays_unknown_and_bounds_hold():
212
+ ws = WorkingSet(taskId="empty",contextId="c",observedAt=1.0,items=[])
213
+ result = evaluate(ws)
214
+ assert result["ral"] == 0 and result["trajectoryStability"] is None
215
+ assert result["state"] == "UNKNOWN"
216
+
217
+
218
+ def test_http_api_auth_origin_validation_recovery_and_assets():
219
+ server, launch = make_server(Store())
220
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
221
+ thread.start()
222
+ base, token = launch.rstrip("/"), server.write_token
223
+ def request(path, data=None, auth=True, origin=None):
224
+ headers = {"Authorization":f"Bearer {token}"} if auth else {}
225
+ if origin:
226
+ headers["Origin"] = origin
227
+ if data is not None:
228
+ headers["Content-Type"] = "application/json"
229
+ req = Request(base + path, json.dumps(data).encode() if data is not None else None, headers)
230
+ with urlopen(req, timeout=5) as response:
231
+ return response.read()
232
+ try:
233
+ assert b"Attention Pet" in request("/", auth=False)
234
+ for path in ("/pet.js", "/pet.css"):
235
+ assert request(path, auth=False)
236
+ assert json.loads(request("/api/state", auth=False))["state"] is None
237
+ with pytest.raises(HTTPError) as error:
238
+ request("/api/snapshot", fixture().model_dump(), auth=False)
239
+ assert error.value.code == 401
240
+ with pytest.raises(HTTPError) as error:
241
+ request("/api/snapshot", fixture().model_dump(), origin="https://example.com")
242
+ assert error.value.code == 403
243
+ with pytest.raises(HTTPError) as error:
244
+ request("/api/snapshot", {"ral":99})
245
+ assert error.value.code == 400
246
+ before = json.loads(request("/api/conversation", scenario("D")["conversation"]))
247
+ after = json.loads(request("/api/recover", {"action":"summarize", "expectedObservedAt":before["state"]["observedAt"]}))
248
+ assert after["state"]["ral"] < before["state"]["ral"]
249
+ assert json.loads(request("/api/state"))["state"] == after["state"]
250
+ with pytest.raises(HTTPError) as error:
251
+ request("/api/demo", {"scenario":"C", "step":3})
252
+ assert error.value.code == 404
253
+ assert json.loads(request("/api/state"))["state"] == after["state"]
254
+ finally:
255
+ server.shutdown()
256
+ server.server_close()
257
+ thread.join(timeout=5)
@@ -0,0 +1,207 @@
1
+ """Same-host client protocol and session switching."""
2
+
3
+ import json
4
+ import subprocess
5
+ import sys
6
+ import threading
7
+ from pathlib import Path
8
+ from urllib.error import HTTPError
9
+ from urllib.request import Request, urlopen
10
+
11
+ import pytest
12
+
13
+ from xrefkit.attention_pet.client_protocol import ClientStateSource, PROTOCOL_VERSION
14
+ from xrefkit.attention_pet.client_protocol import ClientState
15
+ from xrefkit.attention_pet.client import AttentionPetClient, ClientProtocolError
16
+ from xrefkit.attention_pet.evaluator import Weights
17
+ from xrefkit.attention_pet.server import make_server
18
+ from xrefkit.attention_pet.store import Store
19
+
20
+
21
+ def payload(session: str, revision: int, item_count: int = 1) -> dict:
22
+ observed = float(100 + revision)
23
+ return {
24
+ "protocolVersion": PROTOCOL_VERSION,
25
+ "provider": "codex-desktop",
26
+ "clientInstanceId": "window-1",
27
+ "sessionId": session,
28
+ "activationRevision": revision,
29
+ "observedAt": observed,
30
+ "model": "gpt-6-sol",
31
+ "reasoning": "medium",
32
+ "capabilities": {
33
+ "activeSessionChanged": True,
34
+ "promptSubmitted": True,
35
+ "modelSelected": True,
36
+ "fullConversationAccess": False,
37
+ },
38
+ "workingSet": {
39
+ "schemaVersion": 1,
40
+ "taskId": "task-" + session,
41
+ "contextId": session,
42
+ "observedAt": observed,
43
+ "coverage": "partial",
44
+ "items": [
45
+ {"id": f"item-{index}", "kind": "goal", "text": "structured item",
46
+ "source": f"event-{index}"}
47
+ for index in range(item_count)
48
+ ],
49
+ "dependencies": [],
50
+ "observations": [],
51
+ },
52
+ }
53
+
54
+
55
+ def test_handshake_authentication_and_active_session_switching(tmp_path):
56
+ source = ClientStateSource(tmp_path / "sessions", Weights())
57
+ server, launch = make_server(Store(), source=source)
58
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
59
+ thread.start()
60
+ base = launch.rstrip("/")
61
+ auth = {"Authorization": f"Bearer {server.write_token}"}
62
+
63
+ def get(path, headers=None):
64
+ with urlopen(Request(base + path, headers=headers or {}), timeout=5) as response:
65
+ return json.load(response)
66
+
67
+ def post(path, value):
68
+ request = Request(base + path, json.dumps(value).encode(),
69
+ {**auth, "Content-Type": "application/json"})
70
+ with urlopen(request, timeout=5) as response:
71
+ return json.load(response)
72
+
73
+ try:
74
+ with pytest.raises(HTTPError) as error:
75
+ get("/api/client/handshake")
76
+ assert error.value.code == 401
77
+ hello = get("/api/client/handshake", auth)
78
+ assert hello == {
79
+ "service": "xrefkit-attention-pet",
80
+ "protocolVersion": PROTOCOL_VERSION,
81
+ "instanceId": server.instance_id,
82
+ "capabilities": ["active-session", "working-set-snapshot"],
83
+ "writeAuthentication": "bearer",
84
+ "hostScope": "loopback",
85
+ }
86
+
87
+ accepted = post("/api/active-session", payload("session-a", 1, 1))
88
+ assert accepted["accepted"] is True
89
+ assert accepted["instanceId"] == server.instance_id
90
+ assert post("/api/active-session", payload("session-a", 1, 1)) == accepted
91
+ first = get("/api/state")
92
+ assert first["source"]["sessionId"] == "session-a"
93
+ assert first["source"]["activationRevision"] == 1
94
+ assert first["source"]["profile"] == "sol"
95
+ assert first["state"]["features"]["active_items"] == 1
96
+ english = get("/api/state?lang=en")
97
+ assert english["fit"]["presentation"]["headline"].startswith("For the work currently visible")
98
+ assert english["fit"]["presentation"]["coverageLabel"] == "Partial"
99
+
100
+ with pytest.raises(HTTPError) as error:
101
+ get("/api/state?lang=fr")
102
+ assert error.value.code == 400
103
+
104
+ post("/api/active-session", payload("session-b", 2, 2))
105
+ second = get("/api/state")
106
+ assert second["source"]["sessionId"] == "session-b"
107
+ assert second["state"]["features"]["active_items"] == 2
108
+
109
+ with pytest.raises(HTTPError) as error:
110
+ post("/api/active-session", payload("session-a", 1, 1))
111
+ assert error.value.code == 409
112
+ assert get("/api/state")["source"]["sessionId"] == "session-b"
113
+ finally:
114
+ server.shutdown()
115
+ server.server_close()
116
+ thread.join(timeout=5)
117
+
118
+
119
+ def test_client_contract_rejects_mismatched_session_and_manual_mode(tmp_path):
120
+ source = ClientStateSource(tmp_path / "sessions", Weights())
121
+ server, launch = make_server(Store(), source=source)
122
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
123
+ thread.start()
124
+ base = launch.rstrip("/")
125
+ auth = {"Authorization": f"Bearer {server.write_token}", "Content-Type": "application/json"}
126
+ try:
127
+ invalid = payload("session-a", 1)
128
+ invalid["workingSet"]["contextId"] = "different"
129
+ request = Request(base + "/api/active-session", json.dumps(invalid).encode(), auth)
130
+ with pytest.raises(HTTPError) as error:
131
+ urlopen(request, timeout=5)
132
+ assert error.value.code == 400
133
+ finally:
134
+ server.shutdown()
135
+ server.server_close()
136
+ thread.join(timeout=5)
137
+
138
+
139
+ def test_provider_client_verifies_loopback_identity_and_acknowledgement(tmp_path):
140
+ source = ClientStateSource(tmp_path / "sessions", Weights())
141
+ server, launch = make_server(Store(), source=source)
142
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
143
+ thread.start()
144
+ try:
145
+ client = AttentionPetClient(launch, server.write_token,
146
+ instance_id=server.instance_id)
147
+ assert client.handshake()["service"] == "xrefkit-attention-pet"
148
+ state = ClientState.model_validate(payload("session-client", 3))
149
+ assert client.activate(state)["activeSessionId"] == "session-client"
150
+
151
+ with pytest.raises(ClientProtocolError, match="instance"):
152
+ AttentionPetClient(launch, server.write_token,
153
+ instance_id="different").handshake()
154
+ with pytest.raises(ClientProtocolError, match="request failed"):
155
+ AttentionPetClient(launch, "wrong-token").handshake()
156
+ finally:
157
+ server.shutdown()
158
+ server.server_close()
159
+ thread.join(timeout=5)
160
+
161
+ for invalid in ["https://127.0.0.1:8769", "http://example.com:8769",
162
+ "http://127.0.0.1", "http://127.0.0.1:8769/?token=value"]:
163
+ with pytest.raises(ValueError, match="127.0.0.1"):
164
+ AttentionPetClient(invalid, "token")
165
+
166
+ manual, manual_launch = make_server(Store())
167
+ thread = threading.Thread(target=manual.serve_forever, daemon=True)
168
+ thread.start()
169
+ try:
170
+ request = Request(manual_launch.rstrip("/") + "/api/active-session",
171
+ json.dumps(payload("session-a", 1)).encode(),
172
+ {"Authorization": f"Bearer {manual.write_token}",
173
+ "Content-Type": "application/json"})
174
+ with pytest.raises(HTTPError) as error:
175
+ urlopen(request, timeout=5)
176
+ assert error.value.code == 409
177
+ finally:
178
+ manual.shutdown()
179
+ manual.server_close()
180
+ thread.join(timeout=5)
181
+
182
+
183
+ def test_client_schema_and_launch_record_are_machine_readable(tmp_path):
184
+ root = Path(__file__).resolve().parents[1]
185
+ schema = json.loads((root / "projects/attention-pet/schema/client-state.schema.json").read_text(encoding="utf-8"))
186
+ assert schema == ClientState.model_json_schema()
187
+ packaged = json.loads((root / "xrefkit/resources/attention_pet/client-state.schema.json").read_text(encoding="utf-8"))
188
+ assert packaged == schema
189
+
190
+ process = subprocess.Popen(
191
+ [sys.executable, "-m", "xrefkit.attention_pet", "serve", "--client",
192
+ "--port", "0", "--session", str(tmp_path / "sessions")],
193
+ cwd=root, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
194
+ encoding="utf-8",
195
+ )
196
+ try:
197
+ launch = json.loads(process.stdout.readline())
198
+ assert launch["service"] == "xrefkit-attention-pet"
199
+ assert launch["protocolVersion"] == PROTOCOL_VERSION
200
+ assert launch["endpoint"].startswith("http://127.0.0.1:")
201
+ assert launch["writeToken"]
202
+ client = AttentionPetClient(launch["endpoint"], launch["writeToken"],
203
+ instance_id=launch["instanceId"])
204
+ assert client.handshake()["instanceId"] == launch["instanceId"]
205
+ finally:
206
+ process.terminate()
207
+ process.wait(timeout=10)
@@ -0,0 +1,100 @@
1
+ """The live adapter observes one chat without persisting its message text."""
2
+ import json
3
+ import threading
4
+ from urllib.error import HTTPError
5
+ from urllib.request import Request, urlopen
6
+
7
+ from xrefkit.attention_pet.codex_session import CodexSessionSource, find_rollout
8
+ from xrefkit.attention_pet.server import make_server
9
+ from xrefkit.attention_pet.store import Store
10
+
11
+
12
+ THREAD = "01a0dd3a-aead-7352-a2c4-10e36ffa84dc"
13
+
14
+
15
+ def append(path, timestamp, payload_type, payload):
16
+ with path.open("a", encoding="utf-8") as stream:
17
+ stream.write(json.dumps({"timestamp": timestamp, "type": payload_type,
18
+ "payload": payload}, ensure_ascii=False) + "\n")
19
+
20
+
21
+ def user(path, timestamp, message_id, request):
22
+ append(path, timestamp, "response_item", {"type": "message", "role": "user", "id": message_id,
23
+ "content": [{"type": "input_text", "text":
24
+ "<in-app-browser-context>ambient text</in-app-browser-context>\n"
25
+ "## My request:\n" + request}]})
26
+
27
+
28
+ def test_one_bound_chat_updates_incrementally_without_saving_text(tmp_path):
29
+ home = tmp_path / ".codex"
30
+ log_dir = home / "sessions" / "2026" / "09" / "26"
31
+ log_dir.mkdir(parents=True)
32
+ log = log_dir / f"rollout-{THREAD}.jsonl"
33
+ append(log, "2026-09-26T12:00:00Z", "turn_context", {"model": "gpt-6-sol", "effort": "medium"})
34
+ user(log, "2026-09-26T12:00:00.5Z", "msg-startup", "# AGENTS.md instructions for a repository")
35
+ user(log, "2026-09-26T12:00:01Z", "msg-first", "最初の依頼")
36
+ source = CodexSessionSource(find_rollout(THREAD, home), THREAD)
37
+ saved = tmp_path / "projected.json"
38
+ store = Store(saved)
39
+ source.sync(store)
40
+ first = store.view()
41
+ assert source.selection() == ("sol", "standard")
42
+ assert source.status()["observedUserTurns"] == 1
43
+ assert first["state"]["coverage"] == "partial"
44
+ assert len(first["workingSet"]["items"]) == 1
45
+ assert "最初の依頼" not in saved.read_text(encoding="utf-8")
46
+ source.sync(store)
47
+ assert store.view() == first
48
+ user(log, "2026-09-26T12:00:02Z", "msg-second", "この修正は不要")
49
+ source.sync(store)
50
+ second = store.view()
51
+ assert second["state"]["observedAt"] > first["state"]["observedAt"]
52
+ assert second["state"]["features"]["active_items"] == 2
53
+ assert second["state"]["features"]["dependency_edges"] == 1
54
+ assert second["state"]["trajectoryEvidence"] == []
55
+ assert "この修正は不要" not in saved.read_text(encoding="utf-8")
56
+ append(log, "2026-09-26T12:00:03Z", "turn_context", {"model": "gpt-6-terra", "effort": "low"})
57
+ source.sync(store)
58
+ assert source.selection() == ("terra", "light")
59
+ assert store.view() == second
60
+ append(log, "2026-09-26T12:00:04Z", "turn_context", {"model": "unrecognized-model", "effort": "medium"})
61
+ source.sync(store)
62
+ assert source.selection() == ("", "standard")
63
+ assert store.view() == second
64
+
65
+
66
+ def test_live_api_defaults_to_observed_model_allows_comparison_and_rejects_mutation(tmp_path):
67
+ log = tmp_path / f"rollout-{THREAD}.jsonl"
68
+ append(log, "2026-09-26T12:00:00Z", "turn_context", {"model": "gpt-6-sol", "effort": "medium"})
69
+ user(log, "2026-09-26T12:00:01Z", "msg-first", "作業を確認する")
70
+ source = CodexSessionSource(log, THREAD)
71
+ store = Store(tmp_path / "projected.json")
72
+ server, launch = make_server(store, source=source)
73
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
74
+ thread.start()
75
+ base, token = launch.rstrip("/"), server.write_token
76
+ headers = {"Authorization": f"Bearer {token}"}
77
+ try:
78
+ with urlopen(Request(base + "/api/state?model=astra&reasoning=high"), timeout=5) as response:
79
+ value = json.load(response)
80
+ assert value["source"]["mode"] == "codex-chat"
81
+ assert value["source"]["observedUserTurns"] == 1
82
+ assert value["source"]["profile"] == "sol"
83
+ assert value["source"]["reasoning"] == "standard"
84
+ assert value["fit"]["selected"]["model"] == "astra"
85
+ assert value["fit"]["selected"]["reasoning"] == "high"
86
+ with urlopen(Request(base + "/api/state?model=&reasoning=standard"), timeout=5) as response:
87
+ defaulted = json.load(response)
88
+ assert defaulted["fit"]["selected"]["model"] == "sol"
89
+ before = store.view()
90
+ req = Request(base + "/api/snapshot", b"{}", {**headers, "Content-Type": "application/json"})
91
+ try:
92
+ urlopen(req, timeout=5)
93
+ assert False, "manual mutation should be blocked"
94
+ except HTTPError as error:
95
+ assert error.code == 409
96
+ assert store.view() == before
97
+ finally:
98
+ server.shutdown()
99
+ server.server_close()
100
+ thread.join(timeout=5)