xrefkit 0.4.2__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.
- {xrefkit-0.4.2 → xrefkit-0.4.4}/PKG-INFO +50 -2
- {xrefkit-0.4.2 → xrefkit-0.4.4}/README.md +49 -1
- {xrefkit-0.4.2 → xrefkit-0.4.4}/pyproject.toml +1 -1
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_instruction_workflow.py +20 -0
- xrefkit-0.4.4/tests/test_mcp_setup.py +72 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/__init__.py +1 -1
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/__init__.py +10 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/bootstrap.py +13 -2
- xrefkit-0.4.4/xrefkit/mcp/setup.py +234 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/operations_cli.py +1 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/skillrun.py +45 -5
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit.egg-info/PKG-INFO +50 -2
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit.egg-info/SOURCES.txt +2 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/LICENSE +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/setup.cfg +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_base_sync_ownership.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_boundary_analysis.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_calibration_lint.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_check_skill_knowledge_xids.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_cli.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_collect_analyzer_sarif.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_convert_to_xrefkit_skill.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_cs_scope_probe.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_csharp_commonality.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_csharp_naming_profile.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_ctx.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_cutover_readiness.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_dashboard.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_error_policy_audit.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_error_policy_locator.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_fm_multiroot.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_gate.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_goal_desired_state.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_knowledge_relations_validator.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_ownership.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_packmeta.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_project_quality_baseline.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_resource_provider.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_runtime_contracts.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_sarif_to_locator.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_skill_runtime_audit.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_skillmeta.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_structure_catalog.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_xref.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_xrefkit_instance.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_xrefkit_tools.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_xrefkit_v2_discovery.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_xrefkit_v2_models.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/tests/test_xrefkit_v2_pipeline.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/__main__.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/boundary_analysis.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/catalog_cli.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/cli.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/contracts.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/ctx.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/dashboard.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/discovery.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/gate.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/goalstate.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/hashing.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/import_skill.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/instance.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/loaders.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/audit.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/catalog.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/cli.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/client_cache.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/context_registry.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/contracts.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/dist.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/ownership.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/repository.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/schemas.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/server.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp/startup_contract_pack.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/mcp_tools.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/models/__init__.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/models/common.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/models/effective_bundle.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/models/local_manifest.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/models/package_manifest.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/models/run_log.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/models/server_config.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/models/skill_definition.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/ownership.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/packmeta.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/registry.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resolver.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resource_provider.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resources/base/contracts.json +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resources/base/current.json +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resources/base/model_body.md +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/runlog.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/skillmeta.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/structure_catalog.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/tools/__init__.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/tools/__main__.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/v2_cli.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/workspace.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/xref.py +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit.egg-info/dependency_links.txt +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit.egg-info/entry_points.txt +0 -0
- {xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit.egg-info/requires.txt +0 -0
- {xrefkit-0.4.2 → 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
|
+
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
|
|
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
|
|
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
|
|
@@ -150,3 +150,23 @@ def test_workitem_requires_criterion_or_explicit_unknown_reason(tmp_path: Path)
|
|
|
150
150
|
assert "criterion=`` reason=`The business owner has not defined the acceptance outcome`" in text
|
|
151
151
|
assert _run(tmp_path, "skill", "phase", "--log", str(out), "--phase", "execution", "--status", "done", "--role", "instruction:executor") == 0
|
|
152
152
|
assert _run(tmp_path, "skill", "verify", "--log", str(out)) == 1
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_workitem_criterion_is_immutable_and_changes_use_supersedes(tmp_path: Path) -> None:
|
|
156
|
+
out = tmp_path / "work" / "sessions" / "run.md"
|
|
157
|
+
assert _run(tmp_path, "workflow", "run", "--task", "Do work", "--out", str(out), "--use-default-completion-conditions") == 0
|
|
158
|
+
base = [
|
|
159
|
+
"skill", "workitem", "--log", str(out), "--item", "WI-001",
|
|
160
|
+
"--text", "Implement original outcome", "--completion-criterion", "original outcome is verified",
|
|
161
|
+
"--status", "pending", "--role", "instruction:executor",
|
|
162
|
+
]
|
|
163
|
+
assert _run(tmp_path, *base) == 0
|
|
164
|
+
assert _run(tmp_path, *base[:-6], "--completion-criterion", "different outcome is verified", "--status", "pending", "--role", "instruction:executor") == 1
|
|
165
|
+
assert _run(
|
|
166
|
+
tmp_path,
|
|
167
|
+
"skill", "workitem", "--log", str(out), "--item", "WI-002", "--supersedes", "WI-001",
|
|
168
|
+
"--text", "Implement revised outcome", "--completion-criterion", "revised outcome is verified",
|
|
169
|
+
"--status", "pending", "--role", "instruction:executor",
|
|
170
|
+
) == 0
|
|
171
|
+
text = out.read_text(encoding="utf-8")
|
|
172
|
+
assert "WI-002" in text and "supersedes=`WI-001`" in text
|
|
@@ -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
|
|
@@ -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,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())
|
|
@@ -499,6 +499,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
499
499
|
p_skill_workitem.add_argument("--text", default=None, help="Work item text; required when adding a new item")
|
|
500
500
|
p_skill_workitem.add_argument("--completion-criterion", default=None, help="Observable procedural condition for this work item")
|
|
501
501
|
p_skill_workitem.add_argument("--criterion-unknown-reason", default=None, help="Why the completion criterion cannot yet be defined for unknown/blocked/escalated work")
|
|
502
|
+
p_skill_workitem.add_argument("--supersedes", default=None, help="Existing work item whose criterion is being replaced by this new item")
|
|
502
503
|
p_skill_workitem.add_argument(
|
|
503
504
|
"--status",
|
|
504
505
|
required=True,
|
|
@@ -179,6 +179,12 @@ WORKLIST_ROWS = [
|
|
|
179
179
|
("Handoff", "Record outputs, unresolved items, next owner, and human decision points."),
|
|
180
180
|
]
|
|
181
181
|
WORKITEM_RE = re.compile(
|
|
182
|
+
r"^- \[(?P<checkbox>[ x!])\] (?P<item_id>[A-Za-z0-9_.-]+) "
|
|
183
|
+
r"status=`(?P<status>[^`]+)` role=`(?P<role>[^`]+)` "
|
|
184
|
+
r"criterion=`(?P<criterion>[^`]*)` reason=`(?P<reason>[^`]*)` "
|
|
185
|
+
r"supersedes=`(?P<supersedes>[^`]*)`: (?P<text>.*)$"
|
|
186
|
+
)
|
|
187
|
+
WORKITEM_V2_RE = re.compile(
|
|
182
188
|
r"^- \[(?P<checkbox>[ x!])\] (?P<item_id>[A-Za-z0-9_.-]+) "
|
|
183
189
|
r"status=`(?P<status>[^`]+)` role=`(?P<role>[^`]+)` "
|
|
184
190
|
r"criterion=`(?P<criterion>[^`]*)` reason=`(?P<reason>[^`]*)`: (?P<text>.*)$"
|
|
@@ -838,6 +844,21 @@ def _parse_work_items(text: str) -> list[dict[str, str]]:
|
|
|
838
844
|
"role": match.group("role"),
|
|
839
845
|
"criterion": match.group("criterion"),
|
|
840
846
|
"reason": match.group("reason"),
|
|
847
|
+
"supersedes": match.group("supersedes"),
|
|
848
|
+
"text": match.group("text"),
|
|
849
|
+
}
|
|
850
|
+
)
|
|
851
|
+
continue
|
|
852
|
+
match = WORKITEM_V2_RE.match(line)
|
|
853
|
+
if match:
|
|
854
|
+
items.append(
|
|
855
|
+
{
|
|
856
|
+
"item_id": match.group("item_id"),
|
|
857
|
+
"status": match.group("status"),
|
|
858
|
+
"role": match.group("role"),
|
|
859
|
+
"criterion": match.group("criterion"),
|
|
860
|
+
"reason": match.group("reason"),
|
|
861
|
+
"supersedes": "",
|
|
841
862
|
"text": match.group("text"),
|
|
842
863
|
}
|
|
843
864
|
)
|
|
@@ -852,16 +873,17 @@ def _parse_work_items(text: str) -> list[dict[str, str]]:
|
|
|
852
873
|
"role": match.group("role"),
|
|
853
874
|
"criterion": "",
|
|
854
875
|
"reason": "legacy work item has no recorded completion criterion",
|
|
876
|
+
"supersedes": "",
|
|
855
877
|
"text": match.group("text"),
|
|
856
878
|
}
|
|
857
879
|
)
|
|
858
880
|
return items
|
|
859
881
|
|
|
860
882
|
|
|
861
|
-
def _render_workitem_line(*, item_id: str, status: str, role: str, criterion: str, reason: str, text: str) -> str:
|
|
883
|
+
def _render_workitem_line(*, item_id: str, status: str, role: str, criterion: str, reason: str, supersedes: str, text: str) -> str:
|
|
862
884
|
return (
|
|
863
885
|
f"- [{_workitem_checkbox(status)}] {item_id} status=`{status}` role=`{role}` "
|
|
864
|
-
f"criterion=`{criterion}` reason=`{reason}`: {text}"
|
|
886
|
+
f"criterion=`{criterion}` reason=`{reason}` supersedes=`{supersedes}`: {text}"
|
|
865
887
|
)
|
|
866
888
|
|
|
867
889
|
|
|
@@ -913,6 +935,7 @@ def update_work_item(args) -> SkillRunResult:
|
|
|
913
935
|
item_text = str(args.text or "").strip()
|
|
914
936
|
criterion = str(getattr(args, "completion_criterion", None) or "").strip().replace("`", "'").replace("\n", " ")
|
|
915
937
|
reason = str(getattr(args, "criterion_unknown_reason", None) or "").strip().replace("`", "'").replace("\n", " ")
|
|
938
|
+
supersedes = str(getattr(args, "supersedes", None) or "").strip().replace("`", "'").replace("\n", " ")
|
|
916
939
|
if not item_id:
|
|
917
940
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["missing --item"])
|
|
918
941
|
if status not in VALID_WORKITEM_STATUSES:
|
|
@@ -931,10 +954,21 @@ def update_work_item(args) -> SkillRunResult:
|
|
|
931
954
|
items = _parse_work_items(text)
|
|
932
955
|
existing = next((item for item in items if item["item_id"] == item_id), None)
|
|
933
956
|
if existing:
|
|
957
|
+
existing_criterion = existing.get("criterion", "")
|
|
958
|
+
if criterion and criterion != existing_criterion:
|
|
959
|
+
return SkillRunResult(
|
|
960
|
+
ok=False,
|
|
961
|
+
skill_id=None,
|
|
962
|
+
skill_doc=None,
|
|
963
|
+
run_log=str(log_path),
|
|
964
|
+
errors=[
|
|
965
|
+
f"completion criterion for {item_id} is immutable; create a new work item with --supersedes {item_id}"
|
|
966
|
+
],
|
|
967
|
+
)
|
|
968
|
+
if supersedes:
|
|
969
|
+
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=[f"--supersedes is only valid when creating a new work item, not updating {item_id}"])
|
|
934
970
|
existing["status"] = status
|
|
935
971
|
existing["role"] = role
|
|
936
|
-
if criterion:
|
|
937
|
-
existing["criterion"] = criterion
|
|
938
972
|
if reason:
|
|
939
973
|
existing["reason"] = reason
|
|
940
974
|
if item_text:
|
|
@@ -942,7 +976,9 @@ def update_work_item(args) -> SkillRunResult:
|
|
|
942
976
|
else:
|
|
943
977
|
if not item_text:
|
|
944
978
|
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=["new work item requires --text"])
|
|
945
|
-
|
|
979
|
+
if supersedes and not any(item["item_id"] == supersedes for item in items):
|
|
980
|
+
return SkillRunResult(ok=False, skill_id=None, skill_doc=None, run_log=str(log_path), errors=[f"superseded work item not found: {supersedes}"])
|
|
981
|
+
items.append({"item_id": item_id, "status": status, "role": role, "criterion": criterion, "reason": reason, "supersedes": supersedes, "text": item_text})
|
|
946
982
|
|
|
947
983
|
text = _replace_concrete_work_items_section(text, items)
|
|
948
984
|
text = _append_phase_event(text, phase=f"workitem:{item_id}", status=status, role=role, note=item_text or None)
|
|
@@ -1469,6 +1505,10 @@ def _progression_record_errors(
|
|
|
1469
1505
|
if not work_items:
|
|
1470
1506
|
errors.append("at least one concrete work item is required before closure")
|
|
1471
1507
|
for item in work_items:
|
|
1508
|
+
if item.get("supersedes") and not any(previous["item_id"] == item["supersedes"] for previous in work_items):
|
|
1509
|
+
errors.append(f"work item {item['item_id']} supersedes missing work item {item['supersedes']}")
|
|
1510
|
+
if item.get("supersedes") == item["item_id"]:
|
|
1511
|
+
errors.append(f"work item {item['item_id']} cannot supersede itself")
|
|
1472
1512
|
if not item.get("criterion"):
|
|
1473
1513
|
if item["status"] in {"unknown", "blocked", "escalated"} and item.get("reason"):
|
|
1474
1514
|
pass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xrefkit
|
|
3
|
-
Version: 0.4.
|
|
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
|
|
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
|
|
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.2 → xrefkit-0.4.4}/xrefkit/resources/base/generations/7a682a5272907354/contracts.json
RENAMED
|
File without changes
|
{xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resources/base/generations/7a682a5272907354/model_body.md
RENAMED
|
File without changes
|
{xrefkit-0.4.2 → xrefkit-0.4.4}/xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json
RENAMED
|
File without changes
|
{xrefkit-0.4.2 → xrefkit-0.4.4}/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
|