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.
Files changed (47) hide show
  1. {scriptoria-0.5.1 → scriptoria-0.5.2}/PKG-INFO +1 -1
  2. {scriptoria-0.5.1 → scriptoria-0.5.2}/pyproject.toml +1 -1
  3. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/__init__.py +1 -1
  4. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/frontmatter.py +44 -5
  5. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/graph.py +3 -11
  6. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/similar.py +3 -3
  7. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_frontmatter.py +30 -0
  8. {scriptoria-0.5.1 → scriptoria-0.5.2}/uv.lock +1 -1
  9. {scriptoria-0.5.1 → scriptoria-0.5.2}/.gitignore +0 -0
  10. {scriptoria-0.5.1 → scriptoria-0.5.2}/README.md +0 -0
  11. {scriptoria-0.5.1 → scriptoria-0.5.2}/pyrightconfig.json +0 -0
  12. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/anchors.py +0 -0
  13. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/blocks.py +0 -0
  14. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/cli.py +0 -0
  15. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/embeddings.py +0 -0
  16. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/errors.py +0 -0
  17. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/facts.py +0 -0
  18. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/hashing.py +0 -0
  19. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/ingest.py +0 -0
  20. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/lock.py +0 -0
  21. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/manifest.py +0 -0
  22. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/query.py +0 -0
  23. {scriptoria-0.5.1 → scriptoria-0.5.2}/src/scrip/retrieval.py +0 -0
  24. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/conftest.py +0 -0
  25. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_anchor_cmd.py +0 -0
  26. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_anchors.py +0 -0
  27. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_blocks.py +0 -0
  28. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_embeddings.py +0 -0
  29. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_fact_cmd.py +0 -0
  30. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_graph_status.py +0 -0
  31. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_hashing.py +0 -0
  32. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_index_cmd.py +0 -0
  33. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_ingest.py +0 -0
  34. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_json_shapes.py +0 -0
  35. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_lock.py +0 -0
  36. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_manifest.py +0 -0
  37. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_new_cmd.py +0 -0
  38. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_query.py +0 -0
  39. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_retrieval.py +0 -0
  40. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_similar_cmd.py +0 -0
  41. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_span_cmd.py +0 -0
  42. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_stamp.py +0 -0
  43. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_status_cmd.py +0 -0
  44. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_unlock_cmd.py +0 -0
  45. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_verify.py +0 -0
  46. {scriptoria-0.5.1 → scriptoria-0.5.2}/tests/test_version.py +0 -0
  47. {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.1
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.1"
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.1"
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
- text = p.read_text(encoding="utf-8")
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, _ = frontmatter.load(path)
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, _ = frontmatter.load(root / d["path"])
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' 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"])}'
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")
@@ -1141,7 +1141,7 @@ wheels = [
1141
1141
 
1142
1142
  [[package]]
1143
1143
  name = "scriptoria"
1144
- version = "0.5.1"
1144
+ version = "0.5.2"
1145
1145
  source = { editable = "." }
1146
1146
  dependencies = [
1147
1147
  { name = "duckdb" },
File without changes
File without changes
File without changes
File without changes
File without changes