xrefkit 0.4.3__tar.gz → 0.4.4__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 (108) hide show
  1. {xrefkit-0.4.3 → xrefkit-0.4.4}/PKG-INFO +50 -2
  2. {xrefkit-0.4.3 → xrefkit-0.4.4}/README.md +49 -1
  3. {xrefkit-0.4.3 → xrefkit-0.4.4}/pyproject.toml +1 -1
  4. xrefkit-0.4.4/tests/test_mcp_setup.py +72 -0
  5. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/__init__.py +1 -1
  6. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/__init__.py +10 -0
  7. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/bootstrap.py +13 -2
  8. xrefkit-0.4.4/xrefkit/mcp/setup.py +234 -0
  9. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit.egg-info/PKG-INFO +50 -2
  10. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit.egg-info/SOURCES.txt +2 -0
  11. {xrefkit-0.4.3 → xrefkit-0.4.4}/LICENSE +0 -0
  12. {xrefkit-0.4.3 → xrefkit-0.4.4}/setup.cfg +0 -0
  13. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_base_sync_ownership.py +0 -0
  14. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_boundary_analysis.py +0 -0
  15. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_calibration_lint.py +0 -0
  16. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_check_skill_knowledge_xids.py +0 -0
  17. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_cli.py +0 -0
  18. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_collect_analyzer_sarif.py +0 -0
  19. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_convert_to_xrefkit_skill.py +0 -0
  20. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_cs_scope_probe.py +0 -0
  21. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_csharp_commonality.py +0 -0
  22. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_csharp_naming_profile.py +0 -0
  23. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_ctx.py +0 -0
  24. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_cutover_readiness.py +0 -0
  25. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_dashboard.py +0 -0
  26. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_error_policy_audit.py +0 -0
  27. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_error_policy_locator.py +0 -0
  28. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_fm_multiroot.py +0 -0
  29. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_gate.py +0 -0
  30. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_goal_desired_state.py +0 -0
  31. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_instruction_workflow.py +0 -0
  32. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_knowledge_relations_validator.py +0 -0
  33. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_ownership.py +0 -0
  34. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_packmeta.py +0 -0
  35. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_project_quality_baseline.py +0 -0
  36. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_resource_provider.py +0 -0
  37. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_runtime_contracts.py +0 -0
  38. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_sarif_to_locator.py +0 -0
  39. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_skill_runtime_audit.py +0 -0
  40. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_skillmeta.py +0 -0
  41. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_structure_catalog.py +0 -0
  42. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_xref.py +0 -0
  43. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_xrefkit_instance.py +0 -0
  44. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_xrefkit_tools.py +0 -0
  45. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_xrefkit_v2_discovery.py +0 -0
  46. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_xrefkit_v2_models.py +0 -0
  47. {xrefkit-0.4.3 → xrefkit-0.4.4}/tests/test_xrefkit_v2_pipeline.py +0 -0
  48. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/__main__.py +0 -0
  49. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/boundary_analysis.py +0 -0
  50. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/catalog_cli.py +0 -0
  51. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/cli.py +0 -0
  52. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/contracts.py +0 -0
  53. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/ctx.py +0 -0
  54. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/dashboard.py +0 -0
  55. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/discovery.py +0 -0
  56. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/gate.py +0 -0
  57. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/goalstate.py +0 -0
  58. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/hashing.py +0 -0
  59. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/import_skill.py +0 -0
  60. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/instance.py +0 -0
  61. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/loaders.py +0 -0
  62. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/audit.py +0 -0
  63. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/catalog.py +0 -0
  64. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/cli.py +0 -0
  65. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/client_cache.py +0 -0
  66. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/context_registry.py +0 -0
  67. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/contracts.py +0 -0
  68. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/dist.py +0 -0
  69. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/ownership.py +0 -0
  70. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/repository.py +0 -0
  71. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/schemas.py +0 -0
  72. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/server.py +0 -0
  73. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp/startup_contract_pack.py +0 -0
  74. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/mcp_tools.py +0 -0
  75. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/models/__init__.py +0 -0
  76. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/models/common.py +0 -0
  77. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/models/effective_bundle.py +0 -0
  78. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/models/local_manifest.py +0 -0
  79. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/models/package_manifest.py +0 -0
  80. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/models/run_log.py +0 -0
  81. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/models/server_config.py +0 -0
  82. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/models/skill_definition.py +0 -0
  83. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/operations_cli.py +0 -0
  84. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/ownership.py +0 -0
  85. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/packmeta.py +0 -0
  86. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/registry.py +0 -0
  87. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/resolver.py +0 -0
  88. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/resource_provider.py +0 -0
  89. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/resources/base/contracts.json +0 -0
  90. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/resources/base/current.json +0 -0
  91. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
  92. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
  93. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
  94. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
  95. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/resources/base/model_body.md +0 -0
  96. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/runlog.py +0 -0
  97. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/skillmeta.py +0 -0
  98. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/skillrun.py +0 -0
  99. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/structure_catalog.py +0 -0
  100. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/tools/__init__.py +0 -0
  101. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/tools/__main__.py +0 -0
  102. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/v2_cli.py +0 -0
  103. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/workspace.py +0 -0
  104. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit/xref.py +0 -0
  105. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit.egg-info/dependency_links.txt +0 -0
  106. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit.egg-info/entry_points.txt +0 -0
  107. {xrefkit-0.4.3 → xrefkit-0.4.4}/xrefkit.egg-info/requires.txt +0 -0
  108. {xrefkit-0.4.3 → xrefkit-0.4.4}/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.4
