scriptoria 0.6.1__tar.gz → 0.6.3__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 (49) hide show
  1. {scriptoria-0.6.1 → scriptoria-0.6.3}/PKG-INFO +1 -1
  2. {scriptoria-0.6.1 → scriptoria-0.6.3}/pyproject.toml +1 -1
  3. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/__init__.py +1 -1
  4. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/frontmatter.py +5 -16
  5. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/query.py +19 -10
  6. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_query.py +24 -0
  7. {scriptoria-0.6.1 → scriptoria-0.6.3}/uv.lock +1 -1
  8. {scriptoria-0.6.1 → scriptoria-0.6.3}/.gitignore +0 -0
  9. {scriptoria-0.6.1 → scriptoria-0.6.3}/README.md +0 -0
  10. {scriptoria-0.6.1 → scriptoria-0.6.3}/pyrightconfig.json +0 -0
  11. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/anchors.py +0 -0
  12. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/blocks.py +0 -0
  13. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/cli.py +0 -0
  14. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/embeddings.py +0 -0
  15. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/errors.py +0 -0
  16. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/facts.py +0 -0
  17. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/graph.py +0 -0
  18. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/hashing.py +0 -0
  19. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/ingest.py +0 -0
  20. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/lock.py +0 -0
  21. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/manifest.py +0 -0
  22. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/pageindex_adapter.py +0 -0
  23. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/retrieval.py +0 -0
  24. {scriptoria-0.6.1 → scriptoria-0.6.3}/src/scrip/similar.py +0 -0
  25. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/conftest.py +0 -0
  26. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_anchor_cmd.py +0 -0
  27. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_anchors.py +0 -0
  28. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_blocks.py +0 -0
  29. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_embeddings.py +0 -0
  30. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_fact_cmd.py +0 -0
  31. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_frontmatter.py +0 -0
  32. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_graph_status.py +0 -0
  33. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_hashing.py +0 -0
  34. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_index_cmd.py +0 -0
  35. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_ingest.py +0 -0
  36. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_json_shapes.py +0 -0
  37. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_lock.py +0 -0
  38. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_manifest.py +0 -0
  39. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_new_cmd.py +0 -0
  40. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_pageindex_cmd.py +0 -0
  41. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_retrieval.py +0 -0
  42. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_similar_cmd.py +0 -0
  43. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_span_cmd.py +0 -0
  44. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_stamp.py +0 -0
  45. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_status_cmd.py +0 -0
  46. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_unlock_cmd.py +0 -0
  47. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_verify.py +0 -0
  48. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_version.py +0 -0
  49. {scriptoria-0.6.1 → scriptoria-0.6.3}/tests/test_watch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptoria
3
- Version: 0.6.1
3
+ Version: 0.6.3
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.6.1"
5
+ version = "0.6.3"
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.6.1"
16
+ __version__ = "0.6.3"
17
17
 
18
18
  # --- canonical vault layout ------------------------------------------------
19
19
  # ``root`` is the repo/instance root: the directory containing ``vault/``.
@@ -7,6 +7,7 @@ whole CLI stay "a few hundred LOC" with only duckdb + pyyaml as real deps.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import io
10
11
  from collections.abc import Iterable
11
12
  from pathlib import Path
12
13
 
@@ -25,23 +26,11 @@ FENCE = "---"
25
26
  def parse(text: str) -> tuple[dict, str]:
26
27
  """Return ``(meta, body)``. If there is no frontmatter, ``meta`` is ``{}``
27
28
  and ``body`` is the whole text."""
28
- lines = text.splitlines(keepends=True)
29
- if not lines or lines[0].strip() != FENCE:
29
+ f = io.StringIO(text, newline="")
30
+ found, meta = _read_frontmatter(f)
31
+ if not found:
30
32
  return {}, text
31
- for i in range(1, len(lines)):
32
- if lines[i].strip() == FENCE:
33
- fm_text = "".join(lines[1:i])
34
- body = "".join(lines[i + 1 :])
35
- try:
36
- meta = yaml.load(fm_text, Loader=SafeLoader)
37
- except yaml.YAMLError as e:
38
- raise DataError(f"invalid YAML frontmatter: {e}") from e
39
- if meta is None:
40
- meta = {}
41
- if not isinstance(meta, dict):
42
- raise DataError("frontmatter must be a YAML mapping")
43
- return meta, body
44
- raise DataError("unterminated frontmatter (missing closing '---')")
33
+ return meta, f.read()
45
34
 
46
35
 
