xrefkit 0.4.16__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 (213) hide show
  1. {xrefkit-0.4.16/xrefkit.egg-info → xrefkit-0.5.1}/PKG-INFO +60 -12
  2. xrefkit-0.5.1/README.md +130 -0
  3. {xrefkit-0.4.16 → xrefkit-0.5.1}/pyproject.toml +1 -1
  4. xrefkit-0.5.1/tests/test_artifact_selected_family.py +46 -0
  5. xrefkit-0.5.1/tests/test_attention_pet.py +257 -0
  6. xrefkit-0.5.1/tests/test_attention_pet_client_protocol.py +207 -0
  7. xrefkit-0.5.1/tests/test_attention_pet_codex_session.py +100 -0
  8. xrefkit-0.5.1/tests/test_attention_pet_fit.py +339 -0
  9. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_boundary_analysis.py +42 -1
  10. xrefkit-0.5.1/tests/test_brownfield_csharp_structure_definitions.py +44 -0
  11. xrefkit-0.5.1/tests/test_business_intake_conversation_definitions.py +46 -0
  12. xrefkit-0.5.1/tests/test_business_intake_definition.py +55 -0
  13. xrefkit-0.5.1/tests/test_catalog_preparation_definition.py +47 -0
  14. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_cli.py +9 -1
  15. xrefkit-0.5.1/tests/test_code_constraint_definition.py +47 -0
  16. xrefkit-0.5.1/tests/test_constraint_derivation_family_definition.py +68 -0
  17. xrefkit-0.5.1/tests/test_constraint_derivation_selected_family.py +44 -0
  18. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_contribution_returns.py +1 -0
  19. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_dashboard.py +68 -0
  20. xrefkit-0.5.1/tests/test_database_batch_definition_migration.py +90 -0
  21. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_decision_trace.py +28 -0
  22. xrefkit-0.5.1/tests/test_design_business_intake_selected_family.py +36 -0
  23. xrefkit-0.5.1/tests/test_design_planning_definition_migration.py +72 -0
  24. xrefkit-0.5.1/tests/test_dotnet_definition_candidate.py +103 -0
  25. xrefkit-0.5.1/tests/test_editorial_family_definition.py +58 -0
  26. xrefkit-0.5.1/tests/test_editorial_intake_definition.py +28 -0
  27. xrefkit-0.5.1/tests/test_editorial_selected_family.py +42 -0
  28. xrefkit-0.5.1/tests/test_embedded_startup_pack.py +45 -0
  29. xrefkit-0.5.1/tests/test_execution_binding.py +198 -0
  30. xrefkit-0.5.1/tests/test_existing_skill_definition_batch.py +27 -0
  31. xrefkit-0.5.1/tests/test_final_skilldefinition_batch.py +54 -0
  32. xrefkit-0.5.1/tests/test_flow_selected_family.py +44 -0
  33. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_gateway.py +45 -0
  34. xrefkit-0.5.1/tests/test_gateway_mcp.py +236 -0
  35. xrefkit-0.5.1/tests/test_gateway_skill_adapter.py +342 -0
  36. xrefkit-0.5.1/tests/test_gateway_work_items.py +542 -0
  37. xrefkit-0.5.1/tests/test_implementation_flow_definition.py +75 -0
  38. xrefkit-0.5.1/tests/test_implementation_review_selected_family.py +36 -0
  39. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_instruction_workflow.py +5 -0
  40. xrefkit-0.5.1/tests/test_language_review_definition.py +58 -0
  41. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_mcp_package_routing.py +79 -0
  42. xrefkit-0.5.1/tests/test_mcp_subagent_startup.py +452 -0
  43. xrefkit-0.5.1/tests/test_mcp_subagent_startup_integration.py +170 -0
  44. xrefkit-0.5.1/tests/test_os_family_definition.py +65 -0
  45. xrefkit-0.5.1/tests/test_os_knowledge_family_definition.py +54 -0
  46. xrefkit-0.5.1/tests/test_os_selected_family.py +45 -0
  47. xrefkit-0.5.1/tests/test_qa_report_definition.py +51 -0
  48. xrefkit-0.5.1/tests/test_security_definition_migration.py +37 -0
  49. xrefkit-0.5.1/tests/test_skill_definition.py +157 -0
  50. xrefkit-0.5.1/tests/test_skill_definition_governance.py +121 -0
  51. xrefkit-0.5.1/tests/test_skill_definition_mcp.py +262 -0
  52. xrefkit-0.5.1/tests/test_skill_definition_runtime.py +193 -0
  53. xrefkit-0.5.1/tests/test_subagent_startup.py +154 -0
  54. xrefkit-0.5.1/tests/test_subagent_startup_boundaries.py +153 -0
  55. xrefkit-0.5.1/tests/test_workflow_family_definition.py +60 -0
  56. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_xrefkit_v2_pipeline.py +15 -6
  57. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/__init__.py +1 -1
  58. xrefkit-0.5.1/xrefkit/attention_pet/__init__.py +1 -0
  59. xrefkit-0.5.1/xrefkit/attention_pet/__main__.py +78 -0
  60. xrefkit-0.5.1/xrefkit/attention_pet/client.py +80 -0
  61. xrefkit-0.5.1/xrefkit/attention_pet/client_protocol.py +136 -0
  62. xrefkit-0.5.1/xrefkit/attention_pet/codex_session.py +151 -0
  63. xrefkit-0.5.1/xrefkit/attention_pet/evaluator.py +222 -0
  64. xrefkit-0.5.1/xrefkit/attention_pet/model.py +225 -0
  65. xrefkit-0.5.1/xrefkit/attention_pet/presentation.py +195 -0
  66. xrefkit-0.5.1/xrefkit/attention_pet/profiles.py +31 -0
  67. xrefkit-0.5.1/xrefkit/attention_pet/scenarios.py +42 -0
  68. xrefkit-0.5.1/xrefkit/attention_pet/server.py +144 -0
  69. xrefkit-0.5.1/xrefkit/attention_pet/store.py +118 -0
  70. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/boundary_analysis.py +111 -60
  71. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/cli.py +5 -0
  72. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/dashboard.py +57 -0
  73. xrefkit-0.5.1/xrefkit/execution_binding.py +205 -0
  74. xrefkit-0.5.1/xrefkit/execution_binding_cli.py +20 -0
  75. xrefkit-0.5.1/xrefkit/gateway.py +1733 -0
  76. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/catalog.py +397 -32
  77. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/cli.py +14 -0
  78. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/contracts.py +8 -0
  79. xrefkit-0.5.1/xrefkit/mcp/gateway.py +169 -0
  80. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/schemas.py +10 -6
  81. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/server.py +183 -23
  82. xrefkit-0.5.1/xrefkit/mcp/startup_contract_pack.py +122 -0
  83. xrefkit-0.5.1/xrefkit/mcp/subagent_startup.py +316 -0
  84. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/operations_cli.py +55 -1
  85. xrefkit-0.5.1/xrefkit/resources/attention_pet/client-state.schema.json +306 -0
  86. xrefkit-0.5.1/xrefkit/resources/attention_pet/pet.css +97 -0
  87. xrefkit-0.5.1/xrefkit/resources/attention_pet/pet.html +42 -0
  88. xrefkit-0.5.1/xrefkit/resources/attention_pet/pet.js +207 -0
  89. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/011_startup_xref_routing.md +5 -3
  90. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/017_base_and_xref_layering.md +3 -1
  91. xrefkit-0.5.1/xrefkit/skill_definition.py +260 -0
  92. xrefkit-0.5.1/xrefkit/skill_definition_catalog.py +91 -0
  93. xrefkit-0.5.1/xrefkit/skill_definition_governance.py +133 -0
  94. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/skillrun.py +105 -16
  95. xrefkit-0.5.1/xrefkit/subagent_startup.py +166 -0
  96. {xrefkit-0.4.16 → xrefkit-0.5.1/xrefkit.egg-info}/PKG-INFO +60 -12
  97. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit.egg-info/SOURCES.txt +66 -1
  98. xrefkit-0.4.16/README.md +0 -82
  99. xrefkit-0.4.16/tests/test_base_sync_ownership.py +0 -123
  100. xrefkit-0.4.16/tests/test_gateway_mcp.py +0 -116
  101. xrefkit-0.4.16/xrefkit/gateway.py +0 -450
  102. xrefkit-0.4.16/xrefkit/mcp/gateway.py +0 -98
  103. xrefkit-0.4.16/xrefkit/mcp/startup_contract_pack.py +0 -170
  104. {xrefkit-0.4.16 → xrefkit-0.5.1}/LICENSE +0 -0
  105. {xrefkit-0.4.16 → xrefkit-0.5.1}/setup.cfg +0 -0
  106. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_brownfield_file_editing_protocol.py +0 -0
  107. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_calibration_lint.py +0 -0
  108. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_check_skill_knowledge_xids.py +0 -0
  109. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_collect_analyzer_sarif.py +0 -0
  110. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_convert_to_xrefkit_skill.py +0 -0
  111. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_cs_scope_probe.py +0 -0
  112. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_csharp_commonality.py +0 -0
  113. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_csharp_naming_profile.py +0 -0
  114. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_ctx.py +0 -0
  115. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_cutover_readiness.py +0 -0
  116. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_error_policy_audit.py +0 -0
  117. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_error_policy_locator.py +0 -0
  118. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_fm_multiroot.py +0 -0
  119. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_gate.py +0 -0
  120. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_goal_desired_state.py +0 -0
  121. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_host_precheck.py +0 -0
  122. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_human_evaluation.py +0 -0
  123. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_inbound_uploads.py +0 -0
  124. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_knowledge_relations_validator.py +0 -0
  125. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_mcp_setup.py +0 -0
  126. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_ownership.py +0 -0
  127. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_packmeta.py +0 -0
  128. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_project_quality_baseline.py +0 -0
  129. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_resource_provider.py +0 -0
  130. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_runtime_contracts.py +0 -0
  131. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_sarif_to_locator.py +0 -0
  132. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_skill_edits.py +0 -0
  133. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_skill_maturity_return.py +0 -0
  134. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_skill_runtime_audit.py +0 -0
  135. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_skillmeta.py +0 -0
  136. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_skills_sync.py +0 -0
  137. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_structure_catalog.py +0 -0
  138. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_wbs.py +0 -0
  139. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_xref.py +0 -0
  140. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_xrefkit_instance.py +0 -0
  141. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_xrefkit_tools.py +0 -0
  142. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_xrefkit_v2_discovery.py +0 -0
  143. {xrefkit-0.4.16 → xrefkit-0.5.1}/tests/test_xrefkit_v2_models.py +0 -0
  144. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/__main__.py +0 -0
  145. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/catalog_cli.py +0 -0
  146. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/contracts.py +0 -0
  147. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/ctx.py +0 -0
  148. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/decision_trace.py +0 -0
  149. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/discovery.py +0 -0
  150. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/gate.py +0 -0
  151. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/goalstate.py +0 -0
  152. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/hashing.py +0 -0
  153. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/host_precheck.py +0 -0
  154. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/import_skill.py +0 -0
  155. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/instance.py +0 -0
  156. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/loaders.py +0 -0
  157. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/__init__.py +0 -0
  158. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/audit.py +0 -0
  159. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/bootstrap.py +0 -0
  160. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/client_cache.py +0 -0
  161. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/client_flow.py +0 -0
  162. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/context_registry.py +0 -0
  163. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/context_token.py +0 -0
  164. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/contribution_adoption.py +0 -0
  165. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/contribution_returns.py +0 -0
  166. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/dist.py +0 -0
  167. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/inbound_uploads.py +0 -0
  168. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/knowledge_edits.py +0 -0
  169. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/ownership.py +0 -0
  170. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/repository.py +0 -0
  171. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/setup.py +0 -0
  172. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/skill_edits.py +0 -0
  173. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp/skill_maturity.py +0 -0
  174. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/mcp_tools.py +0 -0
  175. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/models/__init__.py +0 -0
  176. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/models/common.py +0 -0
  177. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/models/effective_bundle.py +0 -0
  178. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/models/human_evaluation.py +0 -0
  179. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/models/local_manifest.py +0 -0
  180. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/models/package_manifest.py +0 -0
  181. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/models/run_log.py +0 -0
  182. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/models/server_config.py +0 -0
  183. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/models/skill_definition.py +0 -0
  184. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/ownership.py +0 -0
  185. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/packmeta.py +0 -0
  186. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/registry.py +0 -0
  187. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resolver.py +0 -0
  188. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resource_provider.py +0 -0
  189. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/contracts.json +0 -0
  190. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/current.json +0 -0
  191. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
  192. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
  193. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
  194. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
  195. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/model_body.md +0 -0
  196. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/000_agent_entry.md +0 -0
  197. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/015_shared_memory_operations.md +0 -0
  198. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/016_uncertainty_protocol.md +0 -0
  199. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/resources/base/startup_sources/053_context_direction_security_guard.md +0 -0
  200. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/runlog.py +0 -0
  201. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/skillmeta.py +0 -0
  202. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/skills_sync.py +0 -0
  203. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/structure_catalog.py +0 -0
  204. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/tools/__init__.py +0 -0
  205. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/tools/__main__.py +0 -0
  206. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/v2_cli.py +0 -0
  207. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/wbs.py +0 -0
  208. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/workspace.py +0 -0
  209. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit/xref.py +0 -0
  210. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit.egg-info/dependency_links.txt +0 -0
  211. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit.egg-info/entry_points.txt +0 -0
  212. {xrefkit-0.4.16 → xrefkit-0.5.1}/xrefkit.egg-info/requires.txt +0 -0
  213. {xrefkit-0.4.16 → 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.4.16
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
@@ -42,11 +42,44 @@ Dynamic: license-file
42
42
  XRefKit is a framework for making AI-assisted work repeatable, reviewable, and
43
43
  handoff-ready.
44
44
 
45
- It helps teams turn domain procedures and knowledge into work that can be
46
- performed with explicit evidence, human judgment, and completion checks.
45
+ It helps teams structure domain procedures and knowledge so that work can
46
+ 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)
@@ -61,24 +94,34 @@ XRefKit provides a repository and runtime model for addressing these problems.
61
94
 