4
4
  Summary: Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit
5
5
  Author: synthaicode
6
6
  License: MIT License
@@ -126,7 +126,36 @@ reason instead of inventing a criterion.
126
126
 
127
127
  ## Quick Start
128
128
 
129
- Install the package and initialize an instance:
129
+ ### Install from PyPI
130
+
131
+ XRefKit requires Python 3.11 or later. For a normal installation, create a
132
+ virtual environment and install the published package from PyPI:
133
+
134
+ ```powershell
135
+ python -m venv .venv
136
+ .\.venv\Scripts\Activate.ps1
137
+ python -m pip install --upgrade pip
138
+ python -m pip install xrefkit
139
+ xrefkit init
140
+ xrefkit --help
141
+ ```
142
+
143
+ If the `xrefkit` command is not available on `PATH`, use the module form:
144
+
145
+ ```powershell
146
+ python -m xrefkit --help
147
+ ```
148
+
149
+ To use the integrated MCP server, install the optional MCP dependencies:
150
+
151
+ ```powershell
152
+ python -m pip install "xrefkit[mcp]"
153
+ xrefkit mcp serve --repo . --transport stdio
154
+ ```
155
+
156
+ ### Install from a checkout
157
+
158
+ For XRefKit development, install the local checkout in editable mode instead:
130
159
 
131
160
  ```powershell
132
161
  python -m pip install -e .
@@ -140,6 +169,25 @@ Start the integrated MCP server over stdio:
140
169
  xrefkit mcp serve --repo . --transport stdio
141
170
  ```
142
171
 
172
+ To import existing Skills and prepare a reviewable VS Code MCP setup, run:
173
+
174
+ ```powershell
175
+ python -m pip install "xrefkit[mcp]"
176
+ xrefkit mcp setup `
177
+ --repo C:\dev\itsm\XRefKit `
178
+ --import C:\work\existing-skills
179
+ ```
180
+
181
+ The command writes `SETUP.md`, `import-report.json`, a VS Code
182
+ `.vscode/mcp.json` example, and reviewed append text for `AGENTS.md` and
183
+ `CLAUDE.md` into a temporary setup folder. Apply those artifacts after review:
184
+
185
+ ```powershell
186
+ xrefkit mcp setup apply `
187
+ --source C:\Users\<user>\AppData\Local\Temp\xrefkit-setup-<id> `
188
+ --repo C:\dev\itsm\XRefKit
189
+ ```
190
+
143
191
  The server writes structured correlation events to
144
192
  `work/mcp/xid_audit.jsonl` by default. After `xrefkit skill run` returns a
145
193
  `run_id`, the client calls MCP `bind_skill_run` and executes the returned
@@ -87,7 +87,36 @@ reason instead of inventing a criterion.
87
87
 
88
88
  ## Quick Start
89
89
 
