xrefkit 0.5.1__tar.gz → 0.6.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.
- {xrefkit-0.5.1 → xrefkit-0.6.1}/PKG-INFO +22 -3
- {xrefkit-0.5.1 → xrefkit-0.6.1}/README.md +21 -2
- {xrefkit-0.5.1 → xrefkit-0.6.1}/pyproject.toml +1 -1
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_attention_pet_client_protocol.py +97 -2
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_attention_pet_codex_session.py +3 -3
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_attention_pet_fit.py +69 -12
- xrefkit-0.6.1/tests/test_legacy_migration.py +231 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/__init__.py +1 -1
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/client_protocol.py +7 -2
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/codex_session.py +2 -2
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/evaluator.py +31 -21
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/model.py +21 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/presentation.py +10 -10
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/profiles.py +25 -5
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/server.py +30 -9
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/__init__.py +4 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/catalog.py +4 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/contracts.py +22 -0
- xrefkit-0.6.1/xrefkit/mcp/legacy_migration.py +383 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/schemas.py +2 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/server.py +28 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/setup.py +27 -34
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/attention_pet/pet.css +2 -2
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/attention_pet/pet.html +9 -9
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/attention_pet/pet.js +58 -16
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit.egg-info/PKG-INFO +22 -3
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit.egg-info/SOURCES.txt +2 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/LICENSE +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/setup.cfg +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_artifact_selected_family.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_attention_pet.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_boundary_analysis.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_brownfield_csharp_structure_definitions.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_brownfield_file_editing_protocol.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_business_intake_conversation_definitions.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_business_intake_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_calibration_lint.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_catalog_preparation_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_check_skill_knowledge_xids.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_cli.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_code_constraint_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_collect_analyzer_sarif.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_constraint_derivation_family_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_constraint_derivation_selected_family.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_contribution_returns.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_convert_to_xrefkit_skill.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_cs_scope_probe.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_csharp_commonality.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_csharp_naming_profile.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_ctx.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_cutover_readiness.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_dashboard.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_database_batch_definition_migration.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_decision_trace.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_design_business_intake_selected_family.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_design_planning_definition_migration.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_dotnet_definition_candidate.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_editorial_family_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_editorial_intake_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_editorial_selected_family.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_embedded_startup_pack.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_error_policy_audit.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_error_policy_locator.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_execution_binding.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_existing_skill_definition_batch.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_final_skilldefinition_batch.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_flow_selected_family.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_fm_multiroot.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_gate.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_gateway.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_gateway_mcp.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_gateway_skill_adapter.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_gateway_work_items.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_goal_desired_state.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_host_precheck.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_human_evaluation.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_implementation_flow_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_implementation_review_selected_family.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_inbound_uploads.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_instruction_workflow.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_knowledge_relations_validator.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_language_review_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_mcp_package_routing.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_mcp_setup.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_mcp_subagent_startup.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_mcp_subagent_startup_integration.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_os_family_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_os_knowledge_family_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_os_selected_family.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_ownership.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_packmeta.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_project_quality_baseline.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_qa_report_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_resource_provider.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_runtime_contracts.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_sarif_to_locator.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_security_definition_migration.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_skill_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_skill_definition_governance.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_skill_definition_mcp.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_skill_definition_runtime.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_skill_edits.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_skill_maturity_return.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_skill_runtime_audit.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_skillmeta.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_skills_sync.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_structure_catalog.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_subagent_startup.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_subagent_startup_boundaries.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_wbs.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_workflow_family_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_xref.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_xrefkit_instance.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_xrefkit_tools.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_xrefkit_v2_discovery.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_xrefkit_v2_models.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/tests/test_xrefkit_v2_pipeline.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/__main__.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/__init__.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/__main__.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/client.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/scenarios.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/attention_pet/store.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/boundary_analysis.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/catalog_cli.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/cli.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/contracts.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/ctx.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/dashboard.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/decision_trace.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/discovery.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/execution_binding.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/execution_binding_cli.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/gate.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/gateway.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/goalstate.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/hashing.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/host_precheck.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/import_skill.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/instance.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/loaders.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/audit.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/bootstrap.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/cli.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/client_cache.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/client_flow.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/context_registry.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/context_token.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/contribution_adoption.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/contribution_returns.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/dist.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/gateway.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/inbound_uploads.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/knowledge_edits.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/ownership.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/repository.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/skill_edits.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/skill_maturity.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/startup_contract_pack.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp/subagent_startup.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/mcp_tools.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/models/__init__.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/models/common.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/models/effective_bundle.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/models/human_evaluation.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/models/local_manifest.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/models/package_manifest.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/models/run_log.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/models/server_config.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/models/skill_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/operations_cli.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/ownership.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/packmeta.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/registry.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resolver.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resource_provider.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/attention_pet/client-state.schema.json +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/contracts.json +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/current.json +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/model_body.md +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/startup_sources/000_agent_entry.md +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/startup_sources/011_startup_xref_routing.md +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/startup_sources/015_shared_memory_operations.md +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/startup_sources/016_uncertainty_protocol.md +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/startup_sources/017_base_and_xref_layering.md +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/resources/base/startup_sources/053_context_direction_security_guard.md +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/runlog.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/skill_definition.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/skill_definition_catalog.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/skill_definition_governance.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/skillmeta.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/skillrun.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/skills_sync.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/structure_catalog.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/subagent_startup.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/tools/__init__.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/tools/__main__.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/v2_cli.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/wbs.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/workspace.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit/xref.py +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit.egg-info/dependency_links.txt +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit.egg-info/entry_points.txt +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.1}/xrefkit.egg-info/requires.txt +0 -0
- {xrefkit-0.5.1 → xrefkit-0.6.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.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit
|
|
5
5
|
Author: synthaicode
|
|
6
6
|
License: MIT License
|
|
@@ -47,7 +47,7 @@ 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
|
|
50
|
+
Experimental local tool: [Attention Pet](projects/attention-pet/README.en.md) helps
|
|
51
51
|
people assess capability sufficiency and lower inference-cost candidates separately.
|
|
52
52
|
Its Model Fit / Cost Fit estimates use uncalibrated profiles; it does not measure internal
|
|
53
53
|
attention or switch models automatically.
|
|
@@ -61,6 +61,11 @@ pet. It also shows lower inference-cost comparison candidates when the
|
|
|
61
61
|
experimental profiles support one. The estimate is observational and does not
|
|
62
62
|
change the Codex model, reasoning level, or chat.
|
|
63
63
|
|
|
64
|
+
It does not measure token usage. It estimates the structural complexity of the
|
|
65
|
+
work context the AI must handle, including retained items, dependencies,
|
|
66
|
+
constraints, decision depth, conflicts, and dispersed evidence. It does not
|
|
67
|
+
read model-internal attention or remaining context-window capacity.
|
|
68
|
+
|
|
64
69
|
Run it from a Codex terminal in this repository so `CODEX_THREAD_ID` identifies
|
|
65
70
|
the current chat:
|
|
66
71
|
|
|
@@ -77,7 +82,7 @@ This Codex preview is bound to the chat that launched it. Selecting another
|
|
|
77
82
|
chat does not move the Pet automatically; launch it again from that chat when
|
|
78
83
|
you want a separate view. The model and reasoning dropdowns only compare
|
|
79
84
|
estimates and do not change the active Codex settings. See the
|
|
80
|
-
[Attention Pet guide](projects/attention-pet/README.md) for interpretation,
|
|
85
|
+
[Attention Pet guide](projects/attention-pet/README.en.md) for interpretation,
|
|
81
86
|
limitations, client integration, and API details.
|
|
82
87
|
|
|
83
88
|
Using AI for real work creates recurring operating problems:
|
|
@@ -151,6 +156,20 @@ This command starts the optional catalog and runtime MCP adapter. This checkout
|
|
|
151
156
|
does not implement management upload, staging, sealing, review, or adoption of
|
|
152
157
|
uploaded SkillDefinition bytes into the active catalog.
|
|
153
158
|
|
|
159
|
+
To connect VS Code/Copilot, run from your target workspace:
|
|
160
|
+
|
|
161
|
+
```powershell
|
|
162
|
+
python -m pip install "xrefkit[mcp]"
|
|
163
|
+
python -m xrefkit mcp setup --repo . --output .xrefkit/setup-review
|
|
164
|
+
python -m xrefkit mcp setup apply --repo . --source .xrefkit/setup-review
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Review the generated files before applying them, open the workspace in VS Code,
|
|
168
|
+
and start the `xrefkit` MCP server. In Copilot chat, ask: "Call XRefKit's
|
|
169
|
+
get_startup_context and help me get started." The AI receives any needed legacy
|
|
170
|
+
migration guidance; you can describe your goal without specifying Skill names.
|
|
171
|
+
Opening VS Code alone does not guarantee an AI conversation starts.
|
|
172
|
+
|
|
154
173
|
## Where to go next
|
|
155
174
|
|
|
156
175
|
- [Install XRefKit and register Skill Packages](docs/guides/089_xrefkit_package_first_registration.md#xid-4F8C2A7D1E90)
|
|
@@ -8,7 +8,7 @@ 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
|
|
11
|
+
Experimental local tool: [Attention Pet](projects/attention-pet/README.en.md) helps
|
|
12
12
|
people assess capability sufficiency and lower inference-cost candidates separately.
|
|
13
13
|
Its Model Fit / Cost Fit estimates use uncalibrated profiles; it does not measure internal
|
|
14
14
|
attention or switch models automatically.
|
|
@@ -22,6 +22,11 @@ pet. It also shows lower inference-cost comparison candidates when the
|
|
|
22
22
|
experimental profiles support one. The estimate is observational and does not
|
|
23
23
|
change the Codex model, reasoning level, or chat.
|
|
24
24
|
|
|
25
|
+
It does not measure token usage. It estimates the structural complexity of the
|
|
26
|
+
work context the AI must handle, including retained items, dependencies,
|
|
27
|
+
constraints, decision depth, conflicts, and dispersed evidence. It does not
|
|
28
|
+
read model-internal attention or remaining context-window capacity.
|
|
29
|
+
|
|
25
30
|
Run it from a Codex terminal in this repository so `CODEX_THREAD_ID` identifies
|
|
26
31
|
the current chat:
|
|
27
32
|
|
|
@@ -38,7 +43,7 @@ This Codex preview is bound to the chat that launched it. Selecting another
|
|
|
38
43
|
chat does not move the Pet automatically; launch it again from that chat when
|
|
39
44
|
you want a separate view. The model and reasoning dropdowns only compare
|
|
40
45
|
estimates and do not change the active Codex settings. See the
|
|
41
|
-
[Attention Pet guide](projects/attention-pet/README.md) for interpretation,
|
|
46
|
+
[Attention Pet guide](projects/attention-pet/README.en.md) for interpretation,
|
|
42
47
|
limitations, client integration, and API details.
|
|
43
48
|
|
|
44
49
|
Using AI for real work creates recurring operating problems:
|
|
@@ -112,6 +117,20 @@ This command starts the optional catalog and runtime MCP adapter. This checkout
|
|
|
112
117
|
does not implement management upload, staging, sealing, review, or adoption of
|
|
113
118
|
uploaded SkillDefinition bytes into the active catalog.
|
|
114
119
|
|
|
120
|
+
To connect VS Code/Copilot, run from your target workspace:
|
|
121
|
+
|
|
122
|
+
```powershell
|
|
123
|
+
python -m pip install "xrefkit[mcp]"
|
|
124
|
+
python -m xrefkit mcp setup --repo . --output .xrefkit/setup-review
|
|
125
|
+
python -m xrefkit mcp setup apply --repo . --source .xrefkit/setup-review
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Review the generated files before applying them, open the workspace in VS Code,
|
|
129
|
+
and start the `xrefkit` MCP server. In Copilot chat, ask: "Call XRefKit's
|
|
130
|
+
get_startup_context and help me get started." The AI receives any needed legacy
|
|
131
|
+
migration guidance; you can describe your goal without specifying Skill names.
|
|
132
|
+
Opening VS Code alone does not guarantee an AI conversation starts.
|
|
133
|
+
|
|
115
134
|
## Where to go next
|
|
116
135
|
|
|
117
136
|
- [Install XRefKit and register Skill Packages](docs/guides/089_xrefkit_package_first_registration.md#xid-4F8C2A7D1E90)
|
|
@@ -101,10 +101,16 @@ def test_handshake_authentication_and_active_session_switching(tmp_path):
|
|
|
101
101
|
get("/api/state?lang=fr")
|
|
102
102
|
assert error.value.code == 400
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
next_session = payload("session-b", 2, 2)
|
|
105
|
+
next_session["model"] = "gpt-6-astra"
|
|
106
|
+
next_session["reasoning"] = "high"
|
|
107
|
+
post("/api/active-session", next_session)
|
|
108
|
+
second = get("/api/state?model=&reasoning=medium")
|
|
106
109
|
assert second["source"]["sessionId"] == "session-b"
|
|
110
|
+
assert second["fit"]["selectedProfile"] == {"model": "astra", "reasoning": "high"}
|
|
107
111
|
assert second["state"]["features"]["active_items"] == 2
|
|
112
|
+
manual_comparison = get("/api/state?model=sol&reasoning=medium")
|
|
113
|
+
assert manual_comparison["fit"]["selectedProfile"] == {"model": "sol", "reasoning": "medium"}
|
|
108
114
|
|
|
109
115
|
with pytest.raises(HTTPError) as error:
|
|
110
116
|
post("/api/active-session", payload("session-a", 1, 1))
|
|
@@ -116,6 +122,95 @@ def test_handshake_authentication_and_active_session_switching(tmp_path):
|
|
|
116
122
|
thread.join(timeout=5)
|
|
117
123
|
|
|
118
124
|
|
|
125
|
+
def test_state_response_keeps_one_session_when_activation_follows_snapshot(tmp_path, monkeypatch):
|
|
126
|
+
source = ClientStateSource(tmp_path / "sessions", Weights())
|
|
127
|
+
source.activate(ClientState.model_validate(payload("session-a", 1)))
|
|
128
|
+
next_session = payload("session-b", 2)
|
|
129
|
+
next_session["model"] = "gpt-6-astra"
|
|
130
|
+
next_session["reasoning"] = "high"
|
|
131
|
+
next_state = ClientState.model_validate(next_session)
|
|
132
|
+
original_snapshot = source.snapshot
|
|
133
|
+
|
|
134
|
+
def switch_after_snapshot(fallback_store):
|
|
135
|
+
snapshot = original_snapshot(fallback_store)
|
|
136
|
+
source.activate(next_state)
|
|
137
|
+
return snapshot
|
|
138
|
+
|
|
139
|
+
monkeypatch.setattr(source, "snapshot", switch_after_snapshot)
|
|
140
|
+
server, launch = make_server(Store(), source=source)
|
|
141
|
+
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
142
|
+
thread.start()
|
|
143
|
+
try:
|
|
144
|
+
with urlopen(launch + "api/state?model=&reasoning=medium", timeout=5) as response:
|
|
145
|
+
before = json.load(response)
|
|
146
|
+
assert before["state"]["taskId"] == "task-session-a"
|
|
147
|
+
assert before["source"]["sessionId"] == "session-a"
|
|
148
|
+
assert before["fit"]["selectedProfile"] == {"model": "sol", "reasoning": "medium"}
|
|
149
|
+
|
|
150
|
+
monkeypatch.setattr(source, "snapshot", original_snapshot)
|
|
151
|
+
with urlopen(launch + "api/state?model=&reasoning=medium", timeout=5) as response:
|
|
152
|
+
after = json.load(response)
|
|
153
|
+
assert after["state"]["taskId"] == "task-session-b"
|
|
154
|
+
assert after["source"]["sessionId"] == "session-b"
|
|
155
|
+
assert after["fit"]["selectedProfile"] == {"model": "astra", "reasoning": "high"}
|
|
156
|
+
finally:
|
|
157
|
+
server.shutdown()
|
|
158
|
+
server.server_close()
|
|
159
|
+
thread.join(timeout=5)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_client_snapshot_blocks_activation_until_all_session_fields_are_read(tmp_path, monkeypatch):
|
|
163
|
+
source = ClientStateSource(tmp_path / "sessions", Weights())
|
|
164
|
+
source.activate(ClientState.model_validate(payload("session-a", 1)))
|
|
165
|
+
next_session = payload("session-b", 2)
|
|
166
|
+
next_session["model"] = "gpt-6-astra"
|
|
167
|
+
next_session["reasoning"] = "high"
|
|
168
|
+
next_state = ClientState.model_validate(next_session)
|
|
169
|
+
view_read = threading.Event()
|
|
170
|
+
resume_view = threading.Event()
|
|
171
|
+
activation_started = threading.Event()
|
|
172
|
+
activation_done = threading.Event()
|
|
173
|
+
captured = {}
|
|
174
|
+
original_view = source.view
|
|
175
|
+
|
|
176
|
+
def pause_after_view(fallback_store):
|
|
177
|
+
result = original_view(fallback_store)
|
|
178
|
+
view_read.set()
|
|
179
|
+
assert resume_view.wait(timeout=5)
|
|
180
|
+
return result
|
|
181
|
+
|
|
182
|
+
def read_snapshot():
|
|
183
|
+
captured["snapshot"] = source.snapshot(Store())
|
|
184
|
+
|
|
185
|
+
def activate_next():
|
|
186
|
+
activation_started.set()
|
|
187
|
+
source.activate(next_state)
|
|
188
|
+
activation_done.set()
|
|
189
|
+
|
|
190
|
+
monkeypatch.setattr(source, "view", pause_after_view)
|
|
191
|
+
reader = threading.Thread(target=read_snapshot)
|
|
192
|
+
activator = threading.Thread(target=activate_next)
|
|
193
|
+
reader.start()
|
|
194
|
+
try:
|
|
195
|
+
assert view_read.wait(timeout=5)
|
|
196
|
+
activator.start()
|
|
197
|
+
assert activation_started.wait(timeout=5)
|
|
198
|
+
assert not activation_done.wait(timeout=0.1)
|
|
199
|
+
finally:
|
|
200
|
+
resume_view.set()
|
|
201
|
+
reader.join(timeout=5)
|
|
202
|
+
if activator.ident is not None:
|
|
203
|
+
activator.join(timeout=5)
|
|
204
|
+
|
|
205
|
+
assert not reader.is_alive() and not activator.is_alive()
|
|
206
|
+
result, selection, status = captured["snapshot"]
|
|
207
|
+
assert result["state"]["taskId"] == "task-session-a"
|
|
208
|
+
assert selection == ("sol", "medium")
|
|
209
|
+
assert status["sessionId"] == "session-a"
|
|
210
|
+
assert activation_done.is_set()
|
|
211
|
+
assert source.status()["sessionId"] == "session-b"
|
|
212
|
+
|
|
213
|
+
|
|
119
214
|
def test_client_contract_rejects_mismatched_session_and_manual_mode(tmp_path):
|
|
120
215
|
source = ClientStateSource(tmp_path / "sessions", Weights())
|
|
121
216
|
server, launch = make_server(Store(), source=source)
|
|
@@ -38,7 +38,7 @@ def test_one_bound_chat_updates_incrementally_without_saving_text(tmp_path):
|
|
|
38
38
|
store = Store(saved)
|
|
39
39
|
source.sync(store)
|
|
40
40
|
first = store.view()
|
|
41
|
-
assert source.selection() == ("sol", "
|
|
41
|
+
assert source.selection() == ("sol", "medium")
|
|
42
42
|
assert source.status()["observedUserTurns"] == 1
|
|
43
43
|
assert first["state"]["coverage"] == "partial"
|
|
44
44
|
assert len(first["workingSet"]["items"]) == 1
|
|
@@ -55,7 +55,7 @@ def test_one_bound_chat_updates_incrementally_without_saving_text(tmp_path):
|
|
|
55
55
|
assert "この修正は不要" not in saved.read_text(encoding="utf-8")
|
|
56
56
|
append(log, "2026-09-26T12:00:03Z", "turn_context", {"model": "gpt-6-terra", "effort": "low"})
|
|
57
57
|
source.sync(store)
|
|
58
|
-
assert source.selection() == ("terra", "
|
|
58
|
+
assert source.selection() == ("terra", "low")
|
|
59
59
|
assert store.view() == second
|
|
60
60
|
append(log, "2026-09-26T12:00:04Z", "turn_context", {"model": "unrecognized-model", "effort": "medium"})
|
|
61
61
|
source.sync(store)
|
|
@@ -80,7 +80,7 @@ def test_live_api_defaults_to_observed_model_allows_comparison_and_rejects_mutat
|
|
|
80
80
|
assert value["source"]["mode"] == "codex-chat"
|
|
81
81
|
assert value["source"]["observedUserTurns"] == 1
|
|
82
82
|
assert value["source"]["profile"] == "sol"
|
|
83
|
-
assert value["source"]["reasoning"] == "
|
|
83
|
+
assert value["source"]["reasoning"] == "medium"
|
|
84
84
|
assert value["fit"]["selected"]["model"] == "astra"
|
|
85
85
|
assert value["fit"]["selected"]["reasoning"] == "high"
|
|
86
86
|
with urlopen(Request(base + "/api/state?model=&reasoning=standard"), timeout=5) as response:
|
|
@@ -16,6 +16,50 @@ from xrefkit.attention_pet.server import make_server
|
|
|
16
16
|
from xrefkit.attention_pet.store import Store
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
def test_execution_profile_search_crosses_models_and_efforts(monkeypatch):
|
|
20
|
+
# Controlled hypotheses make the two cross-axis cases independent of scenario thresholds.
|
|
21
|
+
state = {"ral": 50, "causes": [], "features": {"active_items": 1},
|
|
22
|
+
"coverage": "reviewed", "trajectoryEvidence": []}
|
|
23
|
+
for model, base in (("luna", 20), ("sol", 40)):
|
|
24
|
+
monkeypatch.setitem(PROFILES, model, PROFILES[model].model_copy(
|
|
25
|
+
update={"capability": Capability(reasoning=base, constraint_tracking=base,
|
|
26
|
+
evidence_handling=base)}))
|
|
27
|
+
for model in ("terra", "astra"):
|
|
28
|
+
monkeypatch.setitem(PROFILES, model, PROFILES[model].model_copy(
|
|
29
|
+
update={"relative_inference_cost": 100.0}))
|
|
30
|
+
result = evaluate_fit(state, "sol", "high")
|
|
31
|
+
assert result["modelFit"] == "Sufficient"
|
|
32
|
+
assert result["costFit"] == "LowerCostCandidateAvailable"
|
|
33
|
+
assert { (c["model"], c["reasoning"]) for c in result["lowerCostCandidates"] } >= {
|
|
34
|
+
("luna", "xhigh"), ("sol", "medium")}
|
|
35
|
+
assert not next(c for c in result["alternatives"] if
|
|
36
|
+
(c["model"], c["reasoning"]) == ("luna", "high"))["meetsRequirements"]
|
|
37
|
+
assert result["lowerCostCandidate"] == {"model": "sol", "reasoning": "medium"}
|
|
38
|
+
assert result["selectedProfile"] == {"model": "sol", "reasoning": "high"}
|
|
39
|
+
assert result["expectedTotalCost"] is None
|
|
40
|
+
|
|
41
|
+
underpowered = evaluate_fit(state, "sol", "low")
|
|
42
|
+
assert underpowered["modelFit"] == "Underpowered"
|
|
43
|
+
assert underpowered["costFit"] == "RetryRisk"
|
|
44
|
+
assert underpowered["lowerCostCandidate"] is None
|
|
45
|
+
assert underpowered["lowerCostCandidates"] == []
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_unknown_and_legacy_execution_profiles():
|
|
49
|
+
state = evaluate(task())
|
|
50
|
+
for model, reasoning in (("absent", "high"), ("sol", "absent"),
|
|
51
|
+
("terra", "xhigh")):
|
|
52
|
+
result = evaluate_fit(state, model, reasoning)
|
|
53
|
+
assert result["modelFit"] == result["costFit"] == "Unknown"
|
|
54
|
+
assert result["presentation"]["petState"] == "Unknown"
|
|
55
|
+
assert evaluate_fit(state, "sol", "light")["selected"]["reasoning"] == "light"
|
|
56
|
+
assert evaluate_fit(state, "sol", "standard")["selected"]["reasoning"] == "standard"
|
|
57
|
+
assert evaluate_fit(state, "sol", "light")["selectedProfile"] == {"model": "sol", "reasoning": "low"}
|
|
58
|
+
assert evaluate_fit(state, "sol", "standard")["selectedProfile"] == {"model": "sol", "reasoning": "medium"}
|
|
59
|
+
assert evaluate_fit(state, "sol", "standard")["selected"]["capability"] == \
|
|
60
|
+
evaluate_fit(state, "sol", "medium")["selected"]["capability"]
|
|
61
|
+
|
|
62
|
+
|
|
19
63
|
def task(name="A", step=5):
|
|
20
64
|
return WorkingSet.model_validate(scenario(name, step)["workingSet"])
|
|
21
65
|
|
|
@@ -70,10 +114,10 @@ def test_comparison_is_pure_and_total_cost_is_unknown():
|
|
|
70
114
|
assert {r["baseRal"] for r in results} == {state["ral"]}
|
|
71
115
|
for result in results:
|
|
72
116
|
assert result["expectedTotalCost"] is None
|
|
73
|
-
assert all(result[k] is None for k in ("inferenceCost", "retryCost", "correctionCost", "failureRiskCost"))
|
|
117
|
+
assert all(result[k] is None for k in ("inferenceCost", "retryCost", "correctionCost", "latencyCost", "failureRiskCost"))
|
|
74
118
|
assert result["calibration"] == "uncalibrated"
|
|
75
119
|
assert results[0]["costFit"] == "RetryRisk"
|
|
76
|
-
assert results[1]["costFit"] == "
|
|
120
|
+
assert results[1]["costFit"] == "LowerCostCandidateAvailable"
|
|
77
121
|
assert results[2]["costFit"] == "LowerCostCandidateAvailable"
|
|
78
122
|
|
|
79
123
|
|
|
@@ -121,9 +165,9 @@ def test_schema_matches_python_cost_fit_contract():
|
|
|
121
165
|
|
|
122
166
|
|
|
123
167
|
@pytest.mark.parametrize("name,step,costs,faces", [
|
|
124
|
-
("A", 5, ["
|
|
125
|
-
("B", 3, ["RetryRisk"
|
|
126
|
-
("B", 5, ["RetryRisk"] * 3 + ["
|
|
168
|
+
("A", 5, ["LowerCostCandidateAvailable"] * 4, ["Relaxed"] * 4),
|
|
169
|
+
("B", 3, ["RetryRisk"] + ["LowerCostCandidateAvailable"] * 3, ["Strained"] + ["Relaxed"] * 3),
|
|
170
|
+
("B", 5, ["RetryRisk"] * 3 + ["LowerCostCandidateAvailable"], ["Strained"] * 3 + ["Relaxed"]),
|
|
127
171
|
("D", 5, ["RetryRisk"] * 4, ["Strained"] * 4),
|
|
128
172
|
("C", 3, ["ReviewNeeded"] * 4, ["Review"] * 4),
|
|
129
173
|
])
|
|
@@ -141,11 +185,11 @@ def test_scenarios_separate_cost_and_presentation(name, step, costs, faces):
|
|
|
141
185
|
|
|
142
186
|
def test_other_prices_do_not_change_model_fit(monkeypatch):
|
|
143
187
|
state = evaluate(task())
|
|
144
|
-
before = evaluate_fit(state, "astra")
|
|
188
|
+
before = evaluate_fit(state, "astra", "low")
|
|
145
189
|
assert before["costFit"] == "LowerCostCandidateAvailable"
|
|
146
190
|
for name in ("luna", "terra", "sol"):
|
|
147
191
|
monkeypatch.setitem(PROFILES, name, PROFILES[name].model_copy(update={"relative_inference_cost": 100.0}))
|
|
148
|
-
after = evaluate_fit(state, "astra")
|
|
192
|
+
after = evaluate_fit(state, "astra", "low")
|
|
149
193
|
assert before["modelFit"] == after["modelFit"] == "Sufficient"
|
|
150
194
|
assert after["costFit"] == "NoLowerCostCandidate"
|
|
151
195
|
assert after["lowerCostCandidates"] == []
|
|
@@ -153,9 +197,9 @@ def test_other_prices_do_not_change_model_fit(monkeypatch):
|
|
|
153
197
|
|
|
154
198
|
@pytest.mark.parametrize("name,step,model,status,target", [
|
|
155
199
|
("A", 5, "astra", "Available", "Luna"),
|
|
156
|
-
("B", 3, "astra", "Available", "
|
|
157
|
-
("B", 3, "sol", "Available", "
|
|
158
|
-
("B", 3, "terra", "
|
|
200
|
+
("B", 3, "astra", "Available", "Sol"),
|
|
201
|
+
("B", 3, "sol", "Available", "Sol"),
|
|
202
|
+
("B", 3, "terra", "Available", "Sol"),
|
|
159
203
|
("B", 3, "luna", "Underpowered", None),
|
|
160
204
|
("C", 3, "astra", "HoldForReview", None),
|
|
161
205
|
("D", 5, "astra", "Underpowered", None),
|
|
@@ -168,7 +212,7 @@ def test_lowest_sufficient_downgrade_guidance(name, step, model, status, target)
|
|
|
168
212
|
if target:
|
|
169
213
|
assert f"低コスト比較候補: {target}" in guide["modelGuide"]
|
|
170
214
|
assert target in guide["modelGuideShort"]
|
|
171
|
-
assert "
|
|
215
|
+
assert " / " in guide["modelGuideShort"]
|
|
172
216
|
assert "再試行・修正時間・失敗損失を含む総コストは未比較" in guide["modelGuideDetail"]
|
|
173
217
|
assert "仮の必要能力3軸を満たす試算" in guide["modelGuideDetail"]
|
|
174
218
|
lowest = min(fit["lowerCostCandidates"], key=lambda c: c["relativeInferenceCost"])
|
|
@@ -241,7 +285,7 @@ def test_english_presentation_preserves_evaluation_meaning():
|
|
|
241
285
|
("Unknown", "Unknown", "Unknown", "まだ評価できません", "情報が不足"),
|
|
242
286
|
("Underpowered", "RetryRisk", "Strained", "能力が不足する可能性", "一部を満たしていません"),
|
|
243
287
|
("Sufficient", "NoLowerCostCandidate", "Balanced", "必要な能力を満たす試算", "候補は確認されていません"),
|
|
244
|
-
("Sufficient", "LowerCostCandidateAvailable", "Relaxed", "より低い推論コストの候補", "
|
|
288
|
+
("Sufficient", "LowerCostCandidateAvailable", "Relaxed", "より低い推論コストの候補", "現在の実行プロファイルでも必要能力を満たす試算"),
|
|
245
289
|
("Sufficient", "ReviewNeeded", "Review", "実際の結果", "モデル能力だけを原因とは判断していません"),
|
|
246
290
|
("Underpowered", "ReviewNeeded", "Review", "実際の結果", "モデル能力だけを原因とは判断していません"),
|
|
247
291
|
])
|
|
@@ -309,6 +353,13 @@ def test_profiles_reject_invalid_costs(field, value):
|
|
|
309
353
|
ModelProfile.model_validate(data)
|
|
310
354
|
|
|
311
355
|
|
|
356
|
+
def test_profiles_reject_reasoning_without_depth_definition():
|
|
357
|
+
data = PROFILES["luna"].model_dump()
|
|
358
|
+
data["supported_reasoning"] = ("low", "ultra")
|
|
359
|
+
with pytest.raises(ValidationError, match="unsupported reasoning levels: ultra"):
|
|
360
|
+
ModelProfile.model_validate(data)
|
|
361
|
+
|
|
362
|
+
|
|
312
363
|
def test_api_model_selection_is_read_only_and_invalid_queries_cannot_mutate():
|
|
313
364
|
store = Store()
|
|
314
365
|
before = store.submit(task("B", 3))
|
|
@@ -324,6 +375,12 @@ def test_api_model_selection_is_read_only_and_invalid_queries_cannot_mutate():
|
|
|
324
375
|
return json.load(response)
|
|
325
376
|
|
|
326
377
|
try:
|
|
378
|
+
catalog = request("/api/profiles")
|
|
379
|
+
assert next(p for p in catalog["models"] if p["id"] == "luna")["reasoning"] == [
|
|
380
|
+
"low", "medium", "high", "xhigh"]
|
|
381
|
+
assert catalog["legacyAliases"] == {"light": "low", "standard": "medium"}
|
|
382
|
+
unknown = request("/api/state?model=absent&reasoning=xhigh")
|
|
383
|
+
assert unknown["fit"]["modelFit"] == "Unknown"
|
|
327
384
|
for model, expected in zip(PROFILES, ["Underpowered", "Sufficient", "Sufficient", "Sufficient"], strict=True):
|
|
328
385
|
value = request(f"/api/state?model={model}&reasoning=standard")
|
|
329
386
|
assert value["fit"]["modelFit"] == expected
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
import subprocess
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from xrefkit.mcp.catalog import XRefCatalog
|
|
13
|
+
from xrefkit.mcp.legacy_migration import (
|
|
14
|
+
STATE, dismiss_legacy_migration, import_legacy_assets, migration_context,
|
|
15
|
+
plan_legacy_migration, record_mcp_retrieval,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def legacy(tmp_path: Path) -> tuple[Path, Path]:
|
|
20
|
+
source, root = tmp_path / "legacy", tmp_path / "target"
|
|
21
|
+
root.mkdir()
|
|
22
|
+
(source / "skills" / "nested" / "review").mkdir(parents=True)
|
|
23
|
+
(source / "knowledge").mkdir()
|
|
24
|
+
(source / "skills" / "nested" / "review" / "SKILL.md").write_text(
|
|
25
|
+
"# Review\n\nUse [rules](../../../knowledge/rules.md).\n", encoding="utf-8")
|
|
26
|
+
(source / "knowledge" / "rules.md").write_text("# Rules\n\nKeep evidence.\n", encoding="utf-8")
|
|
27
|
+
(source / "knowledge" / "unlinked.txt").write_text("Independent knowledge.\n", encoding="utf-8")
|
|
28
|
+
return source, root
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def snapshot(root: Path) -> dict[str, bytes]:
|
|
32
|
+
return {p.relative_to(root).as_posix(): p.read_bytes() for p in root.rglob("*") if p.is_file()}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def documents(root: Path) -> list[dict]:
|
|
36
|
+
data = json.loads((root / STATE).read_text(encoding="utf-8"))
|
|
37
|
+
catalog = XRefCatalog.build(root)
|
|
38
|
+
return [catalog.get_document_by_xid(a["xid"]) for unit in data["units"].values()
|
|
39
|
+
for a in unit.get("artifacts", []) if unit["status"] == "installed"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_first_partial_complete_restart_and_upgrade(tmp_path, monkeypatch):
|
|
43
|
+
source, root = legacy(tmp_path)
|
|
44
|
+
original = snapshot(source)
|
|
45
|
+
catalog = XRefCatalog.build(root)
|
|
46
|
+
first = catalog.get_startup_context()["legacy_migration"]
|
|
47
|
+
assert first["status"] == "not_started"
|
|
48
|
+
assert first["client_instructions"]
|
|
49
|
+
assert not (root / STATE).exists()
|
|
50
|
+
plan = plan_legacy_migration(root, str(source))
|
|
51
|
+
assert "knowledge/unlinked.txt" in plan["targets"]
|
|
52
|
+
assert "skills/nested/review" in plan["targets"]
|
|
53
|
+
assert not (root / STATE).exists() # inventory has no write side effect
|
|
54
|
+
result = import_legacy_assets(root, str(source), plan["targets"])
|
|
55
|
+
assert result["ok"]
|
|
56
|
+
assert migration_context(root)["status"] == "in_progress"
|
|
57
|
+
bodies = documents(root)
|
|
58
|
+
# Catalog/CLI verification does not masquerade as an MCP retrieval.
|
|
59
|
+
assert migration_context(root)["status"] == "in_progress"
|
|
60
|
+
record_mcp_retrieval(root, bodies[:1])
|
|
61
|
+
remaining = migration_context(root)
|
|
62
|
+
assert remaining["status"] == "in_progress"
|
|
63
|
+
assert sum(len(u["retrievals"]) for u in remaining["remaining"]) < len(bodies)
|
|
64
|
+
record_mcp_retrieval(root, bodies)
|
|
65
|
+
completed = migration_context(root)
|
|
66
|
+
assert completed["status"] == "completed"
|
|
67
|
+
assert completed["client_instructions"] == []
|
|
68
|
+
assert completed["remaining"] == []
|
|
69
|
+
state = (root / STATE).read_bytes()
|
|
70
|
+
import xrefkit.mcp
|
|
71
|
+
monkeypatch.setattr(xrefkit.mcp, "__version__", "999.0.0")
|
|
72
|
+
restarted = XRefCatalog.build(root).get_startup_context({"irrelevant": "cached"})
|
|
73
|
+
assert restarted["legacy_migration"]["status"] == "completed"
|
|
74
|
+
assert (root / STATE).read_bytes() == state
|
|
75
|
+
assert snapshot(source) == original
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_explicit_scope_and_dismissal(tmp_path):
|
|
79
|
+
source, root = legacy(tmp_path)
|
|
80
|
+
assert dismiss_legacy_migration(root)["status"] == "not_required"
|
|
81
|
+
assert XRefCatalog.build(root).get_startup_context()["legacy_migration"]["client_instructions"] == []
|
|
82
|
+
result = import_legacy_assets(root, str(source), ["knowledge/unlinked.txt"])
|
|
83
|
+
assert result["ok"]
|
|
84
|
+
assert not (root / "skills").exists()
|
|
85
|
+
with pytest.raises(ValueError, match="cannot dismiss"):
|
|
86
|
+
dismiss_legacy_migration(root)
|
|
87
|
+
record_mcp_retrieval(root, documents(root))
|
|
88
|
+
assert migration_context(root)["status"] == "completed"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_conflict_failure_retry_and_existing_assets_protected(tmp_path):
|
|
92
|
+
source, root = legacy(tmp_path)
|
|
93
|
+
protected = root / "skills" / "imported.nested_review" / "SKILL.md"
|
|
94
|
+
protected.parent.mkdir(parents=True)
|
|
95
|
+
protected.write_bytes(b"Existing customized skill\n")
|
|
96
|
+
result = import_legacy_assets(root, str(source), ["skills/nested/review", "knowledge/unlinked.txt"], prefix="imported")
|
|
97
|
+
assert not result["ok"]
|
|
98
|
+
remaining = migration_context(root)["remaining"]
|
|
99
|
+
assert remaining[0]["status"] == "failed"
|
|
100
|
+
assert "refusing to overwrite" in remaining[0]["error"]
|
|
101
|
+
assert protected.read_bytes() == b"Existing customized skill\n"
|
|
102
|
+
record_mcp_retrieval(root, documents(root))
|
|
103
|
+
assert [u["target"] for u in migration_context(root)["remaining"]] == ["skills/nested/review"]
|
|
104
|
+
# Simulate a user moving their customized file aside after reviewing conflict.
|
|
105
|
+
protected.rename(protected.with_suffix(".backup"))
|
|
106
|
+
assert import_legacy_assets(root, str(source), ["skills/nested/review"], prefix="imported")["ok"]
|
|
107
|
+
record_mcp_retrieval(root, documents(root))
|
|
108
|
+
assert migration_context(root)["status"] == "completed"
|
|
109
|
+
assert protected.with_suffix(".backup").read_bytes() == b"Existing customized skill\n"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_partial_publish_retry_is_stable(tmp_path, monkeypatch):
|
|
113
|
+
source, root = legacy(tmp_path)
|
|
114
|
+
real_link = os.link
|
|
115
|
+
failed = False
|
|
116
|
+
def fail_once(source_path, destination, *args, **kwargs):
|
|
117
|
+
nonlocal failed
|
|
118
|
+
if Path(destination).name == "meta.md" and not failed:
|
|
119
|
+
failed = True
|
|
120
|
+
raise OSError("fixture disk failure")
|
|
121
|
+
return real_link(source_path, destination, *args, **kwargs)
|
|
122
|
+
monkeypatch.setattr(os, "link", fail_once)
|
|
123
|
+
assert not import_legacy_assets(root, str(source), ["skills/nested/review"])["ok"]
|
|
124
|
+
published = snapshot(root / "skills")
|
|
125
|
+
assert import_legacy_assets(root, str(source), ["skills/nested/review"])["ok"]
|
|
126
|
+
assert all(snapshot(root / "skills")[p] == b for p, b in published.items())
|
|
127
|
+
record_mcp_retrieval(root, documents(root))
|
|
128
|
+
assert migration_context(root)["status"] == "completed"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_receipts_require_matching_body_identity_and_content(tmp_path):
|
|
132
|
+
source, root = legacy(tmp_path)
|
|
133
|
+
import_legacy_assets(root, str(source), ["knowledge/unlinked.txt"])
|
|
134
|
+
body = documents(root)[0]
|
|
135
|
+
for wrong in ({**body, "content": None}, {**body, "content": "wrong"},
|
|
136
|
+
{**body, "path": "other.md"}, {**body, "xid": "wrong"}):
|
|
137
|
+
record_mcp_retrieval(root, [wrong])
|
|
138
|
+
assert migration_context(root)["status"] == "in_progress"
|
|
139
|
+
record_mcp_retrieval(root, [body])
|
|
140
|
+
assert migration_context(root)["status"] == "completed"
|
|
141
|
+
state = json.loads((root / STATE).read_text(encoding="utf-8"))
|
|
142
|
+
path = root / next(iter(state["units"].values()))["artifacts"][0]["path"]
|
|
143
|
+
path.write_text("Customized after migration", encoding="utf-8")
|
|
144
|
+
assert migration_context(root)["status"] == "in_progress"
|
|
145
|
+
assert not import_legacy_assets(root, str(source), ["knowledge/unlinked.txt"])["ok"]
|
|
146
|
+
assert path.read_text(encoding="utf-8") == "Customized after migration"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_corrupt_state_and_overlap_are_not_reset(tmp_path):
|
|
150
|
+
source, root = legacy(tmp_path)
|
|
151
|
+
for bad in ("{", '{"schema_version": 999, "units": {}}'):
|
|
152
|
+
(root / STATE).parent.mkdir(exist_ok=True)
|
|
153
|
+
(root / STATE).write_text(bad, encoding="utf-8")
|
|
154
|
+
assert migration_context(root)["status"] == "blocked"
|
|
155
|
+
with pytest.raises(ValueError):
|
|
156
|
+
import_legacy_assets(root, str(source), ["knowledge/unlinked.txt"])
|
|
157
|
+
assert (root / STATE).read_text(encoding="utf-8") == bad
|
|
158
|
+
for overlap in (root, root.parent):
|
|
159
|
+
with pytest.raises(ValueError, match="non-overlapping"):
|
|
160
|
+
plan_legacy_migration(root, str(overlap))
|
|
161
|
+
with pytest.raises(ValueError, match="exact targets"):
|
|
162
|
+
import_legacy_assets(root, str(source), ["../escape"])
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_existing_xid_conflict_preserves_catalog_and_source(tmp_path):
|
|
166
|
+
source, root = legacy(tmp_path)
|
|
167
|
+
xid = "ABCDEF012345"
|
|
168
|
+
(source / "knowledge" / "rules.md").write_text(f"<!-- xid: {xid} -->\n# Legacy rules\n", encoding="utf-8")
|
|
169
|
+
existing = root / "knowledge" / "existing.md"
|
|
170
|
+
existing.parent.mkdir()
|
|
171
|
+
existing.write_text(f"<!-- xid: {xid} -->\n# Customized rules\n", encoding="utf-8")
|
|
172
|
+
before = snapshot(source)
|
|
173
|
+
result = import_legacy_assets(root, str(source), ["knowledge/rules.md"])
|
|
174
|
+
assert not result["ok"]
|
|
175
|
+
assert "XID conflict" in result["migration"]["remaining"][0]["error"]
|
|
176
|
+
assert XRefCatalog.build(root).get_document_by_xid(xid)["title"] == "Customized rules"
|
|
177
|
+
assert snapshot(source) == before
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_unsupported_skill_resources_are_reported_without_false_completion(tmp_path):
|
|
181
|
+
source, root = legacy(tmp_path)
|
|
182
|
+
(source / "skills/nested/review/helper.py").write_text("print('legacy')", encoding="utf-8")
|
|
183
|
+
plan = plan_legacy_migration(root, str(source))
|
|
184
|
+
assert "skills/nested/review/helper.py" in plan["unsupported"]
|
|
185
|
+
assert not import_legacy_assets(root, str(source), ["skills/nested/review"])["ok"]
|
|
186
|
+
assert migration_context(root)["status"] == "in_progress"
|
|
187
|
+
assert not (root / "skills").exists()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_live_stdio_retrieval_and_restart(tmp_path):
|
|
191
|
+
pytest.importorskip("mcp")
|
|
192
|
+
from mcp.client.session import ClientSession
|
|
193
|
+
from mcp.client.stdio import StdioServerParameters, stdio_client
|
|
194
|
+
source, root = legacy(tmp_path)
|
|
195
|
+
original = snapshot(source)
|
|
196
|
+
params = StdioServerParameters(command=sys.executable,
|
|
197
|
+
args=["-m", "xrefkit", "mcp", "serve", "--repo", str(root)])
|
|
198
|
+
async def exercise():
|
|
199
|
+
async def call(session, tool, arguments=None):
|
|
200
|
+
result = await session.call_tool(tool, arguments or {})
|
|
201
|
+
assert not result.isError, result
|
|
202
|
+
return result.structuredContent or json.loads(result.content[0].text)
|
|
203
|
+
async with stdio_client(params) as (read, write):
|
|
204
|
+
async with ClientSession(read, write) as session:
|
|
205
|
+
await session.initialize()
|
|
206
|
+
startup = await call(session, "get_startup_context")
|
|
207
|
+
assert startup["legacy_migration"]["status"] == "not_started"
|
|
208
|
+
plan = await call(session, "plan_legacy_migration", {"source": str(source)})
|
|
209
|
+
argv = [sys.executable, *plan["client_import_command"][1:]]
|
|
210
|
+
for target in plan["targets"]:
|
|
211
|
+
argv.extend(["--target", target])
|
|
212
|
+
proc = await asyncio.to_thread(subprocess.run, argv, capture_output=True, text=True, check=False)
|
|
213
|
+
assert proc.returncode == 0, proc.stdout + proc.stderr
|
|
214
|
+
result = json.loads(proc.stdout)
|
|
215
|
+
assert result["ok"]
|
|
216
|
+
skill_id = result["converted_skills"][0]["skill_id"]
|
|
217
|
+
await call(session, "get_skill", {"skill_id": skill_id})
|
|
218
|
+
context = (await call(session, "get_startup_context"))["legacy_migration"]
|
|
219
|
+
assert context["status"] == "in_progress"
|
|
220
|
+
for unit in context["remaining"]:
|
|
221
|
+
for pending in unit["retrievals"]:
|
|
222
|
+
await call(session, "get_document_by_xid", {"xid": pending["xid"]})
|
|
223
|
+
assert (await call(session, "get_startup_context"))["legacy_migration"]["status"] == "completed"
|
|
224
|
+
async with stdio_client(params) as (read, write):
|
|
225
|
+
async with ClientSession(read, write) as session:
|
|
226
|
+
await session.initialize()
|
|
227
|
+
context = (await call(session, "get_startup_context"))["legacy_migration"]
|
|
228
|
+
assert context["status"] == "completed"
|
|
229
|
+
assert not context["client_instructions"]
|
|
230
|
+
asyncio.run(exercise())
|
|
231
|
+
assert snapshot(source) == original
|