cc-transcript 14.45.12__tar.gz → 14.45.13__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 (198) hide show
  1. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/PKG-INFO +1 -1
  2. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/pyproject.toml +1 -1
  3. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot.rs +160 -55
  4. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_activity.rs +160 -0
  5. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_ledger_tests.rs +71 -10
  6. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_prepared_service.rs +16 -12
  7. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_projection.rs +81 -60
  8. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_root_warm.rs +2 -2
  9. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/Cargo.lock +0 -0
  10. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/Cargo.toml +0 -0
  11. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/LICENSE +0 -0
  12. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/README.md +0 -0
  13. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/__init__.py +0 -0
  14. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/__init__.pyi +0 -0
  15. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/_native.pyi +0 -0
  16. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/activity.py +0 -0
  17. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/activity_probe.py +0 -0
  18. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/builders.py +0 -0
  19. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/codex.py +0 -0
  20. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/command.py +0 -0
  21. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/context.py +0 -0
  22. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/corrections.py +0 -0
  23. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/cost.py +0 -0
  24. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/decision_store.py +0 -0
  25. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/decisions.py +0 -0
  26. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/discovery.py +0 -0
  27. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/disktruth.py +0 -0
  28. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/evidence.py +0 -0
  29. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/extract/__init__.py +0 -0
  30. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/extract/correct.py +0 -0
  31. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/facts.py +0 -0
  32. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/filterspec.py +0 -0
  33. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/heartbeats.py +0 -0
  34. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/ids.py +0 -0
  35. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/judge/__init__.py +0 -0
  36. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/judge/llm.py +0 -0
  37. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/judge/similar.py +0 -0
  38. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/judge/verdicts.py +0 -0
  39. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/ledger.py +0 -0
  40. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/literals.py +0 -0
  41. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/mining/__init__.py +0 -0
  42. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/mining/candidates.py +0 -0
  43. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/mining/confidence.py +0 -0
  44. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/mining/engine.py +0 -0
  45. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/mining/formats.py +0 -0
  46. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/mining/sampling.py +0 -0
  47. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/mining/signals.py +0 -0
  48. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/mining/sourcekind.py +0 -0
  49. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/mining/spec.py +0 -0
  50. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/mining/store.py +0 -0
  51. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/models.py +0 -0
  52. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/nlp.py +0 -0
  53. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/notifications.py +0 -0
  54. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/parser.py +0 -0
  55. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/py.typed +0 -0
  56. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/query.py +0 -0
  57. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/render.py +0 -0
  58. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/sentiment/__init__.py +0 -0
  59. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/sentiment/buckets.py +0 -0
  60. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/sentiment/data/afinn-en-165.tsv +0 -0
  61. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/sentiment/data/domain_overrides.tsv +0 -0
  62. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/sentiment/data/en-ewt.udpipe +0 -0
  63. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/sentiment/engine.py +0 -0
  64. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/sentiment/lexicon.py +0 -0
  65. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/sentiment/scorespec.py +0 -0
  66. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/snapshot_schema/config.schema.json +0 -0
  67. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/snapshot_schema/context.schema.json +0 -0
  68. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/snapshot_schema/request.schema.json +0 -0
  69. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/snapshot_schema/response.schema.json +0 -0
  70. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/snapshot_schema/tool_registry.schema.json +0 -0
  71. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/snapshots.py +0 -0
  72. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/synthetic.py +0 -0
  73. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/tools.py +0 -0
  74. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/cc_transcript/watch.py +0 -0
  75. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/Cargo.toml +0 -0
  76. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/blame.rs +0 -0
  77. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/corpus.rs +0 -0
  78. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/corrections.rs +0 -0
  79. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/digest.rs +0 -0
  80. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/facts.rs +0 -0
  81. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/grep.rs +0 -0
  82. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/list.rs +0 -0
  83. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/mod.rs +0 -0
  84. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/scratchpad.rs +0 -0
  85. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/show.rs +0 -0
  86. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/slice.rs +0 -0
  87. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/stats.rs +0 -0
  88. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/commands/watch.rs +0 -0
  89. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/lib.rs +0 -0
  90. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/output.rs +0 -0
  91. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/target.rs +0 -0
  92. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/cli/src/timearg.rs +0 -0
  93. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/Cargo.toml +0 -0
  94. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/benches/parse.rs +0 -0
  95. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/benches/render.rs +0 -0
  96. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/benches/toolcall.rs +0 -0
  97. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/build.rs +0 -0
  98. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/activity.rs +0 -0
  99. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/actor.rs +0 -0
  100. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/blame.rs +0 -0
  101. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/buckets.rs +0 -0
  102. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/codex/discovery.rs +0 -0
  103. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/codex/lower.rs +0 -0
  104. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/codex/mod.rs +0 -0
  105. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/codex/parse.rs +0 -0
  106. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/codex/probe.rs +0 -0
  107. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/codex/protocol.rs +0 -0
  108. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/codex/types.rs +0 -0
  109. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/command.rs +0 -0
  110. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/context.rs +0 -0
  111. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/corrections.rs +0 -0
  112. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/cost.rs +0 -0
  113. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/discovery.rs +0 -0
  114. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/facts.rs +0 -0
  115. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/feedback.rs +0 -0
  116. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/filter.rs +0 -0
  117. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/gateway.rs +0 -0
  118. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/ids.rs +0 -0
  119. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/judge.rs +0 -0
  120. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/lib.rs +0 -0
  121. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/literals/command.rs +0 -0
  122. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/literals/corrections.rs +0 -0
  123. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/literals/feedback.rs +0 -0
  124. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/literals/mining.rs +0 -0
  125. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/literals/mod.rs +0 -0
  126. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/literals/protocol.rs +0 -0
  127. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/notifications.rs +0 -0
  128. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/parse.rs +0 -0
  129. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/protocol.rs +0 -0
  130. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/pystr.rs +0 -0
  131. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/query.rs +0 -0
  132. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/render.rs +0 -0
  133. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/rng.rs +0 -0
  134. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/scan.rs +0 -0
  135. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/scan_checkpoint.rs +0 -0
  136. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/scan_grep.rs +0 -0
  137. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/scan_grep_stream.rs +0 -0
  138. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/scan_grep_stream_tests.rs +0 -0
  139. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/scan_index.rs +0 -0
  140. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/scan_projection.rs +0 -0
  141. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/scan_regressions.rs +0 -0
  142. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/scan_stream.rs +0 -0
  143. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/schema.rs +0 -0
  144. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_codec.rs +0 -0
  145. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_codex_append.rs +0 -0
  146. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_labels.rs +0 -0
  147. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_ledger.rs +0 -0
  148. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_memory.rs +0 -0
  149. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_owned.rs +0 -0
  150. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_prepared.rs +0 -0
  151. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_prepared_disk.rs +0 -0
  152. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_regressions.rs +0 -0
  153. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/snapshot_text.rs +0 -0
  154. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/sqlite.rs +0 -0
  155. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/toolcall.rs +0 -0
  156. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/types.rs +0 -0
  157. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/value.rs +0 -0
  158. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/src/watch.rs +0 -0
  159. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/tests/sonic_arena_model.rs +0 -0
  160. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/core/tests/std_allocation_model.rs +0 -0
  161. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/Cargo.toml +0 -0
  162. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/build.rs +0 -0
  163. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/data/command_prefix_pins.tsv +0 -0
  164. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/actor_bridge.rs +0 -0
  165. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/bin/stub_gen.rs +0 -0
  166. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/codex.rs +0 -0
  167. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/command.rs +0 -0
  168. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/context.rs +0 -0
  169. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/corrections.rs +0 -0
  170. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/discovery.rs +0 -0
  171. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/feedback.rs +0 -0
  172. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/judge.rs +0 -0
  173. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/lexicon.rs +0 -0
  174. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/lib.rs +0 -0
  175. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/mining.rs +0 -0
  176. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/nlp.rs +0 -0
  177. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/python.rs +0 -0
  178. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/render.rs +0 -0
  179. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/score.rs +0 -0
  180. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/snapshot_decode.rs +0 -0
  181. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/snapshots.rs +0 -0
  182. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/sqlite.rs +0 -0
  183. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/toolcall.rs +0 -0
  184. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/attachment.rs +0 -0
  185. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/blocks.rs +0 -0
  186. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/command.rs +0 -0
  187. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/convert.rs +0 -0
  188. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/dunder.rs +0 -0
  189. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/events.rs +0 -0
  190. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/meta.rs +0 -0
  191. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/mod.rs +0 -0
  192. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/print.rs +0 -0
  193. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/store.rs +0 -0
  194. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/system.rs +0 -0
  195. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/toolcall.rs +0 -0
  196. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/toolresult.rs +0 -0
  197. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/views/transcript.rs +0 -0
  198. {cc_transcript-14.45.12 → cc_transcript-14.45.13}/rust/crates/py/src/watch.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-transcript
