xrefkit 0.4.4__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.4 → xrefkit-0.4.5}/PKG-INFO +45 -1
- {xrefkit-0.4.4 → xrefkit-0.4.5}/README.md +44 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/pyproject.toml +1 -1
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_mcp_setup.py +35 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/__init__.py +1 -1
- xrefkit-0.4.5/xrefkit/mcp/context_token.py +108 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/server.py +137 -17
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/setup.py +13 -6
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit.egg-info/PKG-INFO +45 -1
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit.egg-info/SOURCES.txt +1 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/LICENSE +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/setup.cfg +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_base_sync_ownership.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_boundary_analysis.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_calibration_lint.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_check_skill_knowledge_xids.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_cli.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_collect_analyzer_sarif.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_convert_to_xrefkit_skill.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_cs_scope_probe.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_csharp_commonality.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_csharp_naming_profile.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_ctx.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_cutover_readiness.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_dashboard.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_error_policy_audit.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_error_policy_locator.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_fm_multiroot.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_gate.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_goal_desired_state.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_instruction_workflow.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_knowledge_relations_validator.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_ownership.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_packmeta.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_project_quality_baseline.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_resource_provider.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_runtime_contracts.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_sarif_to_locator.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_skill_runtime_audit.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_skillmeta.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_structure_catalog.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_xref.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_xrefkit_instance.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_xrefkit_tools.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_xrefkit_v2_discovery.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_xrefkit_v2_models.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/tests/test_xrefkit_v2_pipeline.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/__main__.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/boundary_analysis.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/catalog_cli.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/cli.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/contracts.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/ctx.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/dashboard.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/discovery.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/gate.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/goalstate.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/hashing.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/import_skill.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/instance.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/loaders.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/__init__.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/audit.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/bootstrap.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/catalog.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/cli.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/client_cache.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/context_registry.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/contracts.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/dist.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/ownership.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/repository.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/schemas.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp/startup_contract_pack.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/mcp_tools.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/models/__init__.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/models/common.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/models/effective_bundle.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/models/local_manifest.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/models/package_manifest.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/models/run_log.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/models/server_config.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/models/skill_definition.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/operations_cli.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/ownership.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/packmeta.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/registry.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resolver.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resource_provider.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/contracts.json +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/current.json +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/model_body.md +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/runlog.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/skillmeta.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/skillrun.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/structure_catalog.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/tools/__init__.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/tools/__main__.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/v2_cli.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/workspace.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/xref.py +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit.egg-info/dependency_links.txt +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit.egg-info/entry_points.txt +0 -0
- {xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit.egg-info/requires.txt +0 -0
- {xrefkit-0.4.4 → 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/`.
|
|
@@ -196,6 +228,18 @@ searches and XID resolutions then share the same `run_id` as the client Skill
|
|
|
196
228
|
Run. The client separately records actual model-context loading and judgment
|
|
197
229
|
application with `xrefkit skill knowledge --action load|apply`.
|
|
198
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
|
+
|
|
199
243
|
## Skill Run Observation Dashboard
|
|
200
244
|
|
|
201
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/`.
|
|
@@ -157,6 +189,18 @@ searches and XID resolutions then share the same `run_id` as the client Skill
|
|
|
157
189
|
Run. The client separately records actual model-context loading and judgment
|
|
158
190
|
application with `xrefkit skill knowledge --action load|apply`.
|
|
159
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
|
+
|
|
160
204
|
## Skill Run Observation Dashboard
|
|
161
205
|
|
|
162
206
|
The local dashboard lets a human inspect Skill run status, closure and quality
|
|
@@ -55,6 +55,41 @@ def test_mcp_setup_imports_existing_batch_skill_before_writing_report(tmp_path:
|
|
|
55
55
|
assert payload["output"] == str(output.resolve())
|
|
56
56
|
|
|
57
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
|
+
|
|
58
93
|
def test_mcp_setup_apply_copies_config_and_appends_instructions(tmp_path: Path, capsys) -> None:
|
|
59
94
|
output = tmp_path / "setup-output"
|
|
60
95
|
root = tmp_path / "repo"
|
|
@@ -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")
|
|
@@ -3,7 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
5
5
|
import logging
|
|
6
|
+
import os
|
|
6
7
|
import sys
|
|
8
|
+
import uuid
|
|
7
9
|
import weakref
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from typing import Any
|
|
@@ -11,6 +13,7 @@ from typing import Any
|
|
|
11
13
|
from . import __version__
|
|
12
14
|
from .audit import McpAuditLog, SessionRunBinding, SessionRunRegistry
|
|
13
15
|
from .catalog import XRefCatalog
|
|
16
|
+
from .context_token import CONTEXT_META_KEY, ContextClaims, ContextTokenCodec
|
|
14
17
|
from .dist import DIST_ROUTE_PATH, ArtifactDistribution, add_dist_routes
|
|
15
18
|
from xrefkit.structure_catalog import get_entry as get_structure_entry
|
|
16
19
|
from xrefkit.structure_catalog import list_findings as list_structure_findings
|
|
@@ -49,6 +52,11 @@ def _mark_startup_loaded(ctx: Any) -> None:
|
|
|
49
52
|
|
|
50
53
|
|
|
51
54
|
def _require_startup_loaded(ctx: Any, tool_name: str) -> None:
|
|
55
|
+
claims = _context_claims(ctx)
|
|
56
|
+
if claims is not None:
|
|
57
|
+
if not claims.startup_loaded:
|
|
58
|
+
raise RuntimeError(f"XREFKIT_STARTUP_REQUIRED: call get_startup_context before {tool_name}")
|
|
59
|
+
return
|
|
52
60
|
session = _session_of(ctx)
|
|
53
61
|
if session is not None and session not in _STARTUP_LOADED_SESSIONS:
|
|
54
62
|
raise RuntimeError(
|
|
@@ -72,6 +80,7 @@ def _with_control_reminder(result: dict[str, Any]) -> dict[str, Any]:
|
|
|
72
80
|
# per-Skill flag would make distribution unreachable for Skills that don't
|
|
73
81
|
# declare required_tools even though the general tool catalog still applies.
|
|
74
82
|
_CLIENT_TOOLS_UNLOCKED_SESSIONS: "weakref.WeakSet[Any]" = weakref.WeakSet()
|
|
83
|
+
_CONTEXT_CODEC: ContextTokenCodec | None = None
|
|
75
84
|
|
|
76
85
|
|
|
77
86
|
def _unlock_client_tools(ctx: Any) -> None:
|
|
@@ -80,7 +89,46 @@ def _unlock_client_tools(ctx: Any) -> None:
|
|
|
80
89
|
_CLIENT_TOOLS_UNLOCKED_SESSIONS.add(session)
|
|
81
90
|
|
|
82
91
|
|
|
92
|
+
def _context_claims(ctx: Any) -> ContextClaims | None:
|
|
93
|
+
if _CONTEXT_CODEC is None:
|
|
94
|
+
return None
|
|
95
|
+
meta = getattr(getattr(ctx, "request_context", None), "meta", None)
|
|
96
|
+
extra = getattr(meta, "model_extra", None) or (meta if isinstance(meta, dict) else {})
|
|
97
|
+
token = extra.get(CONTEXT_META_KEY)
|
|
98
|
+
if token is None:
|
|
99
|
+
return None
|
|
100
|
+
try:
|
|
101
|
+
return _CONTEXT_CODEC.verify(str(token))
|
|
102
|
+
except ValueError as exc:
|
|
103
|
+
raise RuntimeError("XREFKIT_CONTEXT_INVALID: context_id could not be verified") from exc
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _context_token(claims: ContextClaims | None) -> str | None:
|
|
107
|
+
if _CONTEXT_CODEC is None or claims is None:
|
|
108
|
+
return None
|
|
109
|
+
return _CONTEXT_CODEC.refresh(claims)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _binding_for(ctx: Any, run_registry: SessionRunRegistry) -> SessionRunBinding | None:
|
|
113
|
+
claims = _context_claims(ctx)
|
|
114
|
+
if claims is not None and claims.run_id and claims.skill_id and claims.mcp_session_id:
|
|
115
|
+
return SessionRunBinding(
|
|
116
|
+
run_id=claims.run_id,
|
|
117
|
+
mcp_session_id=claims.mcp_session_id,
|
|
118
|
+
repository_fingerprint=claims.repository_fingerprint,
|
|
119
|
+
skill_id=claims.skill_id,
|
|
120
|
+
)
|
|
121
|
+
return run_registry.current(_session_of(ctx))
|
|
122
|
+
|
|
123
|
+
|
|
83
124
|
def _require_client_tools_unlocked(ctx: Any, tool_name: str) -> None:
|
|
125
|
+
claims = _context_claims(ctx)
|
|
126
|
+
if claims is not None:
|
|
127
|
+
if not claims.client_tools_unlocked:
|
|
128
|
+
raise RuntimeError(
|
|
129
|
+
f"XREFKIT_SKILL_SELECTION_REQUIRED: call get_skill or get_skill_requirements before {tool_name}"
|
|
130
|
+
)
|
|
131
|
+
return
|
|
84
132
|
session = _session_of(ctx)
|
|
85
133
|
if session is not None and session not in _CLIENT_TOOLS_UNLOCKED_SESSIONS:
|
|
86
134
|
raise RuntimeError(
|
|
@@ -139,6 +187,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
139
187
|
action="store_true",
|
|
140
188
|
help="Enable /dist executable artifacts after deployment trust is configured.",
|
|
141
189
|
)
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
"--stateless-http",
|
|
192
|
+
action="store_true",
|
|
193
|
+
help="Serve Streamable HTTP without MCP protocol sessions; requires XREFKIT_CONTEXT_SECRET.",
|
|
194
|
+
)
|
|
195
|
+
parser.add_argument(
|
|
196
|
+
"--context-secret",
|
|
197
|
+
help="HMAC secret for client-carried XRefKit context tokens; defaults to XREFKIT_CONTEXT_SECRET.",
|
|
198
|
+
)
|
|
142
199
|
parser.add_argument(
|
|
143
200
|
"--distribution-trust-id",
|
|
144
201
|
help="Out-of-band pinned release manifest or signing-key identity.",
|
|
@@ -156,6 +213,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
156
213
|
help="Structured MCP audit JSONL path. Defaults to <repo>/work/mcp/xid_audit.jsonl.",
|
|
157
214
|
)
|
|
158
215
|
args = parser.parse_args(argv)
|
|
216
|
+
if args.stateless_http and args.transport != "streamable-http":
|
|
217
|
+
parser.error("--stateless-http requires --transport streamable-http")
|
|
218
|
+
context_secret = args.context_secret or os.environ.get("XREFKIT_CONTEXT_SECRET")
|
|
219
|
+
if args.stateless_http and not context_secret:
|
|
220
|
+
parser.error("--stateless-http requires --context-secret or XREFKIT_CONTEXT_SECRET")
|
|
159
221
|
try:
|
|
160
222
|
_validate_tls_configuration(
|
|
161
223
|
args.transport,
|
|
@@ -177,6 +239,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
177
239
|
parser.error(str(exc))
|
|
178
240
|
|
|
179
241
|
catalog = XRefCatalog.build(Path(args.repo), args.domain_knowledge_root)
|
|
242
|
+
global _CONTEXT_CODEC
|
|
243
|
+
_CONTEXT_CODEC = ContextTokenCodec(
|
|
244
|
+
context_secret or os.urandom(32).hex(),
|
|
245
|
+
catalog.repository_fingerprint,
|
|
246
|
+
)
|
|
180
247
|
audit_log = McpAuditLog(args.audit_log or (Path(args.repo) / "work" / "mcp" / "xid_audit.jsonl"))
|
|
181
248
|
run_registry = SessionRunRegistry()
|
|
182
249
|
|
|
@@ -212,6 +279,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
212
279
|
port=args.port,
|
|
213
280
|
streamable_http_path=args.http_path,
|
|
214
281
|
log_level=args.log_level.upper(),
|
|
282
|
+
stateless_http=args.stateless_http,
|
|
215
283
|
)
|
|
216
284
|
|
|
217
285
|
@app.tool()
|
|
@@ -221,14 +289,29 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
221
289
|
@app.tool()
|
|
222
290
|
def bind_skill_run(ctx: Context, run_id: str, skill_id: str) -> dict[str, Any]:
|
|
223
291
|
_require_startup_loaded(ctx, "bind_skill_run")
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
292
|
+
claims = _context_claims(ctx)
|
|
293
|
+
if claims is not None:
|
|
294
|
+
try:
|
|
295
|
+
normalized_run_id = str(uuid.UUID(str(run_id)))
|
|
296
|
+
except ValueError as exc:
|
|
297
|
+
raise ValueError(f"run_id must be a UUID: {run_id}") from exc
|
|
298
|
+
if claims.run_id and claims.run_id != normalized_run_id:
|
|
299
|
+
raise ValueError("context_id is already bound to a different Skill Run")
|
|
300
|
+
binding = SessionRunBinding(
|
|
301
|
+
run_id=normalized_run_id,
|
|
302
|
+
mcp_session_id=claims.mcp_session_id or claims.context_id,
|
|
303
|
+
repository_fingerprint=catalog.repository_fingerprint,
|
|
304
|
+
skill_id=skill_id,
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
binding = run_registry.bind(
|
|
308
|
+
_session_of(ctx),
|
|
309
|
+
run_id=run_id,
|
|
310
|
+
repository_fingerprint=catalog.repository_fingerprint,
|
|
311
|
+
skill_id=skill_id,
|
|
312
|
+
)
|
|
230
313
|
audit_log.append("run.bound", binding=binding, tool="bind_skill_run")
|
|
231
|
-
|
|
314
|
+
result = {
|
|
232
315
|
**binding.to_dict(),
|
|
233
316
|
"audit_enabled": True,
|
|
234
317
|
"client_record_command": (
|
|
@@ -237,11 +320,26 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
237
320
|
f"--repository-fingerprint {binding.repository_fingerprint}"
|
|
238
321
|
),
|
|
239
322
|
}
|
|
323
|
+
if claims is not None:
|
|
324
|
+
result["context_id"] = _CONTEXT_CODEC.refresh(
|
|
325
|
+
claims,
|
|
326
|
+
startup_loaded=True,
|
|
327
|
+
run_id=binding.run_id,
|
|
328
|
+
skill_id=binding.skill_id,
|
|
329
|
+
mcp_session_id=binding.mcp_session_id,
|
|
330
|
+
)
|
|
331
|
+
return result
|
|
240
332
|
|
|
241
333
|
@app.tool()
|
|
242
334
|
def end_skill_run(ctx: Context, run_id: str) -> dict[str, str]:
|
|
243
335
|
_require_startup_loaded(ctx, "end_skill_run")
|
|
244
|
-
|
|
336
|
+
claims = _context_claims(ctx)
|
|
337
|
+
if claims is not None:
|
|
338
|
+
binding = _binding_for(ctx, run_registry)
|
|
339
|
+
if binding is None or binding.run_id != run_id:
|
|
340
|
+
raise ValueError("context_id does not match the active MCP Skill Run binding")
|
|
341
|
+
else:
|
|
342
|
+
binding = run_registry.end(_session_of(ctx), run_id=run_id)
|
|
245
343
|
audit_log.append("run.ended", binding=binding, tool="end_skill_run")
|
|
246
344
|
return binding.to_dict()
|
|
247
345
|
|
|
@@ -254,6 +352,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
254
352
|
for xid in result.get("load_order", []):
|
|
255
353
|
_log_xid_query("get_startup_context", xid)
|
|
256
354
|
_mark_startup_loaded(ctx)
|
|
355
|
+
if _CONTEXT_CODEC is not None:
|
|
356
|
+
claims = _context_claims(ctx)
|
|
357
|
+
if claims is None:
|
|
358
|
+
result["context_id"] = _CONTEXT_CODEC.issue(startup_loaded=True)
|
|
359
|
+
else:
|
|
360
|
+
result["context_id"] = _CONTEXT_CODEC.refresh(claims, startup_loaded=True)
|
|
257
361
|
if dist is not None:
|
|
258
362
|
result = _with_artifact_distribution(result, dist, dist_base_url)
|
|
259
363
|
return result
|
|
@@ -293,7 +397,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
293
397
|
|
|
294
398
|
@app.tool()
|
|
295
399
|
def search_knowledge_catalog(ctx: Context, query: str, limit: int = 10) -> list[dict[str, Any]]:
|
|
296
|
-
binding =
|
|
400
|
+
binding = _binding_for(ctx, run_registry)
|
|
297
401
|
result = catalog.search_knowledge_catalog(query, limit)
|
|
298
402
|
if binding is not None:
|
|
299
403
|
audit_log.append(
|
|
@@ -309,7 +413,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
309
413
|
@app.tool()
|
|
310
414
|
def get_knowledge_summary(ctx: Context, xid: str) -> dict[str, Any]:
|
|
311
415
|
_require_startup_loaded(ctx, "get_knowledge_summary")
|
|
312
|
-
binding =
|
|
416
|
+
binding = _binding_for(ctx, run_registry)
|
|
313
417
|
result = catalog.expand_knowledge(xid)["entry"]
|
|
314
418
|
_log_xid_query(
|
|
315
419
|
"get_knowledge_summary",
|
|
@@ -323,7 +427,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
323
427
|
@app.tool()
|
|
324
428
|
def expand_knowledge(ctx: Context, xid: str) -> dict[str, Any]:
|
|
325
429
|
_require_startup_loaded(ctx, "expand_knowledge")
|
|
326
|
-
binding =
|
|
430
|
+
binding = _binding_for(ctx, run_registry)
|
|
327
431
|
result = catalog.expand_knowledge(xid)
|
|
328
432
|
_log_xid_query(
|
|
329
433
|
"expand_knowledge",
|
|
@@ -341,7 +445,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
341
445
|
known_version: str | None = None,
|
|
342
446
|
) -> dict[str, Any]:
|
|
343
447
|
_require_startup_loaded(ctx, "get_document_by_xid")
|
|
344
|
-
binding =
|
|
448
|
+
binding = _binding_for(ctx, run_registry)
|
|
345
449
|
result = catalog.get_document_by_xid(xid, known_version)
|
|
346
450
|
_log_xid_query(
|
|
347
451
|
"get_document_by_xid",
|
|
@@ -357,7 +461,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
357
461
|
@app.tool()
|
|
358
462
|
def build_knowledge_context(ctx: Context, query: str, limit: int = 5) -> dict[str, Any]:
|
|
359
463
|
_require_startup_loaded(ctx, "build_knowledge_context")
|
|
360
|
-
binding =
|
|
464
|
+
binding = _binding_for(ctx, run_registry)
|
|
361
465
|
result = catalog.build_knowledge_context(query, limit)
|
|
362
466
|
if binding is not None:
|
|
363
467
|
audit_log.append(
|
|
@@ -403,7 +507,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
403
507
|
known_document_versions: dict[str, str] | None = None,
|
|
404
508
|
) -> dict[str, Any]:
|
|
405
509
|
_require_startup_loaded(ctx, "get_skill")
|
|
406
|
-
binding =
|
|
510
|
+
binding = _binding_for(ctx, run_registry)
|
|
407
511
|
if binding is not None and binding.skill_id != skill_id:
|
|
408
512
|
raise ValueError(
|
|
409
513
|
f"get_skill requested {skill_id!r}, but the active Skill Run is bound to {binding.skill_id!r}"
|
|
@@ -417,14 +521,30 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
417
521
|
selected_skill=skill_id,
|
|
418
522
|
)
|
|
419
523
|
_unlock_client_tools(ctx)
|
|
420
|
-
|
|
524
|
+
response = _with_control_reminder(result)
|
|
525
|
+
claims = _context_claims(ctx)
|
|
526
|
+
if claims is not None:
|
|
527
|
+
response["context_id"] = _CONTEXT_CODEC.refresh(
|
|
528
|
+
claims,
|
|
529
|
+
startup_loaded=True,
|
|
530
|
+
client_tools_unlocked=True,
|
|
531
|
+
)
|
|
532
|
+
return response
|
|
421
533
|
|
|
422
534
|
@app.tool()
|
|
423
535
|
def get_skill_requirements(ctx: Context, skill_id: str) -> dict[str, Any]:
|
|
424
536
|
_require_startup_loaded(ctx, "get_skill_requirements")
|
|
425
537
|
result = catalog.get_skill_requirements(skill_id)
|
|
426
538
|
_unlock_client_tools(ctx)
|
|
427
|
-
|
|
539
|
+
response = _with_control_reminder(result)
|
|
540
|
+
claims = _context_claims(ctx)
|
|
541
|
+
if claims is not None:
|
|
542
|
+
response["context_id"] = _CONTEXT_CODEC.refresh(
|
|
543
|
+
claims,
|
|
544
|
+
startup_loaded=True,
|
|
545
|
+
client_tools_unlocked=True,
|
|
546
|
+
)
|
|
547
|
+
return response
|
|
428
548
|
|
|
429
549
|
@app.tool()
|
|
430
550
|
def resolve_skill_knowledge(ctx: Context, skill_id: str) -> dict[str, Any]:
|
|
@@ -433,7 +553,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
433
553
|
|
|
434
554
|
@app.tool()
|
|
435
555
|
def rank_skills_for_purpose(ctx: Context, purpose: str, limit: int = 5) -> list[dict[str, Any]]:
|
|
436
|
-
binding =
|
|
556
|
+
binding = _binding_for(ctx, run_registry)
|
|
437
557
|
result = catalog.rank_skills_for_purpose(purpose, limit)
|
|
438
558
|
if binding is not None:
|
|
439
559
|
audit_log.append(
|
|
@@ -135,7 +135,7 @@ def setup(args: argparse.Namespace) -> int:
|
|
|
135
135
|
metas: list[Path] = []
|
|
136
136
|
|
|
137
137
|
if args.import_source:
|
|
138
|
-
from xrefkit.import_skill import convert_skill, convert_skill_tree
|
|
138
|
+
from xrefkit.import_skill import _find_skill_doc, _slug, convert_skill, convert_skill_tree
|
|
139
139
|
|
|
140
140
|
source = Path(args.import_source).resolve()
|
|
141
141
|
if (source / "skills").is_dir() or args.batch:
|
|
@@ -148,14 +148,20 @@ def setup(args: argparse.Namespace) -> int:
|
|
|
148
148
|
)
|
|
149
149
|
import_report = result.to_dict()
|
|
150
150
|
else:
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
try:
|
|
152
|
+
_find_skill_doc(source, None)
|
|
153
|
+
except FileNotFoundError as exc:
|
|
154
|
+
raise SystemExit(
|
|
155
|
+
"--import must point to a Skill directory containing SKILL.md or README.md, "
|
|
156
|
+
"or a directory containing skills/"
|
|
157
|
+
) from exc
|
|
158
|
+
skill_id = args.skill_id or _slug(source.name)
|
|
153
159
|
result = convert_skill(
|
|
154
160
|
source_dir=source,
|
|
155
161
|
source_root=source.parent,
|
|
156
162
|
repo_root=root,
|
|
157
|
-
skill_id=
|
|
158
|
-
target_skill_dir=root / "skills" /
|
|
163
|
+
skill_id=skill_id,
|
|
164
|
+
target_skill_dir=root / "skills" / skill_id,
|
|
159
165
|
dry_run=False,
|
|
160
166
|
)
|
|
161
167
|
import_report = result.to_dict()
|
|
@@ -211,7 +217,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
211
217
|
parser = argparse.ArgumentParser(prog="xrefkit mcp")
|
|
212
218
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
213
219
|
setup_parser = sub.add_parser("setup", help="import Skills and generate reviewable MCP setup artifacts")
|
|
214
|
-
setup_parser.add_argument("--repo",
|
|
220
|
+
setup_parser.add_argument("--repo", default=".", help="Target XRefKit repository; defaults to the current directory")
|
|
215
221
|
setup_parser.add_argument("--import", dest="import_source", default=None, help="Skill directory or batch root")
|
|
216
222
|
setup_parser.add_argument("--batch", action="store_true")
|
|
217
223
|
setup_parser.add_argument("--skill-id-prefix", default="imported")
|
|
@@ -232,3 +238,4 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
232
238
|
|
|
233
239
|
if __name__ == "__main__":
|
|
234
240
|
raise SystemExit(main())
|
|
241
|
+
|
|
@@ -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/`.
|
|
@@ -196,6 +228,18 @@ searches and XID resolutions then share the same `run_id` as the client Skill
|
|
|
196
228
|
Run. The client separately records actual model-context loading and judgment
|
|
197
229
|
application with `xrefkit skill knowledge --action load|apply`.
|
|
198
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
|
+
|
|
199
243
|
## Skill Run Observation Dashboard
|
|
200
244
|
|
|
201
245
|
The local dashboard lets a human inspect Skill run status, closure and quality
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json
RENAMED
|
File without changes
|
{xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md
RENAMED
|
File without changes
|
{xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json
RENAMED
|
File without changes
|
{xrefkit-0.4.4 → xrefkit-0.4.5}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|