xrefkit 0.3.0__py3-none-any.whl

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 (65) hide show
  1. xrefkit/__init__.py +5 -0
  2. xrefkit/__main__.py +5 -0
  3. xrefkit/catalog_cli.py +57 -0
  4. xrefkit/cli.py +71 -0
  5. xrefkit/contracts.py +297 -0
  6. xrefkit/ctx.py +160 -0
  7. xrefkit/dashboard.py +1220 -0
  8. xrefkit/discovery.py +85 -0
  9. xrefkit/gate.py +428 -0
  10. xrefkit/goalstate.py +555 -0
  11. xrefkit/hashing.py +18 -0
  12. xrefkit/import_skill.py +469 -0
  13. xrefkit/instance.py +133 -0
  14. xrefkit/loaders.py +77 -0
  15. xrefkit/mcp/__init__.py +26 -0
  16. xrefkit/mcp/audit.py +168 -0
  17. xrefkit/mcp/bootstrap.py +337 -0
  18. xrefkit/mcp/catalog.py +2638 -0
  19. xrefkit/mcp/cli.py +173 -0
  20. xrefkit/mcp/client_cache.py +356 -0
  21. xrefkit/mcp/context_registry.py +277 -0
  22. xrefkit/mcp/contracts.py +243 -0
  23. xrefkit/mcp/dist.py +234 -0
  24. xrefkit/mcp/ownership.py +246 -0
  25. xrefkit/mcp/repository.py +217 -0
  26. xrefkit/mcp/schemas.py +349 -0
  27. xrefkit/mcp/server.py +773 -0
  28. xrefkit/mcp/startup_contract_pack.py +154 -0
  29. xrefkit/mcp_tools.py +47 -0
  30. xrefkit/models/__init__.py +41 -0
  31. xrefkit/models/common.py +185 -0
  32. xrefkit/models/effective_bundle.py +131 -0
  33. xrefkit/models/local_manifest.py +217 -0
  34. xrefkit/models/package_manifest.py +126 -0
  35. xrefkit/models/run_log.py +276 -0
  36. xrefkit/models/server_config.py +160 -0
  37. xrefkit/models/skill_definition.py +131 -0
  38. xrefkit/operations_cli.py +670 -0
  39. xrefkit/ownership.py +276 -0
  40. xrefkit/packmeta.py +289 -0
  41. xrefkit/registry.py +334 -0
  42. xrefkit/resolver.py +252 -0
  43. xrefkit/resource_provider.py +187 -0
  44. xrefkit/resources/base/contracts.json +178 -0
  45. xrefkit/resources/base/current.json +6 -0
  46. xrefkit/resources/base/generations/7a682a5272907354/contracts.json +178 -0
  47. xrefkit/resources/base/generations/7a682a5272907354/model_body.md +22 -0
  48. xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +178 -0
  49. xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +22 -0
  50. xrefkit/resources/base/model_body.md +22 -0
  51. xrefkit/runlog.py +45 -0
  52. xrefkit/skillmeta.py +1034 -0
  53. xrefkit/skillrun.py +2381 -0
  54. xrefkit/structure_catalog.py +199 -0
  55. xrefkit/tools/__init__.py +119 -0
  56. xrefkit/tools/__main__.py +4 -0
  57. xrefkit/v2_cli.py +130 -0
  58. xrefkit/workspace.py +117 -0
  59. xrefkit/xref.py +1048 -0
  60. xrefkit-0.3.0.dist-info/METADATA +203 -0
  61. xrefkit-0.3.0.dist-info/RECORD +65 -0
  62. xrefkit-0.3.0.dist-info/WHEEL +5 -0
  63. xrefkit-0.3.0.dist-info/entry_points.txt +2 -0
  64. xrefkit-0.3.0.dist-info/licenses/LICENSE +21 -0
  65. xrefkit-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,277 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+
