quirework 0.1.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 (138) hide show
  1. quirework/__init__.py +3 -0
  2. quirework/__main__.py +3 -0
  3. quirework/cli.py +431 -0
  4. quirework/core/__init__.py +1 -0
  5. quirework/core/artifact_paths.py +86 -0
  6. quirework/core/checks.py +235 -0
  7. quirework/core/frontmatter.py +83 -0
  8. quirework/core/globs.py +45 -0
  9. quirework/core/pinned.py +169 -0
  10. quirework/core/usage.py +604 -0
  11. quirework/delivery/__init__.py +1 -0
  12. quirework/delivery/artifacts.py +210 -0
  13. quirework/delivery/text.py +166 -0
  14. quirework/manuscript/__init__.py +1 -0
  15. quirework/manuscript/backfill.py +1972 -0
  16. quirework/manuscript/editions.py +111 -0
  17. quirework/manuscript/footprint.py +95 -0
  18. quirework/manuscript/formats.py +208 -0
  19. quirework/manuscript/lifecycle.py +191 -0
  20. quirework/manuscript/notes.py +2301 -0
  21. quirework/manuscript/pages.py +284 -0
  22. quirework/manuscript/quarto.py +416 -0
  23. quirework/manuscript/quotes.py +512 -0
  24. quirework/manuscript/rename.py +309 -0
  25. quirework/manuscript/sources.py +286 -0
  26. quirework/manuscript/trail.py +1875 -0
  27. quirework/manuscript/units.py +416 -0
  28. quirework/project.py +645 -0
  29. quirework/provenance/__init__.py +1 -0
  30. quirework/provenance/agyfilter.py +436 -0
  31. quirework/provenance/backup/__init__.py +469 -0
  32. quirework/provenance/backup/base.py +290 -0
  33. quirework/provenance/backup/command.py +107 -0
  34. quirework/provenance/backup/directory.py +142 -0
  35. quirework/provenance/backup/git.py +150 -0
  36. quirework/provenance/backup/rclone.py +153 -0
  37. quirework/provenance/capture/__init__.py +1 -0
  38. quirework/provenance/capture/commands.py +28 -0
  39. quirework/provenance/capture/extract_artifacts.py +110 -0
  40. quirework/provenance/capture/extract_files.py +118 -0
  41. quirework/provenance/capture/fetch_conversation.py +127 -0
  42. quirework/provenance/capture/fetch_gemini.py +149 -0
  43. quirework/provenance/capture/render_transcript.py +126 -0
  44. quirework/provenance/capture/transform.py +22 -0
  45. quirework/provenance/catalog.py +733 -0
  46. quirework/provenance/ccfilter.py +1126 -0
  47. quirework/provenance/codexfilter.py +883 -0
  48. quirework/provenance/coldread.py +1731 -0
  49. quirework/provenance/curate.py +67 -0
  50. quirework/provenance/humaninput.py +811 -0
  51. quirework/provenance/ingest.py +31 -0
  52. quirework/provenance/ingestion/__init__.py +1 -0
  53. quirework/provenance/ingestion/agy.py +192 -0
  54. quirework/provenance/ingestion/claude.py +493 -0
  55. quirework/provenance/ingestion/codex.py +296 -0
  56. quirework/provenance/lineage.py +148 -0
  57. quirework/provenance/metrics.py +1247 -0
  58. quirework/provenance/rulings.py +478 -0
  59. quirework/provenance/scope.py +73 -0
  60. quirework/provenance/search.py +455 -0
  61. quirework/provenance/semantic.py +475 -0
  62. quirework/provenance/show.py +1213 -0
  63. quirework/provenance/store.py +1271 -0
  64. quirework/provenance/trees.py +133 -0
  65. quirework/provenance/worktrees.py +199 -0
  66. quirework/providers/__init__.py +1 -0
  67. quirework/providers/blind.py +2192 -0
  68. quirework/providers/canary.py +685 -0
  69. quirework/providers/catalog.py +44 -0
  70. quirework/providers/claude_login.py +92 -0
  71. quirework/providers/exits.py +196 -0
  72. quirework/providers/harness.py +830 -0
  73. quirework/providers/images.py +92 -0
  74. quirework/providers/models.py +1284 -0
  75. quirework/providers/quota.py +588 -0
  76. quirework/providers/reads.py +474 -0
  77. quirework/providers/replies.py +175 -0
  78. quirework/publish/__init__.py +1 -0
  79. quirework/publish/backends.py +112 -0
  80. quirework/publish/bookstage.py +1086 -0
  81. quirework/publish/build.py +60 -0
  82. quirework/publish/editions.py +48 -0
  83. quirework/publish/epubcheck.py +287 -0
  84. quirework/publish/experiment.py +1097 -0
  85. quirework/publish/figfonts.py +234 -0
  86. quirework/publish/fignumbers.py +293 -0
  87. quirework/publish/figures.py +729 -0
  88. quirework/publish/folios.py +62 -0
  89. quirework/publish/fonts.py +307 -0
  90. quirework/publish/graybuild.py +73 -0
  91. quirework/publish/hugo.py +117 -0
  92. quirework/publish/kindle.js +411 -0
  93. quirework/publish/kindle.py +1393 -0
  94. quirework/publish/panelkit.py +918 -0
  95. quirework/publish/plans.py +651 -0
  96. quirework/publish/proof.py +792 -0
  97. quirework/publish/redpen.py +1654 -0
  98. quirework/publish/render.py +942 -0
  99. quirework/publish/targets.py +216 -0
  100. quirework/publish/variant.py +918 -0
  101. quirework/runs/__init__.py +1 -0
  102. quirework/runs/__main__.py +15 -0
  103. quirework/runs/commands.py +278 -0
  104. quirework/runs/coverage.py +86 -0
  105. quirework/runs/engine.py +2143 -0
  106. quirework/runs/feed.py +853 -0
  107. quirework/runs/observe.py +379 -0
  108. quirework/runs/storage.py +155 -0
  109. quirework/settings.py +492 -0
  110. quirework/sops/__init__.py +1 -0
  111. quirework/sops/catalog.py +386 -0
  112. quirework/sops/commands.py +567 -0
  113. quirework/sops/composition.py +382 -0
  114. quirework/sops/instruments.py +184 -0
  115. quirework/sops/personas.py +296 -0
  116. quirework/sops/review.py +2568 -0
  117. quirework/sops/room.py +2791 -0
  118. quirework/sops/roundtable.py +2578 -0
  119. quirework/sops/sections.py +169 -0
  120. quirework/sops/workshop.py +1629 -0
  121. quirework/templates/backup.toml +39 -0
  122. quirework/templates/book.toml +88 -0
  123. quirework/templates/run.toml +32 -0
  124. quirework/templates/site.toml +90 -0
  125. quirework/templates/tells.toml +64 -0
  126. quirework/templates/terminology.toml +20 -0
  127. quirework/templates/tmpfiles.conf +42 -0
  128. quirework/testing.py +345 -0
  129. quirework/text/__init__.py +1 -0
  130. quirework/text/jargon.py +545 -0
  131. quirework/text/mdwrap.py +1001 -0
  132. quirework/text/tells.py +547 -0
  133. quirework-0.1.0.dist-info/METADATA +444 -0
  134. quirework-0.1.0.dist-info/RECORD +138 -0
  135. quirework-0.1.0.dist-info/WHEEL +5 -0
  136. quirework-0.1.0.dist-info/entry_points.txt +2 -0
  137. quirework-0.1.0.dist-info/licenses/LICENSE +21 -0
  138. quirework-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,92 @@
