xrefkit 0.4.3__tar.gz → 0.4.5__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 (109) hide show
  1. {xrefkit-0.4.3 → xrefkit-0.4.5}/PKG-INFO +94 -2
  2. {xrefkit-0.4.3 → xrefkit-0.4.5}/README.md +93 -1
  3. {xrefkit-0.4.3 → xrefkit-0.4.5}/pyproject.toml +1 -1
  4. xrefkit-0.4.5/tests/test_mcp_setup.py +107 -0
  5. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/__init__.py +1 -1
  6. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/__init__.py +10 -0
  7. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/bootstrap.py +13 -2
  8. xrefkit-0.4.5/xrefkit/mcp/context_token.py +108 -0
  9. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/server.py +137 -17
  10. xrefkit-0.4.5/xrefkit/mcp/setup.py +241 -0
  11. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit.egg-info/PKG-INFO +94 -2
  12. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit.egg-info/SOURCES.txt +3 -0
  13. {xrefkit-0.4.3 → xrefkit-0.4.5}/LICENSE +0 -0
  14. {xrefkit-0.4.3 → xrefkit-0.4.5}/setup.cfg +0 -0
  15. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_base_sync_ownership.py +0 -0
  16. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_boundary_analysis.py +0 -0
  17. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_calibration_lint.py +0 -0
  18. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_check_skill_knowledge_xids.py +0 -0
  19. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_cli.py +0 -0
  20. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_collect_analyzer_sarif.py +0 -0
  21. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_convert_to_xrefkit_skill.py +0 -0
  22. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_cs_scope_probe.py +0 -0
  23. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_csharp_commonality.py +0 -0
  24. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_csharp_naming_profile.py +0 -0
  25. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_ctx.py +0 -0
  26. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_cutover_readiness.py +0 -0
  27. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_dashboard.py +0 -0
  28. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_error_policy_audit.py +0 -0
  29. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_error_policy_locator.py +0 -0
  30. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_fm_multiroot.py +0 -0
  31. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_gate.py +0 -0
  32. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_goal_desired_state.py +0 -0
  33. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_instruction_workflow.py +0 -0
  34. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_knowledge_relations_validator.py +0 -0
  35. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_ownership.py +0 -0
  36. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_packmeta.py +0 -0
  37. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_project_quality_baseline.py +0 -0
  38. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_resource_provider.py +0 -0
  39. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_runtime_contracts.py +0 -0
  40. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_sarif_to_locator.py +0 -0
  41. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_skill_runtime_audit.py +0 -0
  42. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_skillmeta.py +0 -0
  43. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_structure_catalog.py +0 -0
  44. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xref.py +0 -0
  45. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xrefkit_instance.py +0 -0
  46. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xrefkit_tools.py +0 -0
  47. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xrefkit_v2_discovery.py +0 -0
  48. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xrefkit_v2_models.py +0 -0
  49. {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xrefkit_v2_pipeline.py +0 -0
  50. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/__main__.py +0 -0
  51. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/boundary_analysis.py +0 -0
  52. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/catalog_cli.py +0 -0
  53. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/cli.py +0 -0
  54. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/contracts.py +0 -0
  55. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/ctx.py +0 -0
  56. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/dashboard.py +0 -0
  57. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/discovery.py +0 -0
  58. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/gate.py +0 -0
  59. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/goalstate.py +0 -0
  60. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/hashing.py +0 -0
  61. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/import_skill.py +0 -0
  62. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/instance.py +0 -0
  63. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/loaders.py +0 -0
  64. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/audit.py +0 -0
  65. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/catalog.py +0 -0
  66. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/cli.py +0 -0
  67. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/client_cache.py +0 -0
  68. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/context_registry.py +0 -0
  69. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/contracts.py +0 -0
  70. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/dist.py +0 -0
  71. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/ownership.py +0 -0
  72. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/repository.py +0 -0
  73. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/schemas.py +0 -0
  74. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/startup_contract_pack.py +0 -0
  75. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp_tools.py +0 -0
  76. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/__init__.py +0 -0
  77. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/common.py +0 -0
  78. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/effective_bundle.py +0 -0
  79. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/local_manifest.py +0 -0
  80. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/package_manifest.py +0 -0
  81. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/run_log.py +0 -0
  82. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/server_config.py +0 -0
  83. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/skill_definition.py +0 -0
  84. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/operations_cli.py +0 -0
  85. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/ownership.py +0 -0
  86. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/packmeta.py +0 -0
  87. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/registry.py +0 -0
  88. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resolver.py +0 -0
  89. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resource_provider.py +0 -0
  90. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/contracts.json +0 -0
  91. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/current.json +0 -0
  92. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
  93. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
  94. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
  95. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
  96. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/model_body.md +0 -0
  97. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/runlog.py +0 -0
  98. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/skillmeta.py +0 -0
  99. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/skillrun.py +0 -0
  100. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/structure_catalog.py +0 -0
  101. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/tools/__init__.py +0 -0
  102. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/tools/__main__.py +0 -0
  103. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/v2_cli.py +0 -0
  104. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/workspace.py +0 -0
  105. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/xref.py +0 -0
  106. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit.egg-info/dependency_links.txt +0 -0
  107. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit.egg-info/entry_points.txt +0 -0
  108. {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit.egg-info/requires.txt +0 -0
  109. {xrefkit-0.4.3 → xrefkit-0.4.5}/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.3
3
+ Version: 0.4.5
4
4
  Summary: Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit
5
5
  Author: synthaicode
6
6
  License: MIT License
@@ -101,6 +101,38 @@ and handoff records from collapsing into one opaque instruction block.
101
101
 
102
102
  ![XRefKit repository snapshot](human-docs/en/assets/xrefkit_repository_snapshot/xrefkit_repository_snapshot.png)
103
103
 
104
+ ## Skills: what you can ask XRefKit to do
105
+
106
+ XRefKit includes Skills for common stages of software and business work. For
107
+ example:
108
+
109
+ - **Business discovery and planning**: `business_learning_interview`,
110
+ `business_intake_scoping`, `requirements_flow`, `planning_flow`, and
111
+ `estimation_flow`
112
+ - **Investigation, design, and implementation**: `investigation_flow`,
113
+ `source_structure_overview`, `db_current_state_analysis`, `db_design`,
114
+ `design_flow`, and `implementation_flow`
115
+ - **Constraint and change analysis**: `constraint_derivation_index`,
116
+ `dotnet_change_analysis`, `code_constraint_derivation`,
117
+ `cross_constraint_derivation`, and `integration_scenario_derivation`
118
+ - **Review and quality**: `csharp_review`, `python_review`, `security_review`,
119
+ `test_flow`, `qa_gate_review`, and `review_report_composition`
120
+ - **Knowledge and Skill operations**: `import_skill`, `skill_flow_authoring`,
121
+ `knowledge_ontology_management`, `judgment_log`, `doc_ship`, and
122
+ `skill_calibration_evaluation`
123
+ - **Documents and communication**: `pptx_spec_traceability`,
124
+ `xlsx_spec_traceability`, `presentation_flow_review`, `editorial_intake`,
125
+ `draft_authoring`, and `marketing_slide_png`
126
+
127
+ Each Skill is a reusable procedure with a capability/tuning/responsibility
128
+ identity. Its procedure, routing metadata, domain Knowledge, and run evidence
129
+ remain separate. Describe your goal in natural language and XRefKit routes it
130
+ to the relevant Skill; the [Skill catalog](skills/_index.md#xid-8D91F66DDBB7)
131
+ contains the complete current list and summaries.
132
+
133
+ To install and enable Skills distributed as Python Packages, follow the
134
+ [package-first registration guide](docs/guides/089_xrefkit_package_first_registration.md#xid-4F8C2A7D1E90).
135
+
104
136
  ## How It Works
105
137
 
106
138
  1. Original materials are kept in `sources/`.
@@ -126,7 +158,36 @@ reason instead of inventing a criterion.
126
158
 
127
159
  ## Quick Start
128
160
 
129
- Install the package and initialize an instance:
161
+ ### Install from PyPI
162
+
163
+ XRefKit requires Python 3.11 or later. For a normal installation, create a
164
+ virtual environment and install the published package from PyPI:
165
+
166
+ ```powershell
167
+ python -m venv .venv
168
+ .\.venv\Scripts\Activate.ps1
169
+ python -m pip install --upgrade pip
170
+ python -m pip install xrefkit
171
+ xrefkit init
172
+ xrefkit --help
173
+ ```
174
+
175
+ If the `xrefkit` command is not available on `PATH`, use the module form:
176
+
177
+ ```powershell
178
+ python -m xrefkit --help
179
+ ```
180
+
181
+ To use the integrated MCP server, install the optional MCP dependencies:
182
+
183
+ ```powershell
184
+ python -m pip install "xrefkit[mcp]"
185
+ xrefkit mcp serve --repo . --transport stdio
186
+ ```
187
+
188
+ ### Install from a checkout
189
+
190
+ For XRefKit development, install the local checkout in editable mode instead:
130
191
 
131
192
  ```powershell
132
193
  python -m pip install -e .
@@ -140,6 +201,25 @@ Start the integrated MCP server over stdio:
140
201
  xrefkit mcp serve --repo . --transport stdio
141
202
  ```
142
203
 
204
+ To import existing Skills and prepare a reviewable VS Code MCP setup, run:
205
+
206
+ ```powershell
207
+ python -m pip install "xrefkit[mcp]"
208
+ xrefkit mcp setup `
209
+ --repo C:\dev\itsm\XRefKit `
210
+ --import C:\work\existing-skills
211
+ ```
212
+
213
+ The command writes `SETUP.md`, `import-report.json`, a VS Code
214
+ `.vscode/mcp.json` example, and reviewed append text for `AGENTS.md` and
215
+ `CLAUDE.md` into a temporary setup folder. Apply those artifacts after review:
216
+
217
+ ```powershell
218
+ xrefkit mcp setup apply `
219
+ --source C:\Users\<user>\AppData\Local\Temp\xrefkit-setup-<id> `
220
+ --repo C:\dev\itsm\XRefKit
221
+ ```
222
+
143
223
  The server writes structured correlation events to
144
224
  `work/mcp/xid_audit.jsonl` by default. After `xrefkit skill run` returns a
145
225
  `run_id`, the client calls MCP `bind_skill_run` and executes the returned
@@ -148,6 +228,18 @@ searches and XID resolutions then share the same `run_id` as the client Skill
148
228
  Run. The client separately records actual model-context loading and judgment
149
229
  application with `xrefkit skill knowledge --action load|apply`.
150
230
 
231
+ For a network deployment without MCP protocol sessions, provide a shared HMAC
232
+ secret and enable stateless HTTP. The client must return the `context_id` from
233
+ `get_startup_context` in `_meta.io.xrefkit/context_id` on later requests:
234
+
235
+ ```powershell
236
+ $env:XREFKIT_CONTEXT_SECRET = 'replace-with-a-managed-secret'
237
+ python -m xrefkit.mcp.server --repo . --transport streamable-http --stateless-http
238
+ ```
239
+
240
+ The context token is signed, repository-bound, and short-lived. It is an
241
+ execution-context token, not a canonical XID or an authorization grant.
242
+
151
243
  ## Skill Run Observation Dashboard
152
244
 
153
245
  The local dashboard lets a human inspect Skill run status, closure and quality
@@ -62,6 +62,38 @@ and handoff records from collapsing into one opaque instruction block.
62
62
 
63
63
  ![XRefKit repository snapshot](human-docs/en/assets/xrefkit_repository_snapshot/xrefkit_repository_snapshot.png)
64
64
 
65
+ ## Skills: what you can ask XRefKit to do
66
+
67
+ XRefKit includes Skills for common stages of software and business work. For
68
+ example:
69
+
70
+ - **Business discovery and planning**: `business_learning_interview`,
71
+ `business_intake_scoping`, `requirements_flow`, `planning_flow`, and
72
+ `estimation_flow`
73
+ - **Investigation, design, and implementation**: `investigation_flow`,
74
+ `source_structure_overview`, `db_current_state_analysis`, `db_design`,
75
+ `design_flow`, and `implementation_flow`
76
+ - **Constraint and change analysis**: `constraint_derivation_index`,
77
+ `dotnet_change_analysis`, `code_constraint_derivation`,
78
+ `cross_constraint_derivation`, and `integration_scenario_derivation`
79
+ - **Review and quality**: `csharp_review`, `python_review`, `security_review`,
80
+ `test_flow`, `qa_gate_review`, and `review_report_composition`
81
+ - **Knowledge and Skill operations**: `import_skill`, `skill_flow_authoring`,
82
+ `knowledge_ontology_management`, `judgment_log`, `doc_ship`, and
83
+ `skill_calibration_evaluation`
84
+ - **Documents and communication**: `pptx_spec_traceability`,
85
+ `xlsx_spec_traceability`, `presentation_flow_review`, `editorial_intake`,
86
+ `draft_authoring`, and `marketing_slide_png`
87
+
88
+ Each Skill is a reusable procedure with a capability/tuning/responsibility
89
+ identity. Its procedure, routing metadata, domain Knowledge, and run evidence
90
+ remain separate. Describe your goal in natural language and XRefKit routes it
91
+ to the relevant Skill; the [Skill catalog](skills/_index.md#xid-8D91F66DDBB7)
92
+ contains the complete current list and summaries.
93
+
94
+ To install and enable Skills distributed as Python Packages, follow the
95
+ [package-first registration guide](docs/guides/089_xrefkit_package_first_registration.md#xid-4F8C2A7D1E90).
96
+
65
97
  ## How It Works
66
98
 
67
99
  1. Original materials are kept in `sources/`.
@@ -87,7 +119,36 @@ reason instead of inventing a criterion.
87
119
 
88
120
  ## Quick Start
89
121
 
90
- Install the package and initialize an instance:
122
+ ### Install from PyPI
123
+
124
+ XRefKit requires Python 3.11 or later. For a normal installation, create a
125
+ virtual environment and install the published package from PyPI:
126
+
127
+ ```powershell
128
+ python -m venv .venv
129
+ .\.venv\Scripts\Activate.ps1
130
+ python -m pip install --upgrade pip
131
+ python -m pip install xrefkit
132
+ xrefkit init
133
+ xrefkit --help
134
+ ```
135
+
136
+ If the `xrefkit` command is not available on `PATH`, use the module form:
137
+
138
+ ```powershell
139
+ python -m xrefkit --help
140
+ ```
141
+
142
+ To use the integrated MCP server, install the optional MCP dependencies:
143
+
144
+ ```powershell
145
+ python -m pip install "xrefkit[mcp]"
146
+ xrefkit mcp serve --repo . --transport stdio
147
+ ```
148
+
149
+ ### Install from a checkout
150
+
151
+ For XRefKit development, install the local checkout in editable mode instead:
91
152
 
92
153
  ```powershell
93
154
  python -m pip install -e .
@@ -101,6 +162,25 @@ Start the integrated MCP server over stdio:
101
162
  xrefkit mcp serve --repo . --transport stdio
102
163
  ```
103
164
 
165
+ To import existing Skills and prepare a reviewable VS Code MCP setup, run:
166
+
167
+ ```powershell
168
+ python -m pip install "xrefkit[mcp]"
169
+ xrefkit mcp setup `
170
+ --repo C:\dev\itsm\XRefKit `
171
+ --import C:\work\existing-skills
172
+ ```
173
+
174
+ The command writes `SETUP.md`, `import-report.json`, a VS Code
175
+ `.vscode/mcp.json` example, and reviewed append text for `AGENTS.md` and
176
+ `CLAUDE.md` into a temporary setup folder. Apply those artifacts after review:
177
+
178
+ ```powershell
179
+ xrefkit mcp setup apply `
180
+ --source C:\Users\<user>\AppData\Local\Temp\xrefkit-setup-<id> `
181
+ --repo C:\dev\itsm\XRefKit
182
+ ```
183
+
104
184
  The server writes structured correlation events to
105
185
  `work/mcp/xid_audit.jsonl` by default. After `xrefkit skill run` returns a
106
186
  `run_id`, the client calls MCP `bind_skill_run` and executes the returned
@@ -109,6 +189,18 @@ searches and XID resolutions then share the same `run_id` as the client Skill
109
189
  Run. The client separately records actual model-context loading and judgment
110
190
  application with `xrefkit skill knowledge --action load|apply`.
111
191
 
192
+ For a network deployment without MCP protocol sessions, provide a shared HMAC
193
+ secret and enable stateless HTTP. The client must return the `context_id` from
194
+ `get_startup_context` in `_meta.io.xrefkit/context_id` on later requests:
195
+
196
+ ```powershell
197
+ $env:XREFKIT_CONTEXT_SECRET = 'replace-with-a-managed-secret'
198
+ python -m xrefkit.mcp.server --repo . --transport streamable-http --stateless-http
199
+ ```
200
+
201
+ The context token is signed, repository-bound, and short-lived. It is an
202
+ execution-context token, not a canonical XID or an authorization grant.
203
+
112
204
  ## Skill Run Observation Dashboard
113
205
 
114
206
  The local dashboard lets a human inspect Skill run status, closure and quality
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "xrefkit"
7
- version = "0.4.3"
7
+ version = "0.4.5"
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,107 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from xrefkit.__main__ import main
7
+
8
+
9
+ def test_mcp_setup_writes_reviewable_workspace(tmp_path: Path, capsys) -> None:
10
+ output = tmp_path / "setup-output"
11
+ root = tmp_path / "repo"
12
+
13
+ assert main(["mcp", "setup", "--repo", str(root), "--output", str(output), "--json"]) == 0
14
+
15
+ payload = json.loads(capsys.readouterr().out)
16
+ assert payload["output"] == str(output.resolve())
17
+ assert (output / "SETUP.md").is_file()
18
+ assert (output / "vscode-mcp.json").is_file()
19
+ assert (output / "AGENTS.md.append.md").is_file()
20
+ assert (output / "CLAUDE.md.append.md").is_file()
21
+ assert (output / "import-report.json").is_file()
22
+
23
+ vscode = json.loads((output / "vscode-mcp.json").read_text(encoding="utf-8"))
24
+ server = vscode["servers"]["xrefkit"]
25
+ assert server["type"] == "stdio"
26
+ assert server["args"][-2:] == ["--transport", "stdio"]
27
+
28
+
29
+ def test_mcp_setup_imports_existing_batch_skill_before_writing_report(tmp_path: Path, capsys) -> None:
30
+ source = tmp_path / "existing"
31
+ skill = source / "skills" / "legacy-review"
32
+ skill.mkdir(parents=True)
33
+ (skill / "SKILL.md").write_text("# Legacy Review\n\nReview the supplied change.\n", encoding="utf-8")
34
+ root = tmp_path / "repo"
35
+ output = tmp_path / "setup-output"
36
+
37
+ exit_code = main(
38
+ [
39
+ "mcp",
40
+ "setup",
41
+ "--repo",
42
+ str(root),
43
+ "--import",
44
+ str(source),
45
+ "--output",
46
+ str(output),
47
+ "--json",
48
+ ]
49
+ )
50
+ payload = json.loads(capsys.readouterr().out)
51
+ assert exit_code in {0, 1}
52
+ assert (root / "skills" / "imported.legacy-review" / "SKILL.md").is_file()
53
+ report = json.loads((output / "import-report.json").read_text(encoding="utf-8"))
54
+ assert report["import"]["converted_skills"][0]["skill_id"] == "imported.legacy-review"
55
+ assert payload["output"] == str(output.resolve())
56
+
57
+
58
+ def test_mcp_setup_infers_single_skill_id_without_explicit_option(tmp_path: Path, capsys) -> None:
59
+ source = tmp_path / "existing-skill"
60
+ source.mkdir()
61
+ (source / "SKILL.md").write_text("# Existing Skill\n\nUse the supplied workflow.\n", encoding="utf-8")
62
+ root = tmp_path / "repo"
63
+ output = tmp_path / "setup-output"
64
+
65
+ exit_code = main(
66
+ [
67
+ "mcp",
68
+ "setup",
69
+ "--repo",
70
+ str(root),
71
+ "--import",
72
+ str(source),
73
+ "--output",
74
+ str(output),
75
+ "--json",
76
+ ]
77
+ )
78
+ payload = json.loads(capsys.readouterr().out)
79
+ assert exit_code in {0, 1}
80
+ assert (root / "skills" / "existing-skill" / "SKILL.md").is_file()
81
+ report = json.loads((output / "import-report.json").read_text(encoding="utf-8"))
82
+ assert report["import"]["skill_id"] == "existing-skill"
83
+ assert payload["output"] == str(output.resolve())
84
+
85
+
86
+ def test_mcp_setup_defaults_repo_to_current_directory(tmp_path: Path, monkeypatch, capsys) -> None:
87
+ monkeypatch.chdir(tmp_path)
88
+ assert main(["mcp", "setup", "--json"]) == 0
89
+ payload = json.loads(capsys.readouterr().out)
90
+ assert payload["ok"] is True
91
+
92
+
93
+ def test_mcp_setup_apply_copies_config_and_appends_instructions(tmp_path: Path, capsys) -> None:
94
+ output = tmp_path / "setup-output"
95
+ root = tmp_path / "repo"
96
+ root.mkdir()
97
+ assert main(["mcp", "setup", "--repo", str(root), "--output", str(output)]) == 0
98
+ capsys.readouterr()
99
+
100
+ assert main(["mcp", "setup-apply", "--source", str(output), "--repo", str(root)]) == 0
101
+ capsys.readouterr()
102
+ assert (root / ".vscode" / "mcp.json").is_file()
103
+ agents = (root / "AGENTS.md").read_text(encoding="utf-8")
104
+ assert "XRefKit MCP Skill Routing" in agents
105
+
106
+ assert main(["mcp", "setup-apply", "--source", str(output), "--repo", str(root)]) == 0
107
+ assert (root / "AGENTS.md").read_text(encoding="utf-8").count("XRefKit MCP Skill Routing") == 1
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.4.3"
5
+ __version__ = "0.4.5"
@@ -11,6 +11,16 @@ def main(argv: list[str] | None = None) -> int:
11
11
  from .server import main as server_main
12
12
 
13
13
  args = list(argv or [])
14
+ if args and args[0] == "setup":
15
+ from .setup import main as setup_main
16
+
17
+ if len(args) > 1 and args[1] == "apply":
18
+ return setup_main(["setup-apply", *args[2:]])
19
+ return setup_main(args)
20
+ if args and args[0] == "setup-apply":
21
+ from .setup import main as setup_main
22
+
23
+ return setup_main(args)
14
24
  if args and args[0] == "serve":
15
25
  args = args[1:]
16
26
  return server_main(args)
@@ -37,6 +37,7 @@ import sys
37
37
  import urllib.request
38
38
  import zipfile
39
39
  from pathlib import Path
40
+ from urllib.parse import urlsplit
40
41
 
41
42
 
42
43
  DIST_STATE_RELATIVE_PATH = ".xrefkit/dist-state.json"
@@ -50,6 +51,12 @@ class BootstrapError(RuntimeError):
50
51
  pass
51
52
 
52
53
 
54
+ def _validate_http_url(url: str) -> None:
55
+ parsed = urlsplit(url)
56
+ if parsed.scheme not in {"http", "https"} or not parsed.netloc:
57
+ raise BootstrapError("bootstrap endpoints must use an absolute http(s) URL")
58
+
59
+
53
60
  def _ssl_context(ca_file: str | None) -> ssl.SSLContext:
54
61
  if ca_file:
55
62
  return ssl.create_default_context(cafile=ca_file)
@@ -57,8 +64,10 @@ def _ssl_context(ca_file: str | None) -> ssl.SSLContext:
57
64
 
58
65
 
59
66
  def http_get(url: str, ca_file: str | None = None, headers: dict[str, str] | None = None) -> bytes:
67
+ _validate_http_url(url)
60
68
  request = urllib.request.Request(url, headers=headers or {})
61
- with urllib.request.urlopen(request, context=_ssl_context(ca_file)) as response:
69
+ # The URL scheme is validated above; B310 cannot infer that from Request.
70
+ with urllib.request.urlopen(request, context=_ssl_context(ca_file)) as response: # nosec B310
62
71
  return response.read()
63
72
 
64
73
 
@@ -68,6 +77,7 @@ def http_post_json(
68
77
  ca_file: str | None = None,
69
78
  headers: dict[str, str] | None = None,
70
79
  ) -> tuple[bytes, dict[str, str]]:
80
+ _validate_http_url(url)
71
81
  request_headers = {
72
82
  "Content-Type": "application/json",
73
83
  "Accept": "application/json, text/event-stream",
@@ -79,7 +89,8 @@ def http_post_json(
79
89
  headers=request_headers,
80
90
  method="POST",
81
91
  )
82
- with urllib.request.urlopen(request, context=_ssl_context(ca_file)) as response:
92
+ # The URL scheme is validated above; B310 cannot infer that from Request.
93
+ with urllib.request.urlopen(request, context=_ssl_context(ca_file)) as response: # nosec B310
83
94
  return response.read(), {key.lower(): value for key, value in response.headers.items()}
84
95
 
85
96
 
@@ -0,0 +1,108 @@
1
+ """Signed, client-carried context for stateless MCP requests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import hashlib
7
+ import hmac
8
+ import json
9
+ import time
10
+ import uuid
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+
15
+ CONTEXT_META_KEY = "io.xrefkit/context_id"
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ContextClaims:
20
+ context_id: str
21
+ repository_fingerprint: str
22
+ startup_loaded: bool = False
23
+ client_tools_unlocked: bool = False
24
+ run_id: str | None = None
25
+ skill_id: str | None = None
26
+ mcp_session_id: str | None = None
27
+ expires_at: int = 0
28
+
29
+ def to_dict(self) -> dict[str, Any]:
30
+ return {
31
+ "context_id": self.context_id,
32
+ "repository_fingerprint": self.repository_fingerprint,
33
+ "startup_loaded": self.startup_loaded,
34
+ "client_tools_unlocked": self.client_tools_unlocked,
35
+ "run_id": self.run_id,
36
+ "skill_id": self.skill_id,
37
+ "mcp_session_id": self.mcp_session_id,
38
+ "expires_at": self.expires_at,
39
+ }
40
+
41
+
42
+ class ContextTokenCodec:
43
+ def __init__(self, secret: str, repository_fingerprint: str, *, ttl_seconds: int = 3600) -> None:
44
+ if not secret:
45
+ raise ValueError("context token secret is required")
46
+ self._secret = secret.encode("utf-8")
47
+ self.repository_fingerprint = repository_fingerprint
48
+ self.ttl_seconds = ttl_seconds
49
+
50
+ def issue(
51
+ self,
52
+ *,
53
+ startup_loaded: bool = False,
54
+ client_tools_unlocked: bool = False,
55
+ run_id: str | None = None,
56
+ skill_id: str | None = None,
57
+ mcp_session_id: str | None = None,
58
+ context_id: str | None = None,
59
+ ) -> str:
60
+ claims = ContextClaims(
61
+ context_id=context_id or f"xc_{uuid.uuid4().hex}",
62
+ repository_fingerprint=self.repository_fingerprint,
63
+ startup_loaded=startup_loaded,
64
+ client_tools_unlocked=client_tools_unlocked,
65
+ run_id=run_id,
66
+ skill_id=skill_id,
67
+ mcp_session_id=mcp_session_id,
68
+ expires_at=int(time.time()) + self.ttl_seconds,
69
+ )
70
+ body = _encode(claims.to_dict())
71
+ return f"v1.{body}.{self._signature(body)}"
72
+
73
+ def verify(self, token: str) -> ContextClaims:
74
+ try:
75
+ version, body, signature = str(token).split(".", 2)
76
+ if version != "v1" or not hmac.compare_digest(signature, self._signature(body)):
77
+ raise ValueError("invalid context token signature")
78
+ payload = json.loads(_decode(body))
79
+ if payload["repository_fingerprint"] != self.repository_fingerprint:
80
+ raise ValueError("context token repository mismatch")
81
+ if int(payload["expires_at"]) < int(time.time()):
82
+ raise ValueError("context token expired")
83
+ return ContextClaims(**payload)
84
+ except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
85
+ raise ValueError("invalid context token") from exc
86
+
87
+ def refresh(self, claims: ContextClaims, **updates: Any) -> str:
88
+ values = claims.to_dict()
89
+ values.update(updates)
90
+ values.pop("context_id", None)
91
+ return self.issue(context_id=claims.context_id, **{k: values[k] for k in (
92
+ "startup_loaded", "client_tools_unlocked", "run_id", "skill_id", "mcp_session_id"
93
+ )})
94
+
95
+ def _signature(self, body: str) -> str:
96
+ return _b64(hmac.new(self._secret, body.encode("ascii"), hashlib.sha256).digest())
97
+
98
+
99
+ def _b64(value: bytes) -> str:
100
+ return base64.urlsafe_b64encode(value).decode("ascii").rstrip("=")
101
+
102
+
103
+ def _encode(value: dict[str, Any]) -> str:
104
+ return _b64(json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8"))
105
+
106
+
107
+ def _decode(value: str) -> str:
108
+ return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4)).decode("utf-8")