7
+ REUSE_SAME_VERSION_REASON = "same_xid_version_already_visible_in_active_context"
8
+ VERSION_CHANGE_REASON = "same_xid_different_content_hash_visible_in_active_context"
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class XidContextKey:
13
+ repository_fingerprint: str
14
+ xid: str
15
+ content_hash: str
16
+
17
+ @classmethod
18
+ def from_document(cls, document: dict[str, Any]) -> "XidContextKey":
19
+ try:
20
+ return cls(
21
+ repository_fingerprint=str(document["repository_fingerprint"]),
22
+ xid=str(document["xid"]),
23
+ content_hash=str(document["content_hash"]),
24
+ )
25
+ except KeyError as exc:
26
+ raise ValueError(
27
+ "XID context documents must include repository_fingerprint, xid, and content_hash"
28
+ ) from exc
29
+
30
+
31
+ @dataclass
32
+ class XidContextEntry:
33
+ key: XidContextKey
34
+ visible_in_active_context: bool = False
35
+ injected_turns: list[str] = field(default_factory=list)
36
+ last_injected_turn: str | None = None
37
+ reuse_reasons: list[str] = field(default_factory=list)
38
+ context_entry_id: str | None = None
39
+ materialized_count: int = 0
40
+
41
+
42
+ class SessionXidContextRegistry:
43
+ """Tracks which XID document bodies are visible in one client session."""
44
+
45
+ def __init__(self) -> None:
46
+ self._entries: dict[XidContextKey, XidContextEntry] = {}
47
+ self._entry_sequence = 0
48
+ self.version_changes: list[dict[str, str]] = []
49
+
50
+ def register_materialized(self, document: dict[str, Any]) -> XidContextEntry:
51
+ key = XidContextKey.from_document(document)
52
+ entry = self._entries.get(key)
53
+ if entry is None:
54
+ entry = XidContextEntry(key=key)
55
+ self._entries[key] = entry
56
+ entry.materialized_count += 1
57
+ return entry
58
+
59
+ def should_inject_body(
60
+ self,
61
+ document: dict[str, Any],
62
+ active_task: str,
63
+ active_workflow: dict[str, Any] | str | None = None,
64
+ active_skill: dict[str, Any] | str | None = None,
65
+ ) -> bool:
66
+ key = XidContextKey.from_document(document)
67
+ entry = self._entries.get(key)
68
+ if entry and entry.visible_in_active_context:
69
+ return False
70
+ if not document.get("content"):
71
+ return False
72
+ return _context_requires_document(
73
+ key.xid,
74
+ active_task,
75
+ active_workflow,
76
+ active_skill,
77
+ )
78
+
79
+ def mark_injected(
80
+ self,
81
+ document: dict[str, Any],
82
+ turn_id: str,
83
+ context_entry_id: str | None = None,
84
+ ) -> XidContextEntry:
85
+ entry = self.register_materialized(document)
86
+ entry.visible_in_active_context = True
87
+ entry.injected_turns.append(str(turn_id))
88
+ entry.last_injected_turn = str(turn_id)
89
+ if context_entry_id is not None:
90
+ entry.context_entry_id = context_entry_id
91
+ elif entry.context_entry_id is None:
92
+ self._entry_sequence += 1
93
+ entry.context_entry_id = f"xid-context-{self._entry_sequence}"
94
+ return entry
95
+
96
+ def reference_existing(self, document: dict[str, Any], reason: str) -> dict[str, str]:
97
+ entry = self.register_materialized(document)
98
+ if reason not in entry.reuse_reasons:
99
+ entry.reuse_reasons.append(reason)
100
+ key = entry.key
101
+ result = {
102
+ "repository_fingerprint": key.repository_fingerprint,
103
+ "xid": key.xid,
104
+ "content_hash": key.content_hash,
105
+ "mode": "reuse_existing_session_context",
106
+ "reason": reason,
107
+ }
108
+ if entry.context_entry_id:
109
+ result["context_entry_id"] = entry.context_entry_id
110
+ return result
111
+
112
+ def mark_context_compacted(
113
+ self,
114
+ documents: list[dict[str, Any]] | None = None,
115
+ ) -> None:
116
+ if documents is None:
117
+ for entry in self._entries.values():
118
+ entry.visible_in_active_context = False
119
+ return
120
+ for document in documents:
121
+ entry = self._entries.get(XidContextKey.from_document(document))
122
+ if entry is not None:
123
+ entry.visible_in_active_context = False
124
+
125
+ def handle_version_change(
126
+ self,
127
+ document: dict[str, Any],
128
+ action: str = "replaced",
129
+ ) -> list[dict[str, str]]:
130
+ key = XidContextKey.from_document(document)
131
+ changes: list[dict[str, str]] = []
132
+ for entry_key, entry in list(self._entries.items()):
133
+ if (
134
+ entry_key.repository_fingerprint == key.repository_fingerprint
135
+ and entry_key.xid == key.xid
136
+ and entry_key.content_hash != key.content_hash
137
+ and entry.visible_in_active_context
138
+ ):
139
+ if action == "replaced":
140
+ entry.visible_in_active_context = False
141
+ change = {
142
+ "repository_fingerprint": key.repository_fingerprint,
143
+ "xid": key.xid,
144
+ "old_content_hash": entry_key.content_hash,
145
+ "new_content_hash": key.content_hash,
146
+ "action": action,
147
+ "reason": VERSION_CHANGE_REASON,
148
+ }
149
+ self.version_changes.append(change)
150
+ changes.append(change)
151
+ return changes
152
+
153
+ def entry_for(self, document: dict[str, Any]) -> XidContextEntry | None:
154
+ return self._entries.get(XidContextKey.from_document(document))
155
+
156
+
157
+ class PromptContextAssembler:
158
+ def __init__(self, registry: SessionXidContextRegistry | None = None) -> None:
159
+ self.registry = registry or SessionXidContextRegistry()
160
+
161
+ def assemble(
162
+ self,
163
+ documents: list[dict[str, Any]],
164
+ *,
165
+ turn_id: str,
166
+ active_task: str,
167
+ active_workflow: dict[str, Any] | str | None = None,
168
+ active_skill: dict[str, Any] | str | None = None,
169
+ ) -> dict[str, Any]:
170
+ prompt_items: list[dict[str, Any]] = []
171
+ trace = {
172
+ "turn_id": str(turn_id),
173
+ "active_task": active_task,
174
+ "injected_xids": [],
175
+ "reused_xids": [],
176
+ "version_changes": [],
177
+ }
178
+
179
+ for document in documents:
180
+ self.registry.register_materialized(document)
181
+ visible_entry = self.registry.entry_for(document)
182
+ if visible_entry and visible_entry.visible_in_active_context:
183
+ reuse = self.registry.reference_existing(
184
+ document,
185
+ REUSE_SAME_VERSION_REASON,
186
+ )
187
+ prompt_items.append(reuse)
188
+ trace["reused_xids"].append(reuse)
189
+ continue
190
+
191
+ version_changes = self.registry.handle_version_change(document)
192
+ trace["version_changes"].extend(version_changes)
193
+
194
+ if self.registry.should_inject_body(
195
+ document,
196
+ active_task,
197
+ active_workflow,
198
+ active_skill,
199
+ ):
200
+ entry = self.registry.mark_injected(document, str(turn_id))
201
+ item = _body_prompt_item(document, entry.context_entry_id)
202
+ prompt_items.append(item)
203
+ trace["injected_xids"].append(
204
+ {
205
+ "repository_fingerprint": document["repository_fingerprint"],
206
+ "xid": document["xid"],
207
+ "content_hash": document["content_hash"],
208
+ "reason": "body_required_by_active_context",
209
+ }
210
+ )
211
+ continue
212
+
213
+ prompt_items.append(_metadata_prompt_item(document))
214
+
215
+ return {"prompt_items": prompt_items, "trace": trace}
216
+
217
+
218
+ def _metadata_prompt_item(document: dict[str, Any]) -> dict[str, Any]:
219
+ item = {
220
+ "mode": "metadata_only",
221
+ "xid": document.get("xid"),
222
+ "title": document.get("title"),
223
+ "summary": document.get("summary"),
224
+ "layer": document.get("layer"),
225
+ "required_at_init": document.get("required_at_init"),
226
+ "content_hash": document.get("content_hash"),
227
+ "links": document.get("links", []),
228
+ }
229
+ for field_name in ["cache_status", "client_cache_status"]:
230
+ if field_name in document:
231
+ item[field_name] = document[field_name]
232
+ return item
233
+
234
+
235
+ def _body_prompt_item(
236
+ document: dict[str, Any],
237
+ context_entry_id: str | None,
238
+ ) -> dict[str, Any]:
239
+ item = _metadata_prompt_item(document)
240
+ item["mode"] = "body"
241
+ item["content"] = document["content"]
242
+ if context_entry_id:
243
+ item["context_entry_id"] = context_entry_id
244
+ return item
245
+
246
+
247
+ def _context_requires_document(
248
+ xid: str,
249
+ active_task: str,
250
+ active_workflow: dict[str, Any] | str | None,
251
+ active_skill: dict[str, Any] | str | None,
252
+ ) -> bool:
253
+ task = active_task.lower()
254
+ if xid.lower() in task:
255
+ return True
256
+ return _context_declares_xid(active_workflow, xid) or _context_declares_xid(
257
+ active_skill,
258
+ xid,
259
+ )
260
+
261
+
262
+ def _context_declares_xid(context: dict[str, Any] | str | None, xid: str) -> bool:
263
+ if context is None:
264
+ return False
265
+ if isinstance(context, str):
266
+ return xid in context
267
+ return _contains_xid(context, xid)
268
+
269
+
270
+ def _contains_xid(value: Any, xid: str) -> bool:
271
+ if isinstance(value, str):
272
+ return value == xid or xid in value
273
+ if isinstance(value, dict):
274
+ return any(_contains_xid(item, xid) for item in value.values())
275
+ if isinstance(value, list | tuple | set):
276
+ return any(_contains_xid(item, xid) for item in value)
277
+ return False
@@ -0,0 +1,243 @@
1
+ from __future__ import annotations
2
+
3
+ from .schemas import ToolContract
4
+
5
+
6
+ def builtin_tool_contracts() -> list[ToolContract]:
7
+ contracts = [
8
+ ToolContract(
9
+ tool_id="xref.get_repository_identity",
10
+ provider="xrefkit-mcp",
11
+ version="1",
12
+ execution_location="server",
13
+ side_effects="none",
14
+ input_schema={},
15
+ output_schema={"identity": "repository_identity"},
16
+ requires_workspace=True,
17
+ required_when="A client must select an isolated cache namespace before loading cached XID documents.",
18
+ ),
19
+ ToolContract(
20
+ tool_id="xref.get_startup_context",
21
+ provider="xrefkit-mcp",
22
+ version="2",
23
+ execution_location="server",
24
+ side_effects="none",
25
+ input_schema={"known_document_versions": "object?"},
26
+ output_schema={"context": "startup_context"},
27
+ requires_workspace=True,
28
+ required_when="Client initialization must show base XRefKit control documents and link-resolution instructions before task routing.",
29
+ ),
30
+ ToolContract(
31
+ tool_id="xref.bind_skill_run",
32
+ provider="xrefkit-mcp",
33
+ version="1",
34
+ execution_location="server",
35
+ side_effects="audit_write",
36
+ input_schema={"run_id": "uuid", "skill_id": "string"},
37
+ output_schema={"binding": "skill_run_mcp_binding"},
38
+ requires_workspace=True,
39
+ required_when="A client created a Skill Run and must correlate subsequent MCP routing and XID access with that run.",
40
+ ),
41
+ ToolContract(
42
+ tool_id="xref.end_skill_run",
43
+ provider="xrefkit-mcp",
44
+ version="1",
45
+ execution_location="server",
46
+ side_effects="audit_write",
47
+ input_schema={"run_id": "uuid"},
48
+ output_schema={"binding": "ended_skill_run_mcp_binding"},
49
+ requires_workspace=True,
50
+ required_when="A client completed a Skill Run and must stop attributing later MCP queries to it.",
51
+ ),
52
+ ToolContract(
53
+ tool_id="xref.list_knowledge_catalog",
54
+ provider="xrefkit-mcp",
55
+ version="1",
56
+ execution_location="server",
57
+ side_effects="none",
58
+ input_schema={"limit": "integer?"},
59
+ output_schema={"entries": "knowledge_catalog_entries"},
60
+ requires_workspace=True,
61
+ required_when="Catalog browsing is needed without expanding knowledge body.",
62
+ response_envelope="mcp_result_array",
63
+ ),
64
+ ToolContract(
65
+ tool_id="xref.search_knowledge_catalog",
66
+ provider="xrefkit-mcp",
67
+ version="1",
68
+ execution_location="server",
69
+ side_effects="audit_write",
70
+ input_schema={"query": "string", "limit": "integer?"},
71
+ output_schema={"entries": "knowledge_catalog_entries"},
72
+ requires_workspace=True,
73
+ required_when="A purpose or question must be matched to XID-backed knowledge.",
74
+ response_envelope="mcp_result_array",
75
+ ),
76
+ ToolContract(
77
+ tool_id="xref.expand_knowledge",
78
+ provider="xrefkit-mcp",
79
+ version="1",
80
+ execution_location="server",
81
+ side_effects="audit_write",
82
+ input_schema={"xid": "string"},
83
+ output_schema={"entry": "knowledge_catalog_entry", "content": "markdown"},
84
+ requires_workspace=True,
85
+ required_when="The consumer explicitly needs the selected knowledge body.",
86
+ ),
87
+ ToolContract(
88
+ tool_id="xref.get_knowledge_summary",
89
+ provider="xrefkit-mcp",
90
+ version="1",
91
+ execution_location="server",
92
+ side_effects="audit_write",
93
+ input_schema={"xid": "string"},
94
+ output_schema={"entry": "knowledge_catalog_entry"},
95
+ requires_workspace=True,
96
+ required_when="The consumer needs metadata for one Knowledge entry without expanding the body.",
97
+ ),
98
+ ToolContract(
99
+ tool_id="xref.get_document_by_xid",
100
+ provider="xrefkit-mcp",
101
+ version="2",
102
+ execution_location="server",
103
+ side_effects="audit_write",
104
+ input_schema={"xid": "string", "known_version": "string?"},
105
+ output_schema={"document": "conditional_xref_document"},
106
+ requires_workspace=True,
107
+ required_when="Transferred content has a links[] entry whose xid must be resolved because the client does not have local Markdown files.",
108
+ ),
109
+ ToolContract(
110
+ tool_id="xref.build_knowledge_context",
111
+ provider="xrefkit-mcp",
112
+ version="1",
113
+ execution_location="server",
114
+ side_effects="audit_write",
115
+ input_schema={"query": "string", "limit": "integer?"},
116
+ output_schema={"entries": "expanded_knowledge", "missing": "array"},
117
+ requires_workspace=True,
118
+ required_when="A bounded context pack is needed for a task.",
119
+ ),
120
+ ToolContract(
121
+ tool_id="xref.list_skills",
122
+ provider="xrefkit-mcp",
123
+ version="3",
124
+ execution_location="server",
125
+ side_effects="none",
126
+ input_schema={"limit": "integer?", "include_content": "boolean?"},
127
+ output_schema={"entries": "skill_catalog_entries"},
128
+ requires_workspace=True,
129
+ required_when=(
130
+ "Available Skills must be listed without executing them. "
131
+ "Metadata-only by default; include_content=true returns "
132
+ "procedure bodies and requires get_startup_context first."
133
+ ),
134
+ response_envelope="mcp_result_array",
135
+ ),
136
+ ToolContract(
137
+ tool_id="xref.get_skill",
138
+ provider="xrefkit-mcp",
139
+ version="2",
140
+ execution_location="server",
141
+ side_effects="audit_write",
142
+ input_schema={
143
+ "skill_id": "string",
144
+ "known_document_versions": "object?",
145
+ },
146
+ output_schema={"entry": "skill_catalog_entry"},
147
+ requires_workspace=True,
148
+ required_when="The client needs a Skill's meta/procedure content and skill_links that are resolved through get_document_by_xid.",
149
+ ),
150
+ ToolContract(
151
+ tool_id="xref.get_skill_requirements",
152
+ provider="xrefkit-mcp",
153
+ version="1",
154
+ execution_location="server",
155
+ side_effects="none",
156
+ input_schema={"skill_id": "string"},
157
+ output_schema={"requirements": "skill_requirements"},
158
+ requires_workspace=True,
159
+ required_when="The client needs a Skill's required Knowledge, required tools, and closure contract without loading full Skill bodies.",
160
+ ),
161
+ ToolContract(
162
+ tool_id="xref.rank_skills_for_purpose",
163
+ provider="xrefkit-mcp",
164
+ version="1",
165
+ execution_location="server",
166
+ side_effects="audit_write",
167
+ input_schema={"purpose": "string", "limit": "integer?"},
168
+ output_schema={"results": "skill_rank_results"},
169
+ requires_workspace=True,
170
+ required_when="The consumer needs candidate Skills with evidence, not an automatic decision.",
171
+ response_envelope="mcp_result_array",
172
+ ),
173
+ ToolContract(
174
+ tool_id="xref.list_tool_contracts",
175
+ provider="xrefkit-mcp",
176
+ version="1",
177
+ execution_location="server",
178
+ side_effects="none",
179
+ input_schema={},
180
+ output_schema={"contracts": "tool_contracts"},
181
+ requires_workspace=False,
182
+ required_when="The client performs capability handshake or audit.",
183
+ response_envelope="mcp_result_array",
184
+ ),
185
+ ToolContract(
186
+ tool_id="xref.get_client_tool_manifest",
187
+ provider="xrefkit-mcp",
188
+ version="1",
189
+ execution_location="server",
190
+ side_effects="none",
191
+ input_schema={},
192
+ output_schema={"distribution": "client_tool_distribution"},
193
+ requires_workspace=True,
194
+ required_when="The client needs to know which Python tools can be installed for client-side execution.",
195
+ ),
196
+ ToolContract(
197
+ tool_id="xref.get_client_tool_file",
198
+ provider="xrefkit-mcp",
199
+ version="1",
200
+ execution_location="server",
201
+ side_effects="none",
202
+ input_schema={"path": "string"},
203
+ output_schema={"file": "client_tool_file"},
204
+ requires_workspace=True,
205
+ required_when="The client needs one distributed tool file content by manifest path.",
206
+ ),
207
+ ToolContract(
208
+ tool_id="xref.get_client_tool_bundle",
209
+ provider="xrefkit-mcp",
210
+ version="1",
211
+ execution_location="server",
212
+ side_effects="none",
213
+ input_schema={},
214
+ output_schema={"distribution": "client_tool_distribution", "files": "client_tool_files"},
215
+ requires_workspace=True,
216
+ required_when="The client needs to install all distributable Python tools for client-side execution.",
217
+ ),
218
+ ToolContract(
219
+ tool_id="xref.get_client_tool_pip_package",
220
+ provider="xrefkit-mcp",
221
+ version="1",
222
+ execution_location="server",
223
+ side_effects="none",
224
+ input_schema={},
225
+ output_schema={"package": "client_tool_pip_package"},
226
+ requires_workspace=True,
227
+ required_when="The client wants a pip-installable zip package for client-side XRefKit Python tools.",
228
+ ),
229
+ ToolContract(
230
+ tool_id="xref.check_client_tool_versions",
231
+ provider="xrefkit-mcp",
232
+ version="1",
233
+ execution_location="server",
234
+ side_effects="none",
235
+ input_schema={"installed": "object"},
236
+ output_schema={"result": "client_tool_version_check"},
237
+ requires_workspace=True,
238
+ required_when="Client initialization must compare installed client-side tool package versions with the server manifest.",
239
+ ),
240
+ ]
241
+ for contract in contracts:
242
+ contract.validate()
243
+ return contracts