62
95
  ## What it provides
63
96
 
64
- - **Skills** — reusable procedures for a defined kind of work
65
- - **Knowledge** — source-backed domain facts and local rules
66
- - **Workflow protocol** — recorded progress, deterministic verification, and
67
- closure checks
97
+ - **Skills** — reusable procedures expressed as SkillDefinitions
98
+ - **Knowledge** — source-backed domain facts and local rules resolved by XID
99
+ when a Skill run needs them
100
+ - **Workflow protocol** — recorded progress, deterministic workflow-record
101
+ verification, and closure checks
68
102
  - **Evidence and handoffs** — outputs, judgments, concerns, and decisions that
69
103
  remain reviewable after the work is done
70
- - **Skill Run Dashboard** — inspect Skill execution status, referenced XIDs,
71
- evidence, judgments, concerns, and handoffs to improve the auditability of
72
- results
104
+ - **Skill Run Dashboard** — inspect recorded Skill execution state, definition
105
+ identity, runtime binding, referenced XIDs, evidence, judgments, concerns,
106
+ and handoffs
73
107
  - **XIDs** — stable references for connecting procedures, knowledge, and
74
108
  supporting documents
75
109
 
76
110
  The package includes the resolver, Skill runtime, workflow controls, client
77
111
  tools, and an optional MCP adapter.