3
- Version: 14.45.12
3
+ Version: 14.45.13
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cc-transcript"
3
- version = "14.45.12" # informational — releases stamp the version from the git tag
3
+ version = "14.45.13" # informational — releases stamp the version from the git tag
4
4
  description = "Grep every Claude Code session you've ever run."
5
5
  readme = "README.md"
6
6
  license = "PolyForm-Noncommercial-1.0.0"
@@ -32,7 +32,9 @@ use crate::snapshot_memory::{
32
32
  use crate::snapshot_prepared::FactsNeed;
33
33
  use crate::snapshot_prepared_disk::PreparedDiskKey;
34
34
  use crate::snapshot_projection::JSON_LITERAL_OBJECT_CAPACITY;
35
- use crate::types::{Entry, SYNTHETIC_MODEL};
35
+ use crate::types::Entry;
36
+ #[cfg(test)]
37
+ use crate::types::SYNTHETIC_MODEL;
36
38
 
37
39
  pub const SCHEMA: &str = "cc-transcript.snapshot/1";
38
40
  pub const PARSER_VERSION: &str = "cc-transcript.snapshot/1";
@@ -375,23 +377,15 @@ impl TranscriptSnapshot {
375
377
  if self.window_start == 0 {
376
378
  return None;
377
379
  }
378
- self.chunks
379
- .iter()
380
- .flat_map(|chunk| chunk.entries.iter())
381
- .find_map(Entry::meta)
382
- .map(|meta| meta.timestamp.timestamp_millis())
380
+ self.activity.started_unix_ms()
383
381
  }
384
382
 
385
383
  pub fn model(&self) -> Option<&str> {
386
- self.chunks
387
- .iter()
388
- .rev()
389
- .flat_map(|chunk| chunk.entries.iter().rev())
390
- .find_map(|entry| match entry {
391
- Entry::Assistant(assistant) if assistant.model != SYNTHETIC_MODEL => {
392
- Some(assistant.model.as_str())
393
- }
394
- _ => None,
384
+ self.activity
385
+ .last_model_event()
386
+ .map(|position| match self.entry(position) {
387
+ Entry::Assistant(assistant) => assistant.model.as_str(),
388
+ _ => unreachable!("indexed model is an assistant event"),
395
389
  })
396
390
  }
397
391
 
@@ -533,11 +527,10 @@ struct Lease {
533
527
  registry: Arc<crate::toolcall::ToolRegistrySnapshot>,
534
528
  }
535
529
 
536
- struct RenewedScope {
530
+ struct LeasedScope {
537
531
  snapshot: Arc<TranscriptSnapshot>,
538
532
  classifier: Value,
539
533
  expires: u64,
540
- description_bytes: usize,
541
534
  }
542
535
 
543
536
  // Sized exactly like an acquire reply's re-serialized copy, whose bytes admit graph members.
@@ -2889,6 +2882,8 @@ pub struct NativeStore {
2889
2882
  #[cfg(test)]
2890
2883
  pub(crate) fact_builds: AtomicUsize,
2891
2884
  #[cfg(test)]
2885
+ pub(crate) description_builds: AtomicUsize,
2886
+ #[cfg(test)]
2892
2887
  pub(crate) fact_lookups: AtomicUsize,
2893
2888
  #[cfg(test)]
2894
2889
  pub(crate) fact_read_waits: AtomicUsize,
@@ -3372,6 +3367,8 @@ impl NativeStore {
3372
3367
  #[cfg(test)]
3373
3368
  fact_builds: AtomicUsize::new(0),
3374
3369
  #[cfg(test)]
3370
+ description_builds: AtomicUsize::new(0),
3371
+ #[cfg(test)]
3375
3372
  fact_lookups: AtomicUsize::new(0),
3376
3373
  #[cfg(test)]
3377
3374
  fact_read_waits: AtomicUsize::new(0),
@@ -3520,7 +3517,7 @@ impl NativeStore {
3520
3517
  deadline: u64,
3521
3518
  ) -> Result<u64, SnapshotError> {
3522
3519
  for handle in handles {
3523
- self.pin_scope(handle, context)?;
3520
+ self.pin_record(handle, context)?;
3524
3521
  }
3525
3522
  let state = self.lock_state();
3526
3523
  let mut expires = (now_ms() + self.config.ttl).min(deadline);
@@ -3955,11 +3952,9 @@ impl NativeStore {
3955
3952
  mut bounds: WorkLimits,
3956
3953
  ) -> Result<Value, SnapshotError> {
3957
3954
  cancel.check(bounds.deadline_unix_ms)?;
3958
- let (source, description) =
3959
- self.pin_scope_for_work(handle, context, bounds.deadline_unix_ms)?;
3960
- bounds.deadline_unix_ms = bounds
3961
- .deadline_unix_ms
3962
- .min(number(&description, "lease_expires_unix_ms")? as u64);
3955
+ let scope = self.renew_scope_for_work(handle, context, bounds.deadline_unix_ms)?;
3956
+ let source = scope.snapshot;
3957
+ bounds.deadline_unix_ms = bounds.deadline_unix_ms.min(scope.expires);
3963
3958
  bounds.deadline_unix_ms = bounds
3964
3959
  .deadline_unix_ms
3965
3960
  .min(now_ms() + self.config.preparation);
@@ -4983,6 +4978,29 @@ impl NativeStore {
4983
4978
 
4984
4979
  #[cfg(test)]
4985
4980
  fn audit_snapshot_allocations(snapshot: &Arc<TranscriptSnapshot>) -> Vec<(usize, usize)> {
4981
+ let started = if snapshot.window_start == 0 {
4982
+ None
4983
+ } else {
4984
+ snapshot
4985
+ .chunks
4986
+ .iter()
4987
+ .flat_map(|chunk| chunk.entries.iter())
4988
+ .find_map(Entry::meta)
4989
+ .map(|meta| meta.timestamp.timestamp_millis())
4990
+ };
4991
+ assert_eq!(snapshot.window_started_unix_ms(), started);
4992
+ let model = snapshot
4993
+ .chunks
4994
+ .iter()
4995
+ .rev()
4996
+ .flat_map(|chunk| chunk.entries.iter().rev())
4997
+ .find_map(|entry| match entry {
4998
+ Entry::Assistant(assistant) if assistant.model != SYNTHETIC_MODEL => {
4999
+ Some(assistant.model.as_str())
5000
+ }
5001
+ _ => None,
5002
+ });
5003
+ assert_eq!(snapshot.model(), model);
4986
5004
  let TranscriptSnapshot {
4987
5005
  ledger: _,
4988
5006
  id,
@@ -5863,6 +5881,8 @@ impl NativeStore {
5863
5881
  lease: &str,
5864
5882
  expires: u64,
5865
5883
  ) -> Value {
5884
+ #[cfg(test)]
5885
+ self.description_builds.fetch_add(1, Ordering::Relaxed);
5866
5886
  json!({"handle": LeaseHandle {owner_epoch: &self.owner_epoch, snapshot_id: &snapshot.id, generation: &snapshot.id, lease_id: lease},
5867
5887
  "canonical_path": snapshot.canonical_path.to_string_lossy().as_ref(),
5868
5888
  "source_id": format!("{}:{}", snapshot.stamp.identity.device, snapshot.stamp.identity.inode),
@@ -5879,8 +5899,7 @@ impl NativeStore {
5879
5899
  handle: &Value,
5880
5900
  context: &Value,
5881
5901
  ) -> Result<Arc<TranscriptSnapshot>, SnapshotError> {
5882
- self.pin_scope(handle, context)
5883
- .map(|(snapshot, _)| snapshot)
5902
+ self.pin_record(handle, context).map(|scope| scope.snapshot)
5884
5903
  }
5885
5904
 
5886
5905
  pub fn validate_scope(&self, handle: &Value, context: &Value) -> Result<(), SnapshotError> {
@@ -5893,23 +5912,36 @@ impl NativeStore {
5893
5912
  handle: &Value,
5894
5913
  context: &Value,
5895
5914
  ) -> Result<(Arc<TranscriptSnapshot>, Value), SnapshotError> {
5915
+ let scope = self.capture_scope(handle, context)?;
5916
+ let description = self.description(
5917
+ &scope.snapshot,
5918
+ &scope.classifier,
5919
+ str_field(handle, "lease_id")?,
5920
+ scope.expires,
5921
+ );
5922
+ self.authority(context, Some(&scope.snapshot.canonical_path))?;
5923
+ Ok((scope.snapshot, description))
5924
+ }
5925
+
5926
+ fn pin_record(&self, handle: &Value, context: &Value) -> Result<LeasedScope, SnapshotError> {
5927
+ let scope = self.capture_scope(handle, context)?;
5928
+ self.authority(context, Some(&scope.snapshot.canonical_path))?;
5929
+ Ok(scope)
5930
+ }
5931
+
5932
+ fn capture_scope(&self, handle: &Value, context: &Value) -> Result<LeasedScope, SnapshotError> {
5896
5933
  self.authority(context, None)?;
5897
- let (snapshot, description) = {
5934
+ let scope = {
5898
5935
  let mut state = self.lock_state();
5899
5936
  Self::prune(&mut state);
5900
5937
  let lease = self.lease(&state, handle, context)?;
5901
- (
5902
- Arc::clone(&lease.snapshot),
5903
- self.description(
5904
- &lease.snapshot,
5905
- &lease.classifier,
5906
- str_field(handle, "lease_id")?,
5907
- lease.expires,
5908
- ),
5909
- )
5938
+ LeasedScope {
5939
+ snapshot: Arc::clone(&lease.snapshot),
5940
+ classifier: lease.classifier.clone(),
5941
+ expires: lease.expires,
5942
+ }
5910
5943
  };
5911
- self.authority(context, Some(&snapshot.canonical_path))?;
5912
- Ok((snapshot, description))
5944
+ Ok(scope)
5913
5945
  }
5914
5946
 
5915
5947
  pub fn pin_scope_for_work(
@@ -5933,7 +5965,7 @@ impl NativeStore {
5933
5965
  handle: &Value,
5934
5966
  context: &Value,
5935
5967
  deadline: u64,
5936
- ) -> Result<RenewedScope, SnapshotError> {
5968
+ ) -> Result<LeasedScope, SnapshotError> {
5937
5969
  #[cfg(test)]
5938
5970
  {
5939
5971
  let hook = self.pin_hook.lock().expect("pin hook").clone();
@@ -5950,7 +5982,7 @@ impl NativeStore {
5950
5982
  "borrow deadline expired",
5951
5983
  ));
5952
5984
  }
5953
- let (snapshot, probe) = self.pin_scope(handle, context)?;
5985
+ let scope = self.pin_record(handle, context)?;
5954
5986
  let mut state = self.lock_state();
5955
5987
  let lease = self.lease(&state, handle, context)?;
5956
5988
  let expires = lease.expires.max(deadline.min(lease.absolute_deadline));
@@ -5960,11 +5992,10 @@ impl NativeStore {
5960
5992
  .get_mut(str_field(handle, "lease_id")?)
5961
5993
  .expect("validated lease")
5962
5994
  .expires = expires;
5963
- Ok(RenewedScope {
5964
- snapshot,
5995
+ Ok(LeasedScope {
5996
+ snapshot: scope.snapshot,
5965
5997
  classifier,
5966
5998
  expires,
5967
- description_bytes: value_bytes(&probe),
5968
5999
  })
5969
6000
  }
5970
6001
 
@@ -8764,6 +8795,12 @@ impl NativeStore {
8764
8795
  let claimant = str_field(context, "claimant")?;
8765
8796
  let lease_id = str_field(root_handle, "lease_id")?;
8766
8797
  let root_path = scope.snapshot.canonical_path.as_os_str().len();
8798
+ let description_bytes = value_bytes(&self.description(
8799
+ &scope.snapshot,
8800
+ &scope.classifier,
8801
+ lease_id,
8802
+ scope.expires,
8803
+ ));
8767
8804
  let mut reservation = self.reserve_projection(
8768
8805
  context,
8769
8806
  size_of::<GraphCursor>()
@@ -8773,7 +8810,7 @@ impl NativeStore {
8773
8810
  + value_bytes(root_handle)
8774
8811
  + size_of::<GraphNode>()
8775
8812
  + root_path
8776
- + scope.description_bytes
8813
+ + description_bytes
8777
8814
  + set_growth(&HashSet::<SourceIdentity>::new(), 1)
8778
8815
  + task_count * size_of::<GraphTask>()
8779
8816
  + attachment_bytes
@@ -8781,7 +8818,7 @@ impl NativeStore {
8781
8818
  )?;
8782
8819
  #[cfg(test)]
8783
8820
  self.graph_records.fetch_add(1, Ordering::Relaxed);
8784
- let RenewedScope {
8821
+ let LeasedScope {
8785
8822
  snapshot: root,
8786
8823
  classifier,
8787
8824
  expires,
@@ -9097,7 +9134,7 @@ impl NativeStore {
9097
9134
  usage: &mut [u64; 20],
9098
9135
  ) -> Result<GraphYield, SnapshotError> {
9099
9136
  cancel.check(graph.remaining.deadline_unix_ms)?;
9100
- self.pin_scope_for_work(
9137
+ self.renew_scope_for_work(
9101
9138
  &graph.root_handle,
9102
9139
  &graph.context,
9103
9140
  graph.remaining.deadline_unix_ms,
@@ -9538,12 +9575,10 @@ impl NativeStore {
9538
9575
  let handle = view
9539
9576
  .get("handle")
9540
9577
  .ok_or_else(|| invalid("missing view handle"))?;
9541
- let (snapshot, description) =
9542
- self.pin_scope_for_work(handle, context, bound.deadline_unix_ms)?;
9543
- bound.deadline_unix_ms = bound
9544
- .deadline_unix_ms
9545
- .min(number(&description, "lease_expires_unix_ms")? as u64);
9546
- if !classifier_eq(&view["classifier"], &description["classifier"])? {
9578
+ let scope = self.renew_scope_for_work(handle, context, bound.deadline_unix_ms)?;
9579
+ let snapshot = scope.snapshot;
9580
+ bound.deadline_unix_ms = bound.deadline_unix_ms.min(scope.expires);
9581
+ if !classifier_eq(&view["classifier"], &scope.classifier)? {
9547
9582
  return Err(invalid("view classifier differs from leased generation"));
9548
9583
  }
9549
9584
  let mut page = bound;
@@ -9671,16 +9706,15 @@ impl NativeStore {
9671
9706
  .ok_or_else(|| invalid("missing windows_json"))?;
9672
9707
  let mut sessions = HashMap::new();
9673
9708
  for handle in handles.iter() {
9674
- let (snapshot, description) = self.pin_scope_for_work(
9709
+ let scope = self.renew_scope_for_work(
9675
9710
  handle
9676
9711
  .get("handle")
9677
9712
  .ok_or_else(|| invalid("missing handle"))?,
9678
9713
  context,
9679
9714
  bound.deadline_unix_ms,
9680
9715
  )?;
9681
- bound.deadline_unix_ms = bound
9682
- .deadline_unix_ms
9683
- .min(number(&description, "lease_expires_unix_ms")? as u64);
9716
+ let snapshot = scope.snapshot;
9717
+ bound.deadline_unix_ms = bound.deadline_unix_ms.min(scope.expires);
9684
9718
  let session = str_field(handle, "session_id")?;
9685
9719
  if session != snapshot.session_id {
9686
9720
  return Err(invalid("hydration session differs from handle"));
@@ -16144,6 +16178,77 @@ mod tests {
16144
16178
  store.assert_conserved();
16145
16179
  }
16146
16180
 
16181
+ #[test]
16182
+ fn repeated_root_predicates_keep_scope_metadata_typed() {
16183
+ let skill = json!({"type":"assistant","uuid":"skill","sessionId":"s","timestamp":"2026-01-02T03:04:06Z","message":{"model":"m","content":[{"type":"tool_use","id":"skill","name":"Skill","input":{"skill":"skill-4"}}]}});
16184
+ let source = Source::new(&format!("{}\n{skill}\n", user_text("prompt", "ready")));
16185
+ let store = fact_budget_store(16 * 1024 * 1024);
16186
+ let owner = context("a");
16187
+ let template = acquire(&source.path);
16188
+ let root = finish(
16189
+ &store,
16190
+ store.request(&template, &owner, &Cancellation::default()),
16191
+ &owner,
16192
+ );
16193
+ let graph = finish(
16194
+ &store,
16195
+ store.request(&json!({"schema":SCHEMA,"id":"prepare","operation":"prepare_graph","view":{"handle":handle(&root),"classifier":{"id":"native","version":"1"},"selectors":[],"attachments":[]},"thread_ids":[],"roots":[],"direct_paths":[],"deadline_unix_ms":template["deadline_unix_ms"],"limits":template["limits"]}), &owner, &Cancellation::default()),
16196
+ &owner,
16197
+ );
16198
+ assert_eq!(graph["status"].as_str(), Some("ok"), "{graph:?}");
16199
+ let before = store.description_builds.load(Ordering::Relaxed);
16200
+ for index in 0..24 {
16201
+ let request = prepared_graph_request(
16202
+ &graph,
16203
+ &template,
16204
+ json!([]),
16205
+ json!({"kind":"has_skill","values":[format!("skill-{index}")],"subagents":true}),
16206
+ None,
16207
+ );
16208
+ let answer = single_step(store.request(&request, &owner, &Cancellation::default()));
16209
+ assert_eq!(value_of(&answer), Some(index == 4), "{answer:?}");
16210
+ }
16211
+ assert_eq!(store.description_builds.load(Ordering::Relaxed), before);
16212
+ let (_, description) = store
16213
+ .pin_scope_for_work(
16214
+ &handle(&root),
16215
+ &owner,
16216
+ template["deadline_unix_ms"].as_u64().unwrap(),
16217
+ )
16218
+ .unwrap();
16219
+ assert_eq!(store.description_builds.load(Ordering::Relaxed), before + 1);
16220
+ assert_eq!(
16221
+ description["classifier"],
16222
+ json!({"id":"native","version":"1"})
16223
+ );
16224
+ assert_eq!(description["model"].as_str(), Some("m"));
16225
+ assert_eq!(
16226
+ description["canonical_path"].as_str(),
16227
+ std::fs::canonicalize(&source.path).unwrap().to_str()
16228
+ );
16229
+ let later = json!({"type":"assistant","uuid":"later","sessionId":"s","timestamp":"2026-01-02T03:04:07Z","message":{"model":"later","content":[]}});
16230
+ std::fs::OpenOptions::new()
16231
+ .append(true)
16232
+ .open(&source.path)
16233
+ .unwrap()
16234
+ .write_all(format!("{later}\n").as_bytes())
16235
+ .unwrap();
16236
+ let newer = finish(
16237
+ &store,
16238
+ store.request(&acquire(&source.path), &owner, &Cancellation::default()),
16239
+ &owner,
16240
+ );
16241
+ assert_eq!(
16242
+ store.pin(&handle(&root), &owner).unwrap().model(),
16243
+ Some("m")
16244
+ );
16245
+ assert_eq!(
16246
+ store.pin(&handle(&newer), &owner).unwrap().model(),
16247
+ Some("later")
16248
+ );
16249
+ store.assert_conserved();
16250
+ }
16251
+
16147
16252
  #[test]
16148
16253
  fn selected_root_slices_answer_has_override_within_their_view() {
16149
16254
  let source = Source::new(&format!(
@@ -4,6 +4,7 @@ use std::ops::Range;
4
4
  use std::sync::Arc;
5
5
 
6
6
  use crate::activity::{lift_session_index_tail, lower_edit, Hunk, LiftedSession, ToolUse, Turn};
7
+ use crate::parse::pushed_capacity;
7
8
  #[cfg(test)]
8
9
  use crate::snapshot::NativeStore;
9
10
  #[cfg(test)]
@@ -53,12 +54,77 @@ pub(crate) struct ResultPosition {
53
54
  charge: MemoryCharge,
54
55
  }
55
56
 
57
+ #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
58
+ pub(crate) struct ActivityShape {
59
+ pub calls: usize,
60
+ pub call_name_bytes: usize,
61
+ pub paths: usize,
62
+ pub path_bytes: usize,
63
+ pub edits: usize,
64
+ pub edit_path_bytes: usize,
65
+ pub commands: usize,
66
+ pub command_bytes: usize,
67
+ pub skills: usize,
68
+ pub skill_bytes: usize,
69
+ pub prose_bytes: usize,
70
+ pub tool_name_slots: usize,
71
+ pub tool_name_bytes: usize,
72
+ }
73
+
74
+ impl ActivityShape {
75
+ fn include_entry(&mut self, entry: &Entry) {
76
+ match entry {
77
+ Entry::User(user) => {
78
+ self.prose_bytes += user.content.text_len()
79
+ + user
80
+ .tool_results()
81
+ .map(|result| result.content.len())
82
+ .sum::<usize>();
83
+ }
84
+ Entry::Assistant(assistant) => {
85
+ self.prose_bytes += crate::types::joined_text_len(&assistant.blocks);
86
+ self.tool_name_slots += pushed_capacity(entry.tool_uses().count());
87
+ self.tool_name_bytes +=
88
+ entry.tool_uses().map(|tool| tool.name.len()).sum::<usize>();
89
+ }
90
+ Entry::System(system) => {
91
+ self.prose_bytes += system.content.as_ref().map_or(0, String::len);
92
+ }
93
+ _ => {}
94
+ }
95
+ }
96
+
97
+ fn include_call(&mut self, call: &ToolCall, edits: &[(String, Vec<Hunk>)]) {
98
+ let paths = call.file_paths();
99
+ self.calls += 1;
100
+ self.call_name_bytes += call.name().len();
101
+ self.paths += paths.len();
102
+ self.path_bytes += paths.iter().map(|path| path.len()).sum::<usize>();
103
+ self.edits += edits.len();
104
+ self.edit_path_bytes += edits.iter().map(|(path, _)| path.len()).sum::<usize>();
105
+ match call {
106
+ ToolCall::Bash(bash) => {
107
+ self.commands += 1;
108
+ self.command_bytes += bash.command.len();
109
+ }
110
+ ToolCall::Skill(skill) => {
111
+ self.skills += 1;
112
+ self.skill_bytes += skill.skill.len();
113
+ }
114
+ _ => {}
115
+ }
116
+ }
117
+ }
118
+
56
119
  #[derive(Debug, Clone, Default)]
57
120
  pub struct ActivityIndex {
58
121
  entries: usize,
59
122
  turns: Arc<Vec<Arc<CachedTurn>>>,
60
123
  uuids: Arc<HashMap<String, usize>>,
61
124
  results: Arc<HashMap<String, ResultPosition>>,
125
+ shape: ActivityShape,
126
+ started_unix_ms: Option<i64>,
127
+ last_model_event: Option<usize>,
62
128
  work: ActivityWork,
63
129
  }
64
130
 
@@ -101,8 +167,12 @@ impl ActivityIndex {
101
167
  ..ActivityWork::default()
102
168
  };
103
169
  for (local, entry) in entries.iter().enumerate() {
170
+ next.shape.include_entry(entry);
104
171
  let event = first_new_index + local;
105
172
  if let Some(meta) = entry.meta() {
173
+ if next.started_unix_ms.is_none() {
174
+ next.started_unix_ms = Some(meta.timestamp.timestamp_millis());
175
+ }
106
176
  if !next.uuids.contains_key(&meta.uuid) {
107
177
  if Arc::strong_count(&next.uuids) > 1 {
108
178
  next.work.copied_index_entries += next.uuids.len();
@@ -110,6 +180,10 @@ impl ActivityIndex {
110
180
  Arc::make_mut(&mut next.uuids).insert(meta.uuid.clone(), event);
111
181
  }
112
182
  }
183
+ if matches!(entry, Entry::Assistant(assistant) if assistant.model != crate::types::SYNTHETIC_MODEL)
184
+ {
185
+ next.last_model_event = Some(event);
186
+ }
113
187
  if let Entry::User(user) = entry {
114
188
  for (ordinal, block) in user.blocks().iter().enumerate() {
115
189
  let ContentBlock::ToolResult(result) = block else {
@@ -149,6 +223,7 @@ impl ActivityIndex {
149
223
  };
150
224
  let call = parse_tool_call(&tool.name, &tool.input);
151
225
  let edits = lower_edit(&call);
226
+ next.shape.include_call(&call, &edits);
152
227
  let id = tool.id.clone();
153
228
  let accounted_bytes = size_of::<CachedCall>()
154
229
  + id.capacity()
@@ -204,6 +279,18 @@ impl ActivityIndex {
204
279
  self.entries
205
280
  }
206
281
 
282
+ pub(crate) fn shape(&self) -> ActivityShape {
283
+ self.shape
284
+ }
285
+
286
+ pub(crate) fn started_unix_ms(&self) -> Option<i64> {
287
+ self.started_unix_ms
288
+ }
289
+
290
+ pub(crate) fn last_model_event(&self) -> Option<usize> {
291
+ self.last_model_event
292
+ }
293
+
207
294
  pub fn turn_count(&self) -> usize {
208
295
  self.turns.len()
209
296
  }
@@ -436,6 +523,9 @@ impl ActivityIndex {
436
523
  turns,
437
524
  uuids,
438
525
  results,
526
+ shape: _,
527
+ started_unix_ms: _,
528
+ last_model_event: _,
439
529
  work: _,
440
530
  } = self;
441
531
  let mut allocations = vec![
@@ -1084,11 +1174,14 @@ mod tests {
1084
1174
  #[test]
1085
1175
  fn every_append_boundary_matches_full_lift() {
1086
1176
  let entries = [
1177
+ entry(r#"{"type":"mode","sessionId":"s","mode":"plan"}"#),
1087
1178
  tools(),
1088
1179
  user("u", "first"),
1089
1180
  tools(),
1090
1181
  user("v", "second"),
1091
1182
  results(),
1183
+ entry(&sonic_rs::json!({"type":"assistant","uuid":"synthetic","sessionId":"s","timestamp":"2026-01-02T03:04:08Z","message":{"model":crate::types::SYNTHETIC_MODEL,"content":[{"type":"text","text":"synthetic"}]}}).to_string()),
1184
+ user("tail", "trailing prose"),
1092
1185
  ];
1093
1186
  let refs: Vec<_> = entries.iter().collect();
1094
1187
  for boundary in 0..=refs.len() {
@@ -1096,9 +1189,69 @@ mod tests {
1096
1189
  let next = prior.append(&refs, boundary, None);
1097
1190
  parity(&next, &refs, None);
1098
1191
  parity(&prior, &refs[..boundary], None);
1192
+ assert_eq!(next.shape(), ActivityIndex::new(&refs, None).shape());
1193
+ assert_eq!(
1194
+ next.started_unix_ms(),
1195
+ refs.iter()
1196
+ .find_map(|entry| entry.meta())
1197
+ .map(|meta| meta.timestamp.timestamp_millis())
1198
+ );
1199
+ assert_eq!(
1200
+ next.last_model_event(),
1201
+ refs.iter().rposition(|entry| matches!(entry, Entry::Assistant(assistant) if assistant.model != crate::types::SYNTHETIC_MODEL))
1202
+ );
1203
+ assert_eq!(
1204
+ prior.started_unix_ms(),
1205
+ refs[..boundary]
1206
+ .iter()
1207
+ .find_map(|entry| entry.meta())
1208
+ .map(|meta| meta.timestamp.timestamp_millis())
1209
+ );
1210
+ assert_eq!(
1211
+ prior.last_model_event(),
1212
+ refs[..boundary].iter().rposition(|entry| matches!(entry, Entry::Assistant(assistant) if assistant.model != crate::types::SYNTHETIC_MODEL))
1213
+ );
1214
+ assert_eq!(
1215
+ prior.shape(),
1216
+ ActivityIndex::new(&refs[..boundary], None).shape()
1217
+ );
1099
1218
  }
1100
1219
  }
1101
1220
 
1221
+ #[test]
1222
+ fn metadata_is_local_to_each_window_and_immutable_prefix() {
1223
+ let entries = [
1224
+ sonic_rs::json!({"type":"user","uuid":"outside","sessionId":"s","timestamp":"2026-01-02T03:04:01Z","message":{"content":"outside"}}),
1225
+ sonic_rs::json!({"type":"assistant","uuid":"before","sessionId":"s","timestamp":"2026-01-02T03:04:02Z","message":{"model":"before","content":[]}}),
1226
+ sonic_rs::json!({"type":"mode","sessionId":"s","mode":"plan"}),
1227
+ sonic_rs::json!({"type":"user","uuid":"inside","sessionId":"s","timestamp":"2026-01-02T03:04:08Z","message":{"content":"inside"}}),
1228
+ sonic_rs::json!({"type":"assistant","uuid":"model","sessionId":"s","timestamp":"2026-01-02T03:04:09Z","message":{"model":"inside","content":[]}}),
1229
+ sonic_rs::json!({"type":"user","uuid":"tail","sessionId":"s","timestamp":"2026-01-02T03:04:10Z","message":{"content":"tail"}}),
1230
+ sonic_rs::json!({"type":"assistant","uuid":"after","sessionId":"s","timestamp":"2026-01-02T03:04:11Z","message":{"model":"after","content":[]}}),
1231
+ ].map(|value| entry(&value.to_string()));
1232
+ let refs: Vec<_> = entries.iter().collect();
1233
+ let global = ActivityIndex::new(&refs, None);
1234
+ for range in [0..5, 2..6, 3..6, 2..4] {
1235
+ let window = &refs[range.clone()];
1236
+ let first = window
1237
+ .iter()
1238
+ .find_map(|entry| entry.meta())
1239
+ .map(|meta| meta.timestamp.timestamp_millis());
1240
+ let model = window.iter().rposition(|entry| matches!(entry, Entry::Assistant(assistant) if assistant.model != crate::types::SYNTHETIC_MODEL));
1241
+ for boundary in 0..=window.len() {
1242
+ let prior = ActivityIndex::new(&window[..boundary], None);
1243
+ let next = prior.append(window, boundary, None);
1244
+ assert_eq!(next.started_unix_ms(), first, "{range:?} {boundary}");
1245
+ assert_eq!(next.last_model_event(), model, "{range:?} {boundary}");
1246
+ assert_eq!(prior.last_model_event(), window[..boundary].iter().rposition(|entry| matches!(entry, Entry::Assistant(assistant) if assistant.model != crate::types::SYNTHETIC_MODEL)));
1247
+ }
1248
+ }
1249
+ let window = ActivityIndex::new(&refs[2..6], None);
1250
+ assert_ne!(window.started_unix_ms(), global.started_unix_ms());
1251
+ assert_eq!(window.last_model_event(), Some(2));
1252
+ assert_eq!(global.last_model_event(), Some(6));
1253
+ }
1254
+
1102
1255
  #[test]
1103
1256
  fn custom_classifier_and_empty_append_preserve_semantics() {
1104
1257
  let entries = [user("u", "first"), tools(), user("v", "second"), results()];
@@ -1109,6 +1262,13 @@ mod tests {
1109
1262
  parity(&next, &refs, Some(&flags));
1110
1263
  let unchanged = next.append(&refs, refs.len(), Some(&flags));
1111
1264
  assert_eq!(unchanged.work(), ActivityWork::default());
1265
+ assert_eq!(unchanged.shape(), next.shape());
1266
+ assert_eq!(unchanged.started_unix_ms(), next.started_unix_ms());
1267
+ assert_eq!(unchanged.last_model_event(), next.last_model_event());
1268
+ assert_eq!(
1269
+ next.shape(),
1270
+ ActivityIndex::new(&refs, Some(&flags)).shape()
1271
+ );
1112
1272
  assert!(Arc::ptr_eq(&next.turns[0], &unchanged.turns[0]));
1113
1273
  assert!(Arc::ptr_eq(&next.uuids, &unchanged.uuids));
1114
1274
  assert!(Arc::ptr_eq(&next.results, &unchanged.results));