47
36
  def _read_frontmatter(f) -> tuple[bool, dict]:
@@ -55,26 +55,35 @@ _NAMED = {
55
55
  _FILTERABLE = {"claims", "entities", "edges", "reconciliations"}
56
56
 
57
57
 
58
+ def _create_empty_reconciliations_view(con: duckdb.DuckDBPyConnection) -> None:
59
+ # Always present (empty stub) so `contradictions` can anti-join it and raw SQL
60
+ # over its columns works even before any reconciliation exists.
61
+ con.execute(
62
+ "CREATE VIEW reconciliations AS SELECT "
63
+ "NULL::VARCHAR AS reconciliation_id, NULL::VARCHAR AS decision, "
64
+ "NULL::VARCHAR AS claim_a, NULL::VARCHAR AS claim_b, "
65
+ "NULL::VARCHAR AS winner, NULL::VARCHAR AS rationale, "
66
+ "NULL::VARCHAR AS at WHERE FALSE"
67
+ )
68
+
69
+
70
+ def _has_rows(path: Path) -> bool:
71
+ with path.open(encoding="utf-8") as f:
72
+ return any(line.strip() for line in f)
73
+
74
+
58
75
  def _connect(root: Path) -> duckdb.DuckDBPyConnection:
59
76
  con = duckdb.connect(":memory:")
60
77
  fd = facts_dir(root)
61
78
  for view, fname in _VIEWS.items():
62
79
  p = fd / fname
63
- if p.exists():
80
+ if p.exists() and (view != "reconciliations" or _has_rows(p)):
64
81
  con.execute(
65
82
  f"CREATE VIEW {view} AS "
66
83
  f"SELECT * FROM read_ndjson_auto('{p.as_posix()}')"
67
84
  )
68
85
  elif view == "reconciliations":
69
- # Always present (empty stub) so `contradictions` can anti-join it and
70
- # raw SQL over its columns works even before any reconciliation exists.
71
- con.execute(
72
- "CREATE VIEW reconciliations AS SELECT "
73
- "NULL::VARCHAR AS reconciliation_id, NULL::VARCHAR AS decision, "
74
- "NULL::VARCHAR AS claim_a, NULL::VARCHAR AS claim_b, "
75
- "NULL::VARCHAR AS winner, NULL::VARCHAR AS rationale, "
76
- "NULL::VARCHAR AS at WHERE FALSE"
77
- )
86
+ _create_empty_reconciliations_view(con)
78
87
  return con
79
88
 
80
89
 
@@ -74,6 +74,16 @@ def test_contradictions_works_without_reconciliations_file(kb):
74
74
  assert len(query.run(kb.root, name="contradictions")[1]) == 1
75
75
 
76
76
 
77
+ def test_contradictions_works_with_empty_reconciliations_file(kb):
78
+ # an existing-but-empty file should use the same stub schema as a missing file
79
+ _contradiction_pair(kb)
80
+ (kb.root / "vault" / "facts" / "reconciliations.ndjson").write_text(
81
+ "\n", encoding="utf-8"
82
+ )
83
+
84
+ assert len(query.run(kb.root, name="contradictions")[1]) == 1
85
+
86
+
77
87
  def test_query_reconciliations_cli_choice(kb):
78
88
  # the named query must be a valid CLI `query` choice, not just in query.run
79
89
  _contradiction_pair(kb)
@@ -88,6 +98,20 @@ def test_reconciliations_stub_exposes_full_schema(kb):
88
98
  assert "decision" in cols
89
99
 
90
100
 
101
+ def test_empty_reconciliations_file_exposes_full_schema(kb):
102
+ _contradiction_pair(kb)
103
+ (kb.root / "vault" / "facts" / "reconciliations.ndjson").write_text(
104
+ "\n", encoding="utf-8"
105
+ )
106
+
107
+ cols, rows = query.run(
108
+ kb.root,
109
+ sql="SELECT decision, winner, reconciliation_id FROM reconciliations",
110
+ )
111
+ assert rows == []
112
+ assert "decision" in cols
113
+
114
+
91
115
  def test_where_and_limit(kb):
92
116
  kb.add_raw("a", "# A\n\nThe sky is blue.\n")
93
117
  kb.add_claim("clm_1", "a", "the sky is blue")
@@ -1141,7 +1141,7 @@ wheels = [
1141
1141
 
1142
1142
  [[package]]
1143
1143
  name = "scriptoria"
1144
- version = "0.6.1"
1144
+ version = "0.6.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