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.
- xrefkit/__init__.py +5 -0
- xrefkit/__main__.py +5 -0
- xrefkit/catalog_cli.py +57 -0
- xrefkit/cli.py +71 -0
- xrefkit/contracts.py +297 -0
- xrefkit/ctx.py +160 -0
- xrefkit/dashboard.py +1220 -0
- xrefkit/discovery.py +85 -0
- xrefkit/gate.py +428 -0
- xrefkit/goalstate.py +555 -0
- xrefkit/hashing.py +18 -0
- xrefkit/import_skill.py +469 -0
- xrefkit/instance.py +133 -0
- xrefkit/loaders.py +77 -0
- xrefkit/mcp/__init__.py +26 -0
- xrefkit/mcp/audit.py +168 -0
- xrefkit/mcp/bootstrap.py +337 -0
- xrefkit/mcp/catalog.py +2638 -0
- xrefkit/mcp/cli.py +173 -0
- xrefkit/mcp/client_cache.py +356 -0
- xrefkit/mcp/context_registry.py +277 -0
- xrefkit/mcp/contracts.py +243 -0
- xrefkit/mcp/dist.py +234 -0
- xrefkit/mcp/ownership.py +246 -0
- xrefkit/mcp/repository.py +217 -0
- xrefkit/mcp/schemas.py +349 -0
- xrefkit/mcp/server.py +773 -0
- xrefkit/mcp/startup_contract_pack.py +154 -0
- xrefkit/mcp_tools.py +47 -0
- xrefkit/models/__init__.py +41 -0
- xrefkit/models/common.py +185 -0
- xrefkit/models/effective_bundle.py +131 -0
- xrefkit/models/local_manifest.py +217 -0
- xrefkit/models/package_manifest.py +126 -0
- xrefkit/models/run_log.py +276 -0
- xrefkit/models/server_config.py +160 -0
- xrefkit/models/skill_definition.py +131 -0
- xrefkit/operations_cli.py +670 -0
- xrefkit/ownership.py +276 -0
- xrefkit/packmeta.py +289 -0
- xrefkit/registry.py +334 -0
- xrefkit/resolver.py +252 -0
- xrefkit/resource_provider.py +187 -0
- xrefkit/resources/base/contracts.json +178 -0
- xrefkit/resources/base/current.json +6 -0
- xrefkit/resources/base/generations/7a682a5272907354/contracts.json +178 -0
- xrefkit/resources/base/generations/7a682a5272907354/model_body.md +22 -0
- xrefkit/resources/base/generations/9929294385ccb7b0/contracts.json +178 -0
- xrefkit/resources/base/generations/9929294385ccb7b0/model_body.md +22 -0
- xrefkit/resources/base/model_body.md +22 -0
- xrefkit/runlog.py +45 -0
- xrefkit/skillmeta.py +1034 -0
- xrefkit/skillrun.py +2381 -0
- xrefkit/structure_catalog.py +199 -0
- xrefkit/tools/__init__.py +119 -0
- xrefkit/tools/__main__.py +4 -0
- xrefkit/v2_cli.py +130 -0
- xrefkit/workspace.py +117 -0
- xrefkit/xref.py +1048 -0
- xrefkit-0.3.0.dist-info/METADATA +203 -0
- xrefkit-0.3.0.dist-info/RECORD +65 -0
- xrefkit-0.3.0.dist-info/WHEEL +5 -0
- xrefkit-0.3.0.dist-info/entry_points.txt +2 -0
- xrefkit-0.3.0.dist-info/licenses/LICENSE +21 -0
- xrefkit-0.3.0.dist-info/top_level.txt +1 -0
xrefkit/__init__.py
ADDED
xrefkit/__main__.py
ADDED
xrefkit/catalog_cli.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""CLI for list-first catalogs and candidate maintenance."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .structure_catalog import get_entry, list_findings, list_targets, load_catalog, maintain_catalog, reconcile_receipts
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
DEFAULT_CATALOG = "knowledge/source_analysis/source_structure_catalog.yaml"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main(argv: list[str] | None = None) -> int:
|
|
16
|
+
parser = argparse.ArgumentParser(prog="xrefkit catalog")
|
|
17
|
+
parser.add_argument("--root", default=".")
|
|
18
|
+
parser.add_argument("--catalog", default=DEFAULT_CATALOG)
|
|
19
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
20
|
+
listing = sub.add_parser("list")
|
|
21
|
+
listing.add_argument("kind", choices=["targets", "findings"])
|
|
22
|
+
listing.add_argument("--target")
|
|
23
|
+
listing.add_argument("--json", action="store_true")
|
|
24
|
+
get = sub.add_parser("get")
|
|
25
|
+
get.add_argument("xid")
|
|
26
|
+
get.add_argument("--json", action="store_true")
|
|
27
|
+
maintain = sub.add_parser("maintain")
|
|
28
|
+
maintain.add_argument("--inbox", default="work/inbox/source_structure_findings")
|
|
29
|
+
maintain.add_argument("--apply-safe", action="store_true")
|
|
30
|
+
maintain.add_argument("--json", action="store_true")
|
|
31
|
+
reconcile = sub.add_parser("reconcile")
|
|
32
|
+
reconcile.add_argument("--reports", default="work/reports")
|
|
33
|
+
reconcile.add_argument("--inbox", default="work/inbox/source_structure_findings")
|
|
34
|
+
reconcile.add_argument("--json", action="store_true")
|
|
35
|
+
args = parser.parse_args(argv)
|
|
36
|
+
root = Path(args.root).resolve()
|
|
37
|
+
try:
|
|
38
|
+
if args.command == "list":
|
|
39
|
+
catalog = load_catalog(root / args.catalog)
|
|
40
|
+
if args.kind == "targets":
|
|
41
|
+
result = list_targets(catalog)
|
|
42
|
+
else:
|
|
43
|
+
if not args.target:
|
|
44
|
+
raise ValueError("--target is required for finding list")
|
|
45
|
+
result = list_findings(catalog, args.target)
|
|
46
|
+
elif args.command == "get":
|
|
47
|
+
result = get_entry(load_catalog(root / args.catalog), args.xid)
|
|
48
|
+
elif args.command == "maintain":
|
|
49
|
+
result = maintain_catalog(root, args.catalog, args.inbox, apply_safe=args.apply_safe)
|
|
50
|
+
else:
|
|
51
|
+
result = reconcile_receipts(root, args.reports, args.inbox)
|
|
52
|
+
except (OSError, KeyError, ValueError) as exc:
|
|
53
|
+
result = {"ok": False, "error": str(exc)}
|
|
54
|
+
print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
|
|
55
|
+
return 1
|
|
56
|
+
print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
|
|
57
|
+
return 0 if not isinstance(result, dict) or result.get("ok", True) else 1
|
xrefkit/cli.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Unified command dispatcher for the XRefKit package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_OPERATIONS = {"xref", "ctx", "goal", "gate", "pack", "dashboard", "skill"}
|
|
10
|
+
_V2 = {"package", "show"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _print_help() -> None:
|
|
14
|
+
print(
|
|
15
|
+
"usage: xrefkit <command> ...\n\n"
|
|
16
|
+
"commands:\n"
|
|
17
|
+
" init initialize or validate an XRefKit instance\n"
|
|
18
|
+
" xref manage XIDs and references\n"
|
|
19
|
+
" ctx build compact context packs\n"
|
|
20
|
+
" skill discover, validate, run, verify, and close Skills\n"
|
|
21
|
+
" tools list and run XID-backed client tools\n"
|
|
22
|
+
" catalog list and maintain Knowledge and structure catalogs\n"
|
|
23
|
+
" pack validate and build runtime/content packs\n"
|
|
24
|
+
" gate evaluate deterministic gates\n"
|
|
25
|
+
" goal manage desired-state Goals and continuation state\n"
|
|
26
|
+
" dashboard inspect runtime state\n"
|
|
27
|
+
" mcp start the integrated MCP server\n"
|
|
28
|
+
" package inspect installed Skill packages\n"
|
|
29
|
+
" show show effective Skill bundles"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
34
|
+
args = list(argv if argv is not None else sys.argv[1:])
|
|
35
|
+
if not args or args[0] in {"-h", "--help"}:
|
|
36
|
+
_print_help()
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
command = args[0]
|
|
40
|
+
if command == "pack" and len(args) > 1 and args[1] in {"build-base", "verify-base"}:
|
|
41
|
+
from .contracts import main as contracts_main
|
|
42
|
+
|
|
43
|
+
return contracts_main(args[1:])
|
|
44
|
+
if command in _OPERATIONS:
|
|
45
|
+
from .operations_cli import main as operations_main
|
|
46
|
+
|
|
47
|
+
return operations_main(args)
|
|
48
|
+
if command in _V2:
|
|
49
|
+
from .v2_cli import main as v2_main
|
|
50
|
+
|
|
51
|
+
return v2_main(args)
|
|
52
|
+
if command == "init":
|
|
53
|
+
from .instance import main as instance_main
|
|
54
|
+
|
|
55
|
+
return instance_main(args[1:])
|
|
56
|
+
if command == "tools":
|
|
57
|
+
from .tools import main as tools_main
|
|
58
|
+
|
|
59
|
+
return tools_main(args[1:])
|
|
60
|
+
if command == "catalog":
|
|
61
|
+
from .catalog_cli import main as catalog_main
|
|
62
|
+
|
|
63
|
+
return catalog_main(args[1:])
|
|
64
|
+
if command == "mcp":
|
|
65
|
+
from .mcp import main as mcp_main
|
|
66
|
+
|
|
67
|
+
return mcp_main(args[1:])
|
|
68
|
+
|
|
69
|
+
print(f"xrefkit: unknown command: {command}", file=sys.stderr)
|
|
70
|
+
_print_help()
|
|
71
|
+
return 2
|
xrefkit/contracts.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Compile and verify model-facing base runtime contract resources."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import shutil
|
|
11
|
+
import tempfile
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
SCHEMA = "xrefkit.base_runtime/v1"
|
|
19
|
+
GENERATED_SCHEMA = "xrefkit.compiled_runtime/v1"
|
|
20
|
+
NORMATIVE_RE = re.compile(r"\bMUST(?:\s+NOT)?\b")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _sha256(data: bytes) -> str:
|
|
24
|
+
return hashlib.sha256(data).hexdigest()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _load_manifest(path: Path) -> dict[str, Any]:
|
|
28
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
29
|
+
if not isinstance(data, dict) or data.get("schema") != SCHEMA:
|
|
30
|
+
raise ValueError(f"unsupported base runtime manifest: {path}")
|
|
31
|
+
return data
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _estimate_tokens(text: str) -> int:
|
|
35
|
+
return (len(text) + 3) // 4
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _atomic_write_bytes(path: Path, data: bytes) -> None:
|
|
39
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
|
|
41
|
+
try:
|
|
42
|
+
with os.fdopen(fd, "wb") as handle:
|
|
43
|
+
handle.write(data)
|
|
44
|
+
handle.flush()
|
|
45
|
+
os.fsync(handle.fileno())
|
|
46
|
+
os.replace(temporary, path)
|
|
47
|
+
except BaseException:
|
|
48
|
+
try:
|
|
49
|
+
os.unlink(temporary)
|
|
50
|
+
except FileNotFoundError:
|
|
51
|
+
pass
|
|
52
|
+
raise
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _publish_generation(output: Path, generated: dict[str, Any], model_body: str) -> None:
|
|
56
|
+
contracts_bytes = json.dumps(generated, ensure_ascii=False, indent=2, default=str).encode("utf-8") + b"\n"
|
|
57
|
+
model_bytes = model_body.encode("utf-8")
|
|
58
|
+
generation_id = hashlib.sha256(contracts_bytes + model_bytes).hexdigest()[:16]
|
|
59
|
+
generations = output / "generations"
|
|
60
|
+
generations.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
generation_path = generations / generation_id
|
|
62
|
+
if not generation_path.exists():
|
|
63
|
+
temporary = Path(tempfile.mkdtemp(prefix=".generation-", dir=generations))
|
|
64
|
+
try:
|
|
65
|
+
(temporary / "contracts.json").write_bytes(contracts_bytes)
|
|
66
|
+
(temporary / "model_body.md").write_bytes(model_bytes)
|
|
67
|
+
os.replace(temporary, generation_path)
|
|
68
|
+
except FileExistsError:
|
|
69
|
+
shutil.rmtree(temporary, ignore_errors=True)
|
|
70
|
+
except BaseException:
|
|
71
|
+
shutil.rmtree(temporary, ignore_errors=True)
|
|
72
|
+
raise
|
|
73
|
+
pointer = {
|
|
74
|
+
"schema": "xrefkit.base_generation/v1",
|
|
75
|
+
"generation": generation_id,
|
|
76
|
+
"contracts": f"generations/{generation_id}/contracts.json",
|
|
77
|
+
"model_body": f"generations/{generation_id}/model_body.md",
|
|
78
|
+
}
|
|
79
|
+
# The pointer is the publication boundary. Fixed files remain compatibility snapshots.
|
|
80
|
+
_atomic_write_bytes(output / "contracts.json", contracts_bytes)
|
|
81
|
+
_atomic_write_bytes(output / "model_body.md", model_bytes)
|
|
82
|
+
_atomic_write_bytes(
|
|
83
|
+
output / "current.json",
|
|
84
|
+
json.dumps(pointer, ensure_ascii=False, indent=2).encode("utf-8") + b"\n",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _published_paths(output: Path) -> tuple[Path, Path]:
|
|
89
|
+
pointer_path = output / "current.json"
|
|
90
|
+
if not pointer_path.is_file():
|
|
91
|
+
raise ValueError("base runtime generation pointer current.json is required")
|
|
92
|
+
pointer = json.loads(pointer_path.read_text(encoding="utf-8"))
|
|
93
|
+
generation = str(pointer["generation"])
|
|
94
|
+
if not re.fullmatch(r"[0-9a-f]{16}", generation):
|
|
95
|
+
raise ValueError("invalid base runtime generation")
|
|
96
|
+
generation_path = output / "generations" / generation
|
|
97
|
+
return generation_path / "contracts.json", generation_path / "model_body.md"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def compile_base_runtime(
|
|
101
|
+
repo_root: str | Path,
|
|
102
|
+
manifest_path: str | Path,
|
|
103
|
+
output_dir: str | Path,
|
|
104
|
+
) -> dict[str, Any]:
|
|
105
|
+
result = _compile_base_runtime(repo_root, manifest_path, output_dir)
|
|
106
|
+
output = Path(output_dir)
|
|
107
|
+
if not output.is_absolute():
|
|
108
|
+
output = Path(repo_root).resolve() / output
|
|
109
|
+
generated = {key: value for key, value in result.items() if key != "model_body"}
|
|
110
|
+
_publish_generation(output, generated, result["model_body"])
|
|
111
|
+
return {key: value for key, value in result.items() if key != "model_body"}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _compile_base_runtime(
|
|
115
|
+
repo_root: str | Path,
|
|
116
|
+
manifest_path: str | Path,
|
|
117
|
+
output_dir: str | Path,
|
|
118
|
+
) -> dict[str, Any]:
|
|
119
|
+
root = Path(repo_root).resolve()
|
|
120
|
+
manifest_file = Path(manifest_path)
|
|
121
|
+
if not manifest_file.is_absolute():
|
|
122
|
+
manifest_file = root / manifest_file
|
|
123
|
+
manifest = _load_manifest(manifest_file)
|
|
124
|
+
sources: dict[str, dict[str, Any]] = {}
|
|
125
|
+
source_texts: dict[str, str] = {}
|
|
126
|
+
for source in manifest.get("sources", []):
|
|
127
|
+
xid = str(source["xid"])
|
|
128
|
+
path = root / str(source["path"])
|
|
129
|
+
body = path.read_text(encoding="utf-8")
|
|
130
|
+
if f"xid: {xid}" not in body:
|
|
131
|
+
raise ValueError(f"source XID mismatch: {path} expected {xid}")
|
|
132
|
+
sources[xid] = {
|
|
133
|
+
"xid": xid,
|
|
134
|
+
"path": str(source["path"]),
|
|
135
|
+
"source_hash": _sha256(body.encode("utf-8")),
|
|
136
|
+
}
|
|
137
|
+
source_texts[xid] = body
|
|
138
|
+
|
|
139
|
+
obligations: list[dict[str, str]] = []
|
|
140
|
+
seen_ids: set[str] = set()
|
|
141
|
+
covered_lines: dict[str, set[str]] = {xid: set() for xid in sources}
|
|
142
|
+
for item in manifest.get("obligations", []):
|
|
143
|
+
obligation_id = str(item["id"])
|
|
144
|
+
if obligation_id in seen_ids:
|
|
145
|
+
raise ValueError(f"duplicate obligation id: {obligation_id}")
|
|
146
|
+
seen_ids.add(obligation_id)
|
|
147
|
+
source_xid = str(item["source_xid"])
|
|
148
|
+
if source_xid not in source_texts:
|
|
149
|
+
raise ValueError(f"unknown obligation source XID: {source_xid}")
|
|
150
|
+
source_match = str(item["source_match"])
|
|
151
|
+
matching = [line.strip() for line in source_texts[source_xid].splitlines() if source_match in line]
|
|
152
|
+
if not matching:
|
|
153
|
+
raise ValueError(f"obligation source_match not found: {obligation_id}")
|
|
154
|
+
covered_lines[source_xid].update(matching)
|
|
155
|
+
obligations.append(
|
|
156
|
+
{
|
|
157
|
+
"id": obligation_id,
|
|
158
|
+
"source_xid": source_xid,
|
|
159
|
+
"level": str(item["level"]),
|
|
160
|
+
"statement": str(item["statement"]),
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
lint_candidates: list[dict[str, Any]] = []
|
|
165
|
+
for xid, body in source_texts.items():
|
|
166
|
+
for line_number, line in enumerate(body.splitlines(), start=1):
|
|
167
|
+
stripped = line.strip()
|
|
168
|
+
if NORMATIVE_RE.search(stripped) and stripped not in covered_lines[xid]:
|
|
169
|
+
lint_candidates.append(
|
|
170
|
+
{
|
|
171
|
+
"source_xid": xid,
|
|
172
|
+
"line": line_number,
|
|
173
|
+
"text": stripped,
|
|
174
|
+
"status": "pending",
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
body_lines = ["# XRefKit Base Runtime Contract", ""]
|
|
179
|
+
for item in obligations:
|
|
180
|
+
body_lines.append(f"- [{item['level']}] {item['id']}: {item['statement']}")
|
|
181
|
+
conditional = manifest.get("conditional_loads", [])
|
|
182
|
+
if conditional:
|
|
183
|
+
body_lines.extend(["", "Conditional loads:"])
|
|
184
|
+
for item in conditional:
|
|
185
|
+
body_lines.append(f"- {item['when']}: xid {item['xid']}")
|
|
186
|
+
model_body = "\n".join(body_lines) + "\n"
|
|
187
|
+
budgets = dict(manifest.get("budgets", {}))
|
|
188
|
+
estimated_tokens = _estimate_tokens(model_body)
|
|
189
|
+
approval = dict(manifest.get("approval", {}))
|
|
190
|
+
result = {
|
|
191
|
+
"schema": GENERATED_SCHEMA,
|
|
192
|
+
"profile": manifest.get("profile", "model-compact"),
|
|
193
|
+
"compiler_version": manifest.get("compiler_version", 1),
|
|
194
|
+
"manifest_path": str(manifest_file.relative_to(root)),
|
|
195
|
+
"manifest_hash": _sha256(manifest_file.read_bytes()),
|
|
196
|
+
"approval": approval,
|
|
197
|
+
"estimator": manifest.get("estimator", {}),
|
|
198
|
+
"budgets": budgets,
|
|
199
|
+
"sources": list(sources.values()),
|
|
200
|
+
"obligations": obligations,
|
|
201
|
+
"conditional_loads": conditional,
|
|
202
|
+
"lint_candidates": lint_candidates,
|
|
203
|
+
"model_body_hash": _sha256(model_body.encode("utf-8")),
|
|
204
|
+
"estimated_tokens": estimated_tokens,
|
|
205
|
+
"release_ready": approval.get("status") == "accepted" and not lint_candidates,
|
|
206
|
+
"model_body": model_body,
|
|
207
|
+
}
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def verify_base_runtime(
|
|
212
|
+
repo_root: str | Path,
|
|
213
|
+
manifest_path: str | Path,
|
|
214
|
+
output_dir: str | Path,
|
|
215
|
+
*,
|
|
216
|
+
release: bool,
|
|
217
|
+
) -> dict[str, Any]:
|
|
218
|
+
compiled = _compile_base_runtime(repo_root, manifest_path, output_dir)
|
|
219
|
+
result = {key: value for key, value in compiled.items() if key != "model_body"}
|
|
220
|
+
errors: list[str] = []
|
|
221
|
+
generated_path = Path(output_dir)
|
|
222
|
+
if not generated_path.is_absolute():
|
|
223
|
+
generated_path = Path(repo_root).resolve() / generated_path
|
|
224
|
+
try:
|
|
225
|
+
contracts_path, model_body_path = _published_paths(generated_path)
|
|
226
|
+
except (OSError, UnicodeDecodeError, KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
|
|
227
|
+
errors.append(f"compiled generation pointer cannot be read: {exc}")
|
|
228
|
+
return {**result, "ok": False, "errors": errors, "release": release}
|
|
229
|
+
if not contracts_path.is_file() or not model_body_path.is_file():
|
|
230
|
+
errors.append("compiled runtime output is missing")
|
|
231
|
+
return {**result, "ok": False, "errors": errors, "release": release}
|
|
232
|
+
try:
|
|
233
|
+
generated = json.loads(contracts_path.read_text(encoding="utf-8"))
|
|
234
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
235
|
+
errors.append(f"compiled contracts cannot be read: {exc}")
|
|
236
|
+
return {**result, "ok": False, "errors": errors, "release": release}
|
|
237
|
+
expected = json.loads(
|
|
238
|
+
json.dumps(
|
|
239
|
+
{key: value for key, value in compiled.items() if key != "model_body"},
|
|
240
|
+
ensure_ascii=False,
|
|
241
|
+
default=str,
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
if generated != expected:
|
|
245
|
+
errors.append("compiled contracts differ from current source and manifest")
|
|
246
|
+
try:
|
|
247
|
+
published_model_body = model_body_path.read_text(encoding="utf-8")
|
|
248
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
249
|
+
errors.append(f"compiled model body cannot be read: {exc}")
|
|
250
|
+
published_model_body = None
|
|
251
|
+
if published_model_body is not None and published_model_body != compiled["model_body"]:
|
|
252
|
+
errors.append("compiled model body differs from current source and manifest")
|
|
253
|
+
l0_budget = int(result["budgets"].get("l0_tokens", 0))
|
|
254
|
+
if not l0_budget or result["estimated_tokens"] > l0_budget:
|
|
255
|
+
errors.append(
|
|
256
|
+
f"L0 token budget exceeded: {result['estimated_tokens']} > {l0_budget}"
|
|
257
|
+
)
|
|
258
|
+
if release:
|
|
259
|
+
if result["approval"].get("status") != "accepted":
|
|
260
|
+
errors.append("runtime budget and manifest approval is not accepted")
|
|
261
|
+
if result["lint_candidates"]:
|
|
262
|
+
errors.append(f"pending normative lint candidates: {len(result['lint_candidates'])}")
|
|
263
|
+
return {**result, "ok": not errors, "errors": errors, "release": release}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def main(argv: list[str] | None = None) -> int:
|
|
267
|
+
parser = argparse.ArgumentParser(prog="xrefkit pack")
|
|
268
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
269
|
+
for name in ("build-base", "verify-base"):
|
|
270
|
+
command = sub.add_parser(name)
|
|
271
|
+
command.add_argument("--root", default=".")
|
|
272
|
+
command.add_argument("--manifest", default="docs/core/contracts/base_runtime_manifest.yaml")
|
|
273
|
+
command.add_argument("--output", default="xrefkit/resources/base")
|
|
274
|
+
command.add_argument("--json", action="store_true")
|
|
275
|
+
if name == "verify-base":
|
|
276
|
+
command.add_argument("--draft", action="store_true")
|
|
277
|
+
args = parser.parse_args(argv)
|
|
278
|
+
try:
|
|
279
|
+
if args.command == "build-base":
|
|
280
|
+
result = compile_base_runtime(args.root, args.manifest, args.output)
|
|
281
|
+
result = {**result, "ok": True}
|
|
282
|
+
else:
|
|
283
|
+
result = verify_base_runtime(
|
|
284
|
+
args.root,
|
|
285
|
+
args.manifest,
|
|
286
|
+
args.output,
|
|
287
|
+
release=not args.draft,
|
|
288
|
+
)
|
|
289
|
+
except (OSError, ValueError, yaml.YAMLError) as exc:
|
|
290
|
+
result = {"ok": False, "errors": [str(exc)]}
|
|
291
|
+
if args.json:
|
|
292
|
+
print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
|
|
293
|
+
else:
|
|
294
|
+
print("ok" if result.get("ok") else "failed")
|
|
295
|
+
for error in result.get("errors", []):
|
|
296
|
+
print(f"- {error}")
|
|
297
|
+
return 0 if result.get("ok") else 1
|
xrefkit/ctx.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from xrefkit.xref import MANAGED_FRAGMENT_RE, WIKI_XREF_RE, XrefConfig, build_index
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class PackedDoc:
|
|
13
|
+
xid: str
|
|
14
|
+
path: str
|
|
15
|
+
title: str | None
|
|
16
|
+
bytes_included: int
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _extract_referenced_xids(text: str) -> set[str]:
|
|
20
|
+
xids = set(MANAGED_FRAGMENT_RE.findall(text))
|
|
21
|
+
xids.update(WIKI_XREF_RE.findall(text))
|
|
22
|
+
return xids
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _read_text(path: Path) -> str:
|
|
26
|
+
return path.read_text(encoding="utf-8")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _build_pack_text(
|
|
30
|
+
*,
|
|
31
|
+
root: Path,
|
|
32
|
+
ordered_xids: list[str],
|
|
33
|
+
index,
|
|
34
|
+
per_doc_bytes: int,
|
|
35
|
+
max_bytes: int,
|
|
36
|
+
) -> tuple[str, list[PackedDoc]]:
|
|
37
|
+
parts: list[str] = []
|
|
38
|
+
packed: list[PackedDoc] = []
|
|
39
|
+
|
|
40
|
+
header = [
|
|
41
|
+
"# Context Pack",
|
|
42
|
+
"",
|
|
43
|
+
f"- root: {root.as_posix()}",
|
|
44
|
+
f"- docs: {len(ordered_xids)}",
|
|
45
|
+
"",
|
|
46
|
+
]
|
|
47
|
+
parts.append("\n".join(header))
|
|
48
|
+
|
|
49
|
+
total = sum(len(p.encode("utf-8")) for p in parts)
|
|
50
|
+
for xid in ordered_xids:
|
|
51
|
+
info = index.get(xid)
|
|
52
|
+
if info is None:
|
|
53
|
+
continue
|
|
54
|
+
rel = str(info.path.relative_to(root)).replace("\\", "/")
|
|
55
|
+
title = info.title
|
|
56
|
+
|
|
57
|
+
text = _read_text(info.path)
|
|
58
|
+
blob = text.encode("utf-8")
|
|
59
|
+
truncated = blob[:per_doc_bytes].decode("utf-8", errors="ignore")
|
|
60
|
+
|
|
61
|
+
section = [
|
|
62
|
+
"---",
|
|
63
|
+
f"## {title or xid}",
|
|
64
|
+
f"- xid: {xid}",
|
|
65
|
+
f"- path: {rel}",
|
|
66
|
+
"",
|
|
67
|
+
truncated.rstrip("\n"),
|
|
68
|
+
"",
|
|
69
|
+
]
|
|
70
|
+
section_text = "\n".join(section)
|
|
71
|
+
section_bytes = len(section_text.encode("utf-8"))
|
|
72
|
+
if total + section_bytes > max_bytes:
|
|
73
|
+
break
|
|
74
|
+
parts.append(section_text)
|
|
75
|
+
total += section_bytes
|
|
76
|
+
packed.append(
|
|
77
|
+
PackedDoc(
|
|
78
|
+
xid=xid,
|
|
79
|
+
path=rel,
|
|
80
|
+
title=title,
|
|
81
|
+
bytes_included=len(truncated.encode("utf-8")),
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return "\n".join(parts).rstrip() + "\n", packed
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def ctx_pack(
|
|
89
|
+
cfg: XrefConfig,
|
|
90
|
+
*,
|
|
91
|
+
seeds: list[str],
|
|
92
|
+
depth: int,
|
|
93
|
+
max_bytes: int,
|
|
94
|
+
per_doc_bytes: int,
|
|
95
|
+
) -> dict[str, object]:
|
|
96
|
+
root = cfg.resolved_root()
|
|
97
|
+
index, issues = build_index(cfg)
|
|
98
|
+
|
|
99
|
+
visited: set[str] = set()
|
|
100
|
+
frontier: set[str] = {s for s in seeds if s in index}
|
|
101
|
+
ordered: list[str] = []
|
|
102
|
+
|
|
103
|
+
for _ in range(max(0, depth) + 1):
|
|
104
|
+
if not frontier:
|
|
105
|
+
break
|
|
106
|
+
next_frontier: set[str] = set()
|
|
107
|
+
for xid in sorted(frontier):
|
|
108
|
+
if xid in visited:
|
|
109
|
+
continue
|
|
110
|
+
visited.add(xid)
|
|
111
|
+
ordered.append(xid)
|
|
112
|
+
text = _read_text(index[xid].path)
|
|
113
|
+
for ref in _extract_referenced_xids(text):
|
|
114
|
+
if ref in index and ref not in visited:
|
|
115
|
+
next_frontier.add(ref)
|
|
116
|
+
frontier = next_frontier
|
|
117
|
+
|
|
118
|
+
pack_text, packed_docs = _build_pack_text(
|
|
119
|
+
root=root,
|
|
120
|
+
ordered_xids=ordered,
|
|
121
|
+
index=index,
|
|
122
|
+
per_doc_bytes=per_doc_bytes,
|
|
123
|
+
max_bytes=max_bytes,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
"issues": issues,
|
|
128
|
+
"seeds": seeds,
|
|
129
|
+
"depth": depth,
|
|
130
|
+
"docs": [d.__dict__ for d in packed_docs],
|
|
131
|
+
"text": pack_text,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def cmd_ctx(args: argparse.Namespace, cfg: XrefConfig) -> int:
|
|
136
|
+
if args.ctx_cmd != "pack":
|
|
137
|
+
raise ValueError(f"Unknown ctx command: {args.ctx_cmd}")
|
|
138
|
+
|
|
139
|
+
result = ctx_pack(
|
|
140
|
+
cfg,
|
|
141
|
+
seeds=list(args.seed or []),
|
|
142
|
+
depth=int(args.depth),
|
|
143
|
+
max_bytes=int(args.max_bytes),
|
|
144
|
+
per_doc_bytes=int(args.per_doc_bytes),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
out_path = Path(args.out).resolve() if args.out else None
|
|
148
|
+
if out_path is not None:
|
|
149
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
out_path.write_text(result["text"], encoding="utf-8", newline="\n")
|
|
151
|
+
|
|
152
|
+
if args.json:
|
|
153
|
+
if out_path is not None:
|
|
154
|
+
result["out"] = str(out_path)
|
|
155
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
156
|
+
else:
|
|
157
|
+
print(result["text"], end="")
|
|
158
|
+
|
|
159
|
+
return 0
|
|
160
|
+
|