90
- Install the package and initialize an instance:
90
+ ### Install from PyPI
91
+
92
+ XRefKit requires Python 3.11 or later. For a normal installation, create a
93
+ virtual environment and install the published package from PyPI:
94
+
95
+ ```powershell
96
+ python -m venv .venv
97
+ .\.venv\Scripts\Activate.ps1
98
+ python -m pip install --upgrade pip
99
+ python -m pip install xrefkit
100
+ xrefkit init
101
+ xrefkit --help
102
+ ```
103
+
104
+ If the `xrefkit` command is not available on `PATH`, use the module form:
105
+
106
+ ```powershell
107
+ python -m xrefkit --help
108
+ ```
109
+
110
+ To use the integrated MCP server, install the optional MCP dependencies:
111
+
112
+ ```powershell
113
+ python -m pip install "xrefkit[mcp]"
114
+ xrefkit mcp serve --repo . --transport stdio
115
+ ```
116
+
117
+ ### Install from a checkout
118
+
119
+ For XRefKit development, install the local checkout in editable mode instead:
91
120
 
92
121
  ```powershell
93
122
  python -m pip install -e .
@@ -101,6 +130,25 @@ Start the integrated MCP server over stdio:
101
130
  xrefkit mcp serve --repo . --transport stdio
102
131
  ```
103
132
 
133
+ To import existing Skills and prepare a reviewable VS Code MCP setup, run:
134
+
135
+ ```powershell
136
+ python -m pip install "xrefkit[mcp]"
137
+ xrefkit mcp setup `
138
+ --repo C:\dev\itsm\XRefKit `
139
+ --import C:\work\existing-skills
140
+ ```
141
+
142
+ The command writes `SETUP.md`, `import-report.json`, a VS Code
143
+ `.vscode/mcp.json` example, and reviewed append text for `AGENTS.md` and
144
+ `CLAUDE.md` into a temporary setup folder. Apply those artifacts after review:
145
+
146
+ ```powershell
147
+ xrefkit mcp setup apply `
148
+ --source C:\Users\<user>\AppData\Local\Temp\xrefkit-setup-<id> `
149
+ --repo C:\dev\itsm\XRefKit
150
+ ```
151
+
104
152
  The server writes structured correlation events to
105
153
  `work/mcp/xid_audit.jsonl` by default. After `xrefkit skill run` returns a
106
154
  `run_id`, the client calls MCP `bind_skill_run` and executes the returned