78
112
 
113
+ Current Skill authoring targets the one-document `skill_definition_v1` format.
114
+ Existing `legacy_split_v1` Skills and unversioned historical run logs remain
115
+ readable during migration. The [Workflow Runtime Binding](docs/core/contracts/111_workflow_runtime_binding.md#xid-8D50A972BA9F)
116
+ defines how instruction-derived `capability`, `tuning`, `responsibility`, and
117
+ `execution_mode` are captured; these are not fixed SkillDefinition properties.
118
+ Knowledge used by a Skill remains separate and is resolved from the XID catalog
119
+ when needed.
120
+
79
121
  The [Skill Run Dashboard](docs/guides/086_skill_run_observation_dashboard_usage.md#xid-4A4763A2DE63)
80
- helps people trace how a Skill run used its referenced XIDs and recorded its
81
- evidence before accepting or handing off the result.
122
+ helps people inspect recorded XID retrieval and use together with the run's
123
+ evidence before a human accepts or hands off the result. The Dashboard reports
124
+ recorded state and missing observations; it does not accept output quality.
82
125
 
83
126
  ## Quick start
84
127
 
@@ -104,12 +147,17 @@ python -m pip install "xrefkit[mcp]"
104
147
  xrefkit mcp serve --repo . --transport stdio
105
148
  ```
106
149
 
150
+ This command starts the optional catalog and runtime MCP adapter. This checkout
151
+ does not implement management upload, staging, sealing, review, or adoption of
152
+ uploaded SkillDefinition bytes into the active catalog.
153
+
107
154
  ## Where to go next
108
155
 
109
156
  - [Install XRefKit and register Skill Packages](docs/guides/089_xrefkit_package_first_registration.md#xid-4F8C2A7D1E90)
110
157
  - [Understand the workflow protocol](docs/guides/087_workflow_protocol_sequence_for_humans.md#xid-E8B4D2F19A63)
111
158
  - [Use an instruction-backed workflow](docs/guides/088_instruction_workflow_protocol.md#xid-9F4C2A7D1B60)
112
159
  - [Understand Skills and Knowledge](docs/core/models/052_flow_capability_skill_knowledge_model.md#xid-91C4B7E2D5A8)
160
+ - [Read the SkillDefinition v1 contract](docs/core/contracts/096_skill_definition_contract.md#xid-E6A19D4B72C3)
113
161
  - [Author a Skill with xref](docs/guides/013_skill_authoring_with_xref.md#xid-3DB05A0F5F5B)
114
162
  - [Browse the complete documentation index](docs/000_index.md#xid-56DD6EB68343)
115
163
 
@@ -0,0 +1,130 @@
1
+ # XRefKit
2
+
3
+ XRefKit is a framework for making AI-assisted work repeatable, reviewable, and
4
+ handoff-ready.
5
+
6
+ It helps teams structure domain procedures and knowledge so that work can
7
+ record evidence, preserve human judgment, and apply explicit completion checks.
8
+
9
+ ## Why XRefKit?
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
+
44
+ Using AI for real work creates recurring operating problems:
45
+
46
+ ![Why XRefKit is needed](human-docs/en/assets/why_xrefkit_needed/whatis_xrefkit.png)
47
+
48
+ - the AI can act from incomplete context or unsupported guesses
49
+ - procedures, domain facts, and judgment criteria get mixed together in prompts
50
+ - execution, checking, and handoff collapse into one opaque step
51
+ - work becomes hard to continue across agents, humans, or sessions
52
+ - outputs may lack evidence, closure discipline, or auditability
53
+
54
+ XRefKit provides a repository and runtime model for addressing these problems.
55
+
56
+ ## What it provides
57
+
58
+ - **Skills** — reusable procedures expressed as SkillDefinitions
59
+ - **Knowledge** — source-backed domain facts and local rules resolved by XID
60
+ when a Skill run needs them
61
+ - **Workflow protocol** — recorded progress, deterministic workflow-record
62
+ verification, and closure checks
63
+ - **Evidence and handoffs** — outputs, judgments, concerns, and decisions that
64
+ remain reviewable after the work is done
65
+ - **Skill Run Dashboard** — inspect recorded Skill execution state, definition
66
+ identity, runtime binding, referenced XIDs, evidence, judgments, concerns,
67
+ and handoffs
68
+ - **XIDs** — stable references for connecting procedures, knowledge, and
69
+ supporting documents
70
+
71
+ The package includes the resolver, Skill runtime, workflow controls, client
72
+ tools, and an optional MCP adapter.
73
+
74
+ Current Skill authoring targets the one-document `skill_definition_v1` format.
75
+ Existing `legacy_split_v1` Skills and unversioned historical run logs remain
76
+ readable during migration. The [Workflow Runtime Binding](docs/core/contracts/111_workflow_runtime_binding.md#xid-8D50A972BA9F)
77
+ defines how instruction-derived `capability`, `tuning`, `responsibility`, and
78
+ `execution_mode` are captured; these are not fixed SkillDefinition properties.
79
+ Knowledge used by a Skill remains separate and is resolved from the XID catalog
80
+ when needed.
81
+
82
+ The [Skill Run Dashboard](docs/guides/086_skill_run_observation_dashboard_usage.md#xid-4A4763A2DE63)
83
+ helps people inspect recorded XID retrieval and use together with the run's
84
+ evidence before a human accepts or hands off the result. The Dashboard reports
85
+ recorded state and missing observations; it does not accept output quality.
86
+
87
+ ## Quick start
88
+
89
+ XRefKit requires Python 3.11 or later.
90
+
91
+ ```powershell
92
+ python -m pip install xrefkit
93
+ xrefkit init
94
+ xrefkit --help
95
+ ```
96
+
97
+ For local development:
98
+
99
+ ```powershell
100
+ python -m pip install -e .
101
+ xrefkit init
102
+ ```
103
+
104
+ For the optional MCP server:
105
+
106
+ ```powershell
107
+ python -m pip install "xrefkit[mcp]"
108
+ xrefkit mcp serve --repo . --transport stdio
109
+ ```
110
+
111
+ This command starts the optional catalog and runtime MCP adapter. This checkout
112
+ does not implement management upload, staging, sealing, review, or adoption of
113
+ uploaded SkillDefinition bytes into the active catalog.
114
+
115
+ ## Where to go next
116
+
117
+ - [Install XRefKit and register Skill Packages](docs/guides/089_xrefkit_package_first_registration.md#xid-4F8C2A7D1E90)
118
+ - [Understand the workflow protocol](docs/guides/087_workflow_protocol_sequence_for_humans.md#xid-E8B4D2F19A63)
119
+ - [Use an instruction-backed workflow](docs/guides/088_instruction_workflow_protocol.md#xid-9F4C2A7D1B60)
120
+ - [Understand Skills and Knowledge](docs/core/models/052_flow_capability_skill_knowledge_model.md#xid-91C4B7E2D5A8)
121
+ - [Read the SkillDefinition v1 contract](docs/core/contracts/096_skill_definition_contract.md#xid-E6A19D4B72C3)
122
+ - [Author a Skill with xref](docs/guides/013_skill_authoring_with_xref.md#xid-3DB05A0F5F5B)
123
+ - [Browse the complete documentation index](docs/000_index.md#xid-56DD6EB68343)
124
+
125
+ ## Security
126
+
127
+ XRefKit does not require provider API keys to explore or install the package.
128
+ Do not commit secrets, API keys, access tokens, `.env` files, or provider
129
+ credentials. Authenticate external AI tools through their official provider
130
+ mechanisms.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "xrefkit"
7
- version = "0.4.16"
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,46 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from xrefkit.mcp.catalog import XRefCatalog
6
+ from xrefkit.skill_definition import load_skill_definition
7
+
8
+
9
+ CASES = [
10
+ ("xlsx_spec_traceability", "xlsx_spec_traceability", "A7C3E9D2F610", "2D1B6A9E7C40", "5A7C2D4E9130"),
11
+ ("marketing-explainer-video", "marketing_explainer_video", "B8F1A6C4D720", "5E147B19D33D", "A6B923E41178"),
12
+ ("marketing_slide_png", "marketing_slide_png", "C2E7B9F5A130", "91B67D4AC2F3", "5E2C4A90D711"),
13
+ ("import_skill", "import_skill", "D5A8C1E6B740", "7C2A492D2B72", "1DF4555E1B02"),
14
+ ]
15
+
16
+
17
+ @pytest.mark.parametrize("directory,skill_id,xid,body_xid,meta_xid", CASES)
18
+ def test_artifact_v1_parse_concrete_metadata_and_legacy(directory, skill_id, xid, body_xid, meta_xid):
19
+ repo = Path(__file__).resolve().parents[1]
20
+ relative = Path(f"skills/{directory}/SKILL.v1.md")
21
+ definition = load_skill_definition(repo / relative)
22
+ metadata = definition["metadata"]
23
+ assert metadata["skill_id"] == skill_id
24
+ assert metadata["xid"] == xid
25
+ assert set(metadata["aliases"]) == {body_xid, meta_xid}
26
+ assert metadata["inputs"] and metadata["outputs"]
27
+ assert metadata["control_refs"] == []
28
+ assert "CAP-" not in definition["method"]
29
+ assert "## Reporting Contract" not in definition["method"]
30
+ assert "skill_doc: `./SKILL.md`" in (repo / relative.parent / "meta.md").read_text()
31
+
32
+
33
+ def test_artifact_v1_explicit_batch_selection_and_knowledge_resolution():
34
+ repo = Path(__file__).resolve().parents[1]
35
+ paths = [Path(f"skills/{directory}/SKILL.v1.md") for directory, *_ in CASES]
36
+ catalog = XRefCatalog.build(repo, skill_definition_paths=paths)
37
+ ids = {case[1] for case in CASES}
38
+ entries = {entry.skill_id: entry for entry in catalog.skills if entry.skill_id in ids}
39
+ assert set(entries) == ids
40
+ assert all(entry.definition_format == "skill_definition_v1" for entry in entries.values())
41
+ for directory, skill_id, *_ in CASES:
42
+ definition = load_skill_definition(repo / f"skills/{directory}/SKILL.v1.md")
43
+ need_ids = [need["id"] for need in definition["metadata"]["knowledge_needs"]]
44
+ result = catalog.resolve_skill_knowledge(skill_id, need_ids)
45
+ assert result["unresolved_activation"] == []
46
+ assert result["unsatisfied_required"] == []
@@ -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)