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.
- {xrefkit-0.4.3 → xrefkit-0.4.5}/PKG-INFO +94 -2
- {xrefkit-0.4.3 → xrefkit-0.4.5}/README.md +93 -1
- {xrefkit-0.4.3 → xrefkit-0.4.5}/pyproject.toml +1 -1
- xrefkit-0.4.5/tests/test_mcp_setup.py +107 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/__init__.py +1 -1
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/__init__.py +10 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/bootstrap.py +13 -2
- xrefkit-0.4.5/xrefkit/mcp/context_token.py +108 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/server.py +137 -17
- xrefkit-0.4.5/xrefkit/mcp/setup.py +241 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit.egg-info/PKG-INFO +94 -2
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit.egg-info/SOURCES.txt +3 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/LICENSE +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/setup.cfg +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_base_sync_ownership.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_boundary_analysis.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_calibration_lint.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_check_skill_knowledge_xids.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_cli.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_collect_analyzer_sarif.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_convert_to_xrefkit_skill.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_cs_scope_probe.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_csharp_commonality.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_csharp_naming_profile.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_ctx.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_cutover_readiness.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_dashboard.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_error_policy_audit.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_error_policy_locator.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_fm_multiroot.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_gate.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_goal_desired_state.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_instruction_workflow.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_knowledge_relations_validator.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_ownership.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_packmeta.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_project_quality_baseline.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_resource_provider.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_runtime_contracts.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_sarif_to_locator.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_skill_runtime_audit.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_skillmeta.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_structure_catalog.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xref.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xrefkit_instance.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xrefkit_tools.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xrefkit_v2_discovery.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xrefkit_v2_models.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/tests/test_xrefkit_v2_pipeline.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/__main__.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/boundary_analysis.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/catalog_cli.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/cli.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/contracts.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/ctx.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/dashboard.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/discovery.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/gate.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/goalstate.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/hashing.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/import_skill.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/instance.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/loaders.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/audit.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/catalog.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/cli.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/client_cache.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/context_registry.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/contracts.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/dist.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/ownership.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/repository.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/schemas.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp/startup_contract_pack.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/mcp_tools.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/__init__.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/common.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/effective_bundle.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/local_manifest.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/package_manifest.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/run_log.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/server_config.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/models/skill_definition.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/operations_cli.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/ownership.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/packmeta.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/registry.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resolver.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resource_provider.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/contracts.json +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/current.json +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/resources/base/model_body.md +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/runlog.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/skillmeta.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/skillrun.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/structure_catalog.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/tools/__init__.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/tools/__main__.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/v2_cli.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/workspace.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit/xref.py +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit.egg-info/dependency_links.txt +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit.egg-info/entry_points.txt +0 -0
- {xrefkit-0.4.3 → xrefkit-0.4.5}/xrefkit.egg-info/requires.txt +0 -0
- {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
|
+
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
|

|
|
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
|
|
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
|

|
|
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
|
|
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
|
|
@@ -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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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")
|