@@ -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.4"
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,72 @@
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_apply_copies_config_and_appends_instructions(tmp_path: Path, capsys) -> None:
59
+ output = tmp_path / "setup-output"
60
+ root = tmp_path / "repo"
61
+ root.mkdir()
62
+ assert main(["mcp", "setup", "--repo", str(root), "--output", str(output)]) == 0
63
+ capsys.readouterr()
64
+
65
+ assert main(["mcp", "setup-apply", "--source", str(output), "--repo", str(root)]) == 0
66
+ capsys.readouterr()
67
+ assert (root / ".vscode" / "mcp.json").is_file()
68
+ agents = (root / "AGENTS.md").read_text(encoding="utf-8")
69
+ assert "XRefKit MCP Skill Routing" in agents
70
+
71
+ assert main(["mcp", "setup-apply", "--source", str(output), "--repo", str(root)]) == 0
72
+ 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.4"
@@ -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,234 @@
1
+ """Prepare an XRefKit repository and client configuration for MCP use."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import shutil
8
+ import subprocess
9
+ import sys
10
+ import tempfile
11
+ from pathlib import Path
12
+
13
+
14
+ AGENT_APPEND = """## XRefKit MCP Skill Routing
15
+
16
+ When the XRefKit MCP server is configured and available in this client, use
17
+ its Skill catalog and semantic routing for Skill selection.
18
+
19
+ The VS Code workspace MCP configuration is `.vscode/mcp.json`.
20
+
21
+ Do not manually import or select individual Skill files during normal task
22
+ execution. The administrator manages Skill registration; the client uses the
23
+ MCP catalog to select and execute Skills.
24
+ """
25
+
26
+
27
+ def _vscode_config(root: Path) -> dict[str, object]:
28
+ python_command = "python"
29
+ venv_python = root / ".venv" / "Scripts" / "python.exe"
30
+ if venv_python.is_file():
31
+ python_command = "${workspaceFolder}\\.venv\\Scripts\\python.exe"
32
+ return {
33
+ "servers": {
34
+ "xrefkit": {
35
+ "type": "stdio",
36
+ "command": python_command,
37
+ "args": [
38
+ "-m",
39
+ "xrefkit",
40
+ "mcp",
41
+ "serve",
42
+ "--repo",
43
+ "${workspaceFolder}",
44
+ "--transport",
45
+ "stdio",
46
+ ],
47
+ }
48
+ }
49
+ }
50
+
51
+
52
+ def _run_xref_fix(root: Path) -> dict[str, object]:
53
+ proc = subprocess.run(
54
+ [sys.executable, "-m", "xrefkit", "xref", "fix", "--root", str(root), "--json"],
55
+ capture_output=True,
56
+ text=True,
57
+ check=False,
58
+ )
59
+ try:
60
+ payload: object = json.loads(proc.stdout)
61
+ except json.JSONDecodeError:
62
+ payload = {"stdout": proc.stdout.strip(), "stderr": proc.stderr.strip()}
63
+ return {"returncode": proc.returncode, "result": payload}
64
+
65
+
66
+ def _run_skill_checks(root: Path, metas: list[Path]) -> list[dict[str, object]]:
67
+ results: list[dict[str, object]] = []
68
+ for meta in metas:
69
+ proc = subprocess.run(
70
+ [
71
+ sys.executable,
72
+ "-m",
73
+ "xrefkit",
74
+ "skill",
75
+ "check",
76
+ "--root",
77
+ str(root),
78
+ "--meta",
79
+ str(meta),
80
+ "--level",
81
+ "trial",
82
+ "--json",
83
+ ],
84
+ capture_output=True,
85
+ text=True,
86
+ check=False,
87
+ )
88
+ try:
89
+ result: object = json.loads(proc.stdout)
90
+ except json.JSONDecodeError:
91
+ result = {"stdout": proc.stdout.strip(), "stderr": proc.stderr.strip()}
92
+ results.append({"meta": meta.relative_to(root).as_posix(), "returncode": proc.returncode, "result": result})
93
+ return results
94
+
95
+
96
+ def _write_setup_files(
97
+ output: Path,
98
+ *,
99
+ root: Path,
100
+ import_report: dict[str, object] | None,
101
+ xref_report: dict[str, object],
102
+ checks: list[dict[str, object]],
103
+ ) -> None:
104
+ output.mkdir(parents=True, exist_ok=True)
105
+ (output / "vscode-mcp.json").write_text(
106
+ json.dumps(_vscode_config(root), ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
107
+ )
108
+ (output / "AGENTS.md.append.md").write_text(AGENT_APPEND, encoding="utf-8")
109
+ (output / "CLAUDE.md.append.md").write_text(AGENT_APPEND, encoding="utf-8")
110
+ report = {"root": str(root), "import": import_report, "xref_fix": xref_report, "skill_checks": checks}
111
+ (output / "import-report.json").write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
112
+ guide = f"""# XRefKit MCP setup review
113
+
114
+ Generated setup files:
115
+
116
+ - `vscode-mcp.json`: copy to `{(root / '.vscode' / 'mcp.json').as_posix()}`.
117
+ - `AGENTS.md.append.md`: review and append to `AGENTS.md`.
118
+ - `CLAUDE.md.append.md`: review and append to `CLAUDE.md`.
119
+ - `import-report.json`: import, XID fix, and Skill validation results.
120
+
121
+ The setup command does not modify client instruction files or overwrite an
122
+ existing VS Code MCP configuration. Apply these files only after review.
123
+
124
+ After applying the files, open the repository in VS Code and start the
125
+ `xrefkit` MCP server from the MCP controls.
126
+ """
127
+ (output / "SETUP.md").write_text(guide, encoding="utf-8")
128
+
129
+
130
+ def setup(args: argparse.Namespace) -> int:
131
+ root = Path(args.repo).resolve()
132
+ root.mkdir(parents=True, exist_ok=True)
133
+ output = Path(args.output).resolve() if args.output else Path(tempfile.mkdtemp(prefix="xrefkit-setup-"))
134
+ import_report: dict[str, object] | None = None
135
+ metas: list[Path] = []
136
+
137
+ if args.import_source:
138
+ from xrefkit.import_skill import convert_skill, convert_skill_tree
139
+
140
+ source = Path(args.import_source).resolve()
141
+ if (source / "skills").is_dir() or args.batch:
142
+ result = convert_skill_tree(
143
+ source_root=source,
144
+ repo_root=root,
145
+ skill_id_prefix=args.skill_id_prefix,
146
+ target_skill_root=root / "skills",
147
+ dry_run=False,
148
+ )
149
+ import_report = result.to_dict()
150
+ else:
151
+ if not args.skill_id:
152
+ raise SystemExit("--skill-id is required when --import is a single Skill directory")
153
+ result = convert_skill(
154
+ source_dir=source,
155
+ source_root=source.parent,
156
+ repo_root=root,
157
+ skill_id=args.skill_id,
158
+ target_skill_dir=root / "skills" / args.skill_id,
159
+ dry_run=False,
160
+ )
161
+ import_report = result.to_dict()
162
+
163
+ if isinstance(import_report, dict):
164
+ metas = [root / Path(item["meta_doc"]) for item in import_report.get("converted_skills", []) if item.get("meta_doc")]
165
+ if not metas and import_report.get("meta_doc"):
166
+ metas = [root / Path(import_report["meta_doc"])]
167
+
168
+ xref_report = _run_xref_fix(root)
169
+ checks = _run_skill_checks(root, metas)
170
+ _write_setup_files(output, root=root, import_report=import_report, xref_report=xref_report, checks=checks)
171
+
172
+ payload = {"ok": xref_report["returncode"] == 0 and all(item["returncode"] == 0 for item in checks), "output": str(output), "report": str(output / "import-report.json")}
173
+ if args.json:
174
+ print(json.dumps(payload, ensure_ascii=False, indent=2))
175
+ else:
176
+ print(f"setup workspace: {output}")
177
+ print(f"guide: {output / 'SETUP.md'}")
178
+ print(f"report: {output / 'import-report.json'}")
179
+ return 0 if payload["ok"] else 1
180
+
181
+
182
+ def apply_setup(args: argparse.Namespace) -> int:
183
+ source = Path(args.source).resolve()
184
+ root = Path(args.repo).resolve()
185
+ if not source.is_dir():
186
+ raise SystemExit(f"setup workspace does not exist: {source}")
187
+ destinations = {
188
+ source / "vscode-mcp.json": root / ".vscode" / "mcp.json",
189
+ source / "AGENTS.md.append.md": root / "AGENTS.md",
190
+ source / "CLAUDE.md.append.md": root / "CLAUDE.md",
191
+ }
192
+ for candidate, destination in destinations.items():
193
+ if not candidate.is_file():
194
+ raise SystemExit(f"missing setup artifact: {candidate}")
195
+ if destination.name == "mcp.json":
196
+ destination.parent.mkdir(parents=True, exist_ok=True)
197
+ if destination.exists() and destination.read_bytes() != candidate.read_bytes() and not args.force:
198
+ raise SystemExit(f"refusing to overwrite {destination}; use --force after review")
199
+ if not destination.exists() or destination.read_bytes() != candidate.read_bytes():
200
+ shutil.copyfile(candidate, destination)
201
+ else:
202
+ existing = destination.read_text(encoding="utf-8") if destination.exists() else ""
203
+ addition = candidate.read_text(encoding="utf-8")
204
+ if addition.strip() not in existing:
205
+ destination.write_text(existing.rstrip() + "\n\n" + addition, encoding="utf-8")
206
+ print(f"applied setup artifacts from: {source}")
207
+ return 0
208
+
209
+
210
+ def main(argv: list[str] | None = None) -> int:
211
+ parser = argparse.ArgumentParser(prog="xrefkit mcp")
212
+ sub = parser.add_subparsers(dest="command", required=True)
213
+ setup_parser = sub.add_parser("setup", help="import Skills and generate reviewable MCP setup artifacts")
214
+ setup_parser.add_argument("--repo", required=True)
215
+ setup_parser.add_argument("--import", dest="import_source", default=None, help="Skill directory or batch root")
216
+ setup_parser.add_argument("--batch", action="store_true")
217
+ setup_parser.add_argument("--skill-id-prefix", default="imported")
218
+ setup_parser.add_argument("--skill-id", default=None)
219
+ setup_parser.add_argument("--output", default=None, help="Setup workspace; defaults to a temporary folder")
220
+ setup_parser.add_argument("--json", action="store_true")
221
+ setup_parser.set_defaults(handler=setup)
222
+
223
+ apply_parser = sub.add_parser("setup-apply", help="apply reviewed setup artifacts")
224
+ apply_parser.add_argument("--source", required=True)
225
+ apply_parser.add_argument("--repo", required=True)
226
+ apply_parser.add_argument("--force", action="store_true")
227
+ apply_parser.set_defaults(handler=apply_setup)
228
+
229
+ args = parser.parse_args(argv)
230
+ return args.handler(args)
231
+
232
+
233
+ if __name__ == "__main__":
234
+ raise SystemExit(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xrefkit
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: Portable XID, Skill, Knowledge, workflow, and MCP runtime for XRefKit
5
5
  Author: synthaicode
6
6
  License: MIT License
@@ -126,7 +126,36 @@ reason instead of inventing a criterion.
126
126
 
127
127
  ## Quick Start
128
128
 
129
- Install the package and initialize an instance:
129
+ ### Install from PyPI
130
+
131
+ XRefKit requires Python 3.11 or later. For a normal installation, create a
132
+ virtual environment and install the published package from PyPI:
133
+
134
+ ```powershell
135
+ python -m venv .venv
136
+ .\.venv\Scripts\Activate.ps1
137
+ python -m pip install --upgrade pip
138
+ python -m pip install xrefkit
139
+ xrefkit init
140
+ xrefkit --help
141
+ ```
142
+
143
+ If the `xrefkit` command is not available on `PATH`, use the module form:
144
+
145
+ ```powershell
146
+ python -m xrefkit --help
147
+ ```
148
+
149
+ To use the integrated MCP server, install the optional MCP dependencies:
150
+
151
+ ```powershell
152
+ python -m pip install "xrefkit[mcp]"
153
+ xrefkit mcp serve --repo . --transport stdio
154
+ ```
155
+
156
+ ### Install from a checkout
157
+
158
+ For XRefKit development, install the local checkout in editable mode instead:
130
159
 
131
160
  ```powershell
132
161
  python -m pip install -e .
@@ -140,6 +169,25 @@ Start the integrated MCP server over stdio:
140
169
  xrefkit mcp serve --repo . --transport stdio
141
170
  ```
142
171
 
172
+ To import existing Skills and prepare a reviewable VS Code MCP setup, run:
173
+
174
+ ```powershell
175
+ python -m pip install "xrefkit[mcp]"
176
+ xrefkit mcp setup `
177
+ --repo C:\dev\itsm\XRefKit `
178
+ --import C:\work\existing-skills
179
+ ```
180
+
181
+ The command writes `SETUP.md`, `import-report.json`, a VS Code
182
+ `.vscode/mcp.json` example, and reviewed append text for `AGENTS.md` and
183
+ `CLAUDE.md` into a temporary setup folder. Apply those artifacts after review:
184
+
185
+ ```powershell
186
+ xrefkit mcp setup apply `
187
+ --source C:\Users\<user>\AppData\Local\Temp\xrefkit-setup-<id> `
188
+ --repo C:\dev\itsm\XRefKit
189
+ ```
190
+
143
191
  The server writes structured correlation events to
144
192
  `work/mcp/xid_audit.jsonl` by default. After `xrefkit skill run` returns a
145
193
  `run_id`, the client calls MCP `bind_skill_run` and executes the returned
@@ -21,6 +21,7 @@ tests/test_gate.py
21
21
  tests/test_goal_desired_state.py
22
22
  tests/test_instruction_workflow.py
23
23
  tests/test_knowledge_relations_validator.py
24
+ tests/test_mcp_setup.py
24
25
  tests/test_ownership.py
25
26
  tests/test_packmeta.py
26
27
  tests/test_project_quality_baseline.py
@@ -84,6 +85,7 @@ xrefkit/mcp/ownership.py
84
85
  xrefkit/mcp/repository.py
85
86
  xrefkit/mcp/schemas.py
86
87
  xrefkit/mcp/server.py
88
+ xrefkit/mcp/setup.py
87
89
  xrefkit/mcp/startup_contract_pack.py
88
90
  xrefkit/models/__init__.py
89
91
  xrefkit/models/common.py
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