scriptoria 0.5.1__tar.gz → 0.5.2__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.
- {scriptoria-0.5.1 → scriptoria-0.5.2}/PKG-INFO +1 -1
- {scriptoria-0.5.1 → scriptoria-0.5.2}/pyproject.toml +1 -1
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/__init__.py +1 -1
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/frontmatter.py +44 -5
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/graph.py +3 -11
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/similar.py +3 -3
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_frontmatter.py +30 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/uv.lock +1 -1
- {scriptoria-0.5.1 → scriptoria-0.5.2}/.gitignore +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/README.md +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/pyrightconfig.json +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/anchors.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/blocks.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/cli.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/embeddings.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/errors.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/facts.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/hashing.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/ingest.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/lock.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/manifest.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/query.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/retrieval.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/conftest.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_anchor_cmd.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_anchors.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_blocks.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_embeddings.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_fact_cmd.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_graph_status.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_hashing.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_index_cmd.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_ingest.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_json_shapes.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_lock.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_manifest.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_new_cmd.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_query.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_retrieval.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_similar_cmd.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_span_cmd.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_stamp.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_status_cmd.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_unlock_cmd.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_verify.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_version.py +0 -0
- {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_watch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scriptoria
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Deterministic scriptorium-keeper (the `scrip` CLI): staleness, provenance integrity, and fact queries for an agent-compiled knowledge base
|
|
5
5
|
Project-URL: Homepage, https://github.com/coredipper/scriptorium
|
|
6
6
|
Project-URL: Changelog, https://github.com/coredipper/scriptorium/blob/main/CHANGELOG.md
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Distribution name on PyPI is `scriptoria` (scrip/scriptorium were taken); the
|
|
3
3
|
# CLI command and the import package both remain `scrip`.
|
|
4
4
|
name = "scriptoria"
|
|
5
|
-
version = "0.5.
|
|
5
|
+
version = "0.5.2"
|
|
6
6
|
description = "Deterministic scriptorium-keeper (the `scrip` CLI): staleness, provenance integrity, and fact queries for an agent-compiled knowledge base"
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
requires-python = ">=3.10"
|
|
@@ -13,7 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
-
__version__ = "0.5.
|
|
16
|
+
__version__ = "0.5.2"
|
|
17
17
|
|
|
18
18
|
# --- canonical vault layout ------------------------------------------------
|
|
19
19
|
# ``root`` is the repo/instance root: the directory containing ``vault/``.
|
|
@@ -39,13 +39,54 @@ def parse(text: str) -> tuple[dict, str]:
|
|
|
39
39
|
raise DataError("unterminated frontmatter (missing closing '---')")
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
def _read_frontmatter(f) -> tuple[bool, dict]:
|
|
43
|
+
first_line = f.readline()
|
|
44
|
+
if not first_line or first_line.strip() != FENCE:
|
|
45
|
+
return False, {}
|
|
46
|
+
|
|
47
|
+
fm_lines = []
|
|
48
|
+
for line in f:
|
|
49
|
+
if line.strip() == FENCE:
|
|
50
|
+
break
|
|
51
|
+
fm_lines.append(line)
|
|
52
|
+
else:
|
|
53
|
+
raise DataError("unterminated frontmatter (missing closing '---')")
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
meta = yaml.safe_load("".join(fm_lines))
|
|
57
|
+
except yaml.YAMLError as e:
|
|
58
|
+
raise DataError(f"invalid YAML frontmatter: {e}") from e
|
|
59
|
+
if meta is None:
|
|
60
|
+
meta = {}
|
|
61
|
+
if not isinstance(meta, dict):
|
|
62
|
+
raise DataError("frontmatter must be a YAML mapping")
|
|
63
|
+
return True, meta
|
|
64
|
+
|
|
65
|
+
|
|
42
66
|
def load(path: str | Path) -> tuple[dict, str]:
|
|
43
67
|
p = Path(path)
|
|
44
68
|
try:
|
|
45
|
-
|
|
69
|
+
with open(p, encoding="utf-8") as f:
|
|
70
|
+
found, meta = _read_frontmatter(f)
|
|
71
|
+
if not found:
|
|
72
|
+
f.seek(0)
|
|
73
|
+
return {}, f.read()
|
|
74
|
+
body = f.read()
|
|
75
|
+
return meta, body
|
|
76
|
+
except OSError as e:
|
|
77
|
+
raise DataError(f"cannot read {p}: {e}") from e
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load_meta(path: str | Path) -> dict:
|
|
81
|
+
"""Read just the frontmatter from a file without loading the body.
|
|
82
|
+
Significantly faster for metadata-only operations like scanning the vault."""
|
|
83
|
+
p = Path(path)
|
|
84
|
+
try:
|
|
85
|
+
with open(p, encoding="utf-8") as f:
|
|
86
|
+
_, meta = _read_frontmatter(f)
|
|
87
|
+
return meta
|
|
46
88
|
except OSError as e:
|
|
47
89
|
raise DataError(f"cannot read {p}: {e}") from e
|
|
48
|
-
return parse(text)
|
|
49
90
|
|
|
50
91
|
|
|
51
92
|
def dump(meta: dict, body: str) -> str:
|
|
@@ -69,9 +110,7 @@ def as_str(meta: dict, key: str, where: str = "frontmatter") -> str | None:
|
|
|
69
110
|
if value is None:
|
|
70
111
|
return None
|
|
71
112
|
if not isinstance(value, str):
|
|
72
|
-
raise DataError(
|
|
73
|
-
f"{where}: '{key}' must be a string, got {type(value).__name__}"
|
|
74
|
-
)
|
|
113
|
+
raise DataError(f"{where}: '{key}' must be a string, got {type(value).__name__}")
|
|
75
114
|
return value
|
|
76
115
|
|
|
77
116
|
|
|
@@ -77,7 +77,7 @@ def scan_derived(root: Path) -> dict:
|
|
|
77
77
|
wd = wiki_dir(root)
|
|
78
78
|
if wd.is_dir():
|
|
79
79
|
for path in sorted(wd.rglob("*.md")):
|
|
80
|
-
meta
|
|
80
|
+
meta = frontmatter.load_meta(path)
|
|
81
81
|
if not meta or "derived-from" not in meta:
|
|
82
82
|
continue # index.md, log.md, hand notes: not tracked artifacts
|
|
83
83
|
where = str(path.relative_to(root))
|
|
@@ -195,11 +195,7 @@ def compute_status(
|
|
|
195
195
|
else:
|
|
196
196
|
ok.append({"id": did, "path": d["path"]})
|
|
197
197
|
|
|
198
|
-
uncompiled = [
|
|
199
|
-
{"id": rid, "path": r["path"]}
|
|
200
|
-
for rid, r in raw.items()
|
|
201
|
-
if rid not in referenced
|
|
202
|
-
]
|
|
198
|
+
uncompiled = [{"id": rid, "path": r["path"]} for rid, r in raw.items() if rid not in referenced]
|
|
203
199
|
|
|
204
200
|
if rebuild:
|
|
205
201
|
manifest_mod.save(root, manifest_mod.build(raw, derived))
|
|
@@ -271,11 +267,7 @@ def print_status(result: dict) -> None:
|
|
|
271
267
|
if stale:
|
|
272
268
|
print(f"STALE ({len(stale)})")
|
|
273
269
|
for s in stale:
|
|
274
|
-
tag = (
|
|
275
|
-
f" [changed: {', '.join(s['changed_sources'])}]"
|
|
276
|
-
if s["changed_sources"]
|
|
277
|
-
else ""
|
|
278
|
-
)
|
|
270
|
+
tag = f" [changed: {', '.join(s['changed_sources'])}]" if s["changed_sources"] else ""
|
|
279
271
|
print(f" ✗ {s['id']} — {s['reason']}{tag}")
|
|
280
272
|
if unc:
|
|
281
273
|
print(f"UNCOMPILED ({len(unc)})")
|
|
@@ -115,7 +115,7 @@ def compute_similar(
|
|
|
115
115
|
if d.get("type") != want_type or cid in skip:
|
|
116
116
|
continue # other-kind pages and the facts.set row are dropped here
|
|
117
117
|
c_sources = _source_set(d["derived_from"])
|
|
118
|
-
meta
|
|
118
|
+
meta = frontmatter.load_meta(root / d["path"])
|
|
119
119
|
c_title = (meta.get("title") if meta else "") or ""
|
|
120
120
|
c_tags = _page_tags(c_sources, source_tags)
|
|
121
121
|
|
|
@@ -163,7 +163,7 @@ def print_similar(result: dict) -> None:
|
|
|
163
163
|
s = c["scores"]
|
|
164
164
|
print(f' {s["combined"]:.3f} {c["id"]} "{c["title"]}"')
|
|
165
165
|
print(
|
|
166
|
-
f
|
|
167
|
-
f
|
|
166
|
+
f" sources {s['sources']:.2f} tags {s['tags']:.2f} title {s['title']:.2f}"
|
|
167
|
+
f" shared sources: {len(c['shared']['sources'])}, tags: {len(c['shared']['tags'])}"
|
|
168
168
|
)
|
|
169
169
|
print(f"({len(cands)} candidate(s))")
|
|
@@ -44,6 +44,36 @@ def test_load_missing_file_raises(tmp_path):
|
|
|
44
44
|
frontmatter.load(tmp_path / "nope.md")
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
def test_load_no_frontmatter_returns_whole_text(tmp_path):
|
|
48
|
+
path = tmp_path / "plain.md"
|
|
49
|
+
path.write_text("just a body\n", encoding="utf-8")
|
|
50
|
+
|
|
51
|
+
meta, body = frontmatter.load(path)
|
|
52
|
+
|
|
53
|
+
assert meta == {}
|
|
54
|
+
assert body == "just a body\n"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_load_empty_frontmatter_strips_fences(tmp_path):
|
|
58
|
+
path = tmp_path / "empty.md"
|
|
59
|
+
path.write_text("---\n---\nbody\n", encoding="utf-8")
|
|
60
|
+
|
|
61
|
+
meta, body = frontmatter.load(path)
|
|
62
|
+
|
|
63
|
+
assert meta == {}
|
|
64
|
+
assert body == "body\n"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_load_comment_only_frontmatter_strips_fences(tmp_path):
|
|
68
|
+
path = tmp_path / "comment.md"
|
|
69
|
+
path.write_text("---\n# intentionally empty\n---\nbody\n", encoding="utf-8")
|
|
70
|
+
|
|
71
|
+
meta, body = frontmatter.load(path)
|
|
72
|
+
|
|
73
|
+
assert meta == {}
|
|
74
|
+
assert body == "body\n"
|
|
75
|
+
|
|
76
|
+
|
|
47
77
|
def test_dump_roundtrips_and_preserves_insertion_order():
|
|
48
78
|
meta = {"id": "concept/x", "type": "wiki.concept", "derived-from": ["raw/a"]}
|
|
49
79
|
text = frontmatter.dump(meta, "Body.\n")
|
|
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
|