1
+ """Verify image identities in native session ancestry."""
2
+
3
+ import base64
4
+ import hashlib
5
+ import json
6
+ from pathlib import Path
7
+
8
+ # Claude Code's records of the requests it sent, not turns of the dialogue.
9
+ # A blob stores a request's messages by hash, so when the client retries a
10
+ # stream that was cut off, the retried turn's images appear in a blob as
11
+ # well as in the user row that delivered them. Counting both reports a
12
+ # figure the reader was shown once as shown twice.
13
+ REQUEST_RECORDS = frozenset({"api-request", "api-request-shape", "api-request-blob"})
14
+
15
+
16
+ def _agy_media_hashes(transcript, rows):
17
+ """Hash the images an agy session was shown, from the copies agy keeps.
18
+
19
+ agy records an image it showed the model as a step's `media`, naming a
20
+ file it copied into the session's own directory, beside the transcript's
21
+ `.system_generated/logs/`. The name is the path inside the jail, so only
22
+ the part from `brain/` on is kept and resolved against this session's
23
+ directory on the host. A copy that is missing is not counted: the check
24
+ it feeds then reports a shortfall rather than proof.
25
+ """
26
+ if "step_index" not in (rows[0] if rows else {}):
27
+ return []
28
+ brain = transcript.parents[3]
29
+ hashes = []
30
+ for row in rows:
31
+ if row.get("status") != "DONE":
32
+ continue
33
+ for item in row.get("media") or []:
34
+ uri = str(item.get("uri", "")) if isinstance(item, dict) else ""
35
+ parts = Path(uri).parts
36
+ if "brain" not in parts:
37
+ continue
38
+ copy = brain.joinpath(*parts[parts.index("brain") + 1 :]).resolve()
39
+ if copy.is_file() and copy.is_relative_to(brain.resolve()):
40
+ hashes.append(hashlib.sha256(copy.read_bytes()).hexdigest())
41
+ return hashes
42
+
43
+
44
+ def history_image_hashes(rollout, home, seen=None):
45
+ """Audit persisted image bytes, following Codex's native parent references.
46
+
47
+ Recent Codex forks store new turns plus a parent reference, whereas Claude
48
+ copies its earlier messages into the child transcript. Neither shape may
49
+ be mistaken for evidence that the reader lost its earlier pages. agy
50
+ stores no image in its transcript at all, only a copy beside it.
51
+ """
52
+ path = Path(rollout)
53
+ seen = set() if seen is None else seen
54
+ if path in seen:
55
+ raise RuntimeError("cycle in native fork history")
56
+ seen.add(path)
57
+ rows = [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
58
+ hashes = []
59
+
60
+ def visit(value):
61
+ if isinstance(value, dict):
62
+ if value.get("type") == "input_image":
63
+ url = value.get("image_url", "")
64
+ if not url.startswith("data:image/") or ";base64," not in url:
65
+ raise RuntimeError("persisted image is not self-contained")
66
+ hashes.append(
67
+ hashlib.sha256(base64.b64decode(url.split(",", 1)[1])).hexdigest()
68
+ )
69
+ elif value.get("type") == "image" and isinstance(value.get("source"), dict):
70
+ source = value["source"]
71
+ if source.get("type") != "base64":
72
+ raise RuntimeError("persisted Claude image is not self-contained")
73
+ hashes.append(
74
+ hashlib.sha256(base64.b64decode(source["data"])).hexdigest()
75
+ )
76
+ for child in value.values():
77
+ visit(child)
78
+ elif isinstance(value, list):
79
+ for child in value:
80
+ visit(child)
81
+
82
+ visit([row for row in rows if row.get("type") not in REQUEST_RECORDS])
83
+ hashes.extend(_agy_media_hashes(path, rows))
84
+ for row in rows:
85
+ if row.get("type") == "session_meta":
86
+ parent = row["payload"].get("forked_from_id")
87
+ if parent:
88
+ parents = list((Path(home) / "sessions").glob(f"**/*{parent}.jsonl"))
89
+ if len(parents) != 1:
90
+ raise RuntimeError("fork history has a missing or ambiguous parent")
91
+ hashes.extend(history_image_hashes(parents[0], home, seen))
92
+ return hashes