cc-transcript 14.8.0__tar.gz → 14.9.0__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 (157) hide show
  1. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/PKG-INFO +1 -1
  2. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/__init__.py +9 -1
  3. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/__init__.pyi +4 -0
  4. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/query.py +137 -11
  5. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/tools.py +1 -0
  6. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/pyproject.toml +1 -1
  7. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/toolcall.rs +16 -0
  8. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/Cargo.lock +0 -0
  9. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/Cargo.toml +0 -0
  10. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/LICENSE +0 -0
  11. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/README.md +0 -0
  12. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/_native.pyi +0 -0
  13. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/activity.py +0 -0
  14. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/activity_probe.py +0 -0
  15. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/builders.py +0 -0
  16. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/codex.py +0 -0
  17. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/command.py +0 -0
  18. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/context.py +0 -0
  19. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/corrections.py +0 -0
  20. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/cost.py +0 -0
  21. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/decisions.py +0 -0
  22. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/discovery.py +0 -0
  23. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/disktruth.py +0 -0
  24. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/evidence.py +0 -0
  25. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/extract/__init__.py +0 -0
  26. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/extract/correct.py +0 -0
  27. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/facts.py +0 -0
  28. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/filterspec.py +0 -0
  29. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/heartbeats.py +0 -0
  30. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/ids.py +0 -0
  31. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/judge/__init__.py +0 -0
  32. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/judge/llm.py +0 -0
  33. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/judge/similar.py +0 -0
  34. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/judge/verdicts.py +0 -0
  35. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/ledger.py +0 -0
  36. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/literals.py +0 -0
  37. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/mining/__init__.py +0 -0
  38. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/mining/candidates.py +0 -0
  39. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/mining/confidence.py +0 -0
  40. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/mining/engine.py +0 -0
  41. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/mining/formats.py +0 -0
  42. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/mining/sampling.py +0 -0
  43. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/mining/signals.py +0 -0
  44. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/mining/sourcekind.py +0 -0
  45. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/mining/spec.py +0 -0
  46. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/mining/store.py +0 -0
  47. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/models.py +0 -0
  48. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/nlp.py +0 -0
  49. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/notifications.py +0 -0
  50. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/parser.py +0 -0
  51. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/py.typed +0 -0
  52. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/render.py +0 -0
  53. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/sentiment/__init__.py +0 -0
  54. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/sentiment/buckets.py +0 -0
  55. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/sentiment/data/afinn-en-165.tsv +0 -0
  56. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/sentiment/data/domain_overrides.tsv +0 -0
  57. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/sentiment/data/en-ewt.udpipe +0 -0
  58. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/sentiment/engine.py +0 -0
  59. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/sentiment/lexicon.py +0 -0
  60. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/sentiment/scorespec.py +0 -0
  61. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/synthetic.py +0 -0
  62. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/cc_transcript/watch.py +0 -0
  63. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/Cargo.toml +0 -0
  64. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/corrections.rs +0 -0
  65. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/digest.rs +0 -0
  66. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/facts.rs +0 -0
  67. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/grep.rs +0 -0
  68. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/list.rs +0 -0
  69. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/mod.rs +0 -0
  70. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/scratchpad.rs +0 -0
  71. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/show.rs +0 -0
  72. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/slice.rs +0 -0
  73. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/stats.rs +0 -0
  74. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/commands/watch.rs +0 -0
  75. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/lib.rs +0 -0
  76. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/main.rs +0 -0
  77. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/output.rs +0 -0
  78. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/cli/src/target.rs +0 -0
  79. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/Cargo.toml +0 -0
  80. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/benches/parse.rs +0 -0
  81. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/benches/render.rs +0 -0
  82. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/benches/toolcall.rs +0 -0
  83. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/activity.rs +0 -0
  84. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/actor.rs +0 -0
  85. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/buckets.rs +0 -0
  86. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/codex/discovery.rs +0 -0
  87. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/codex/lower.rs +0 -0
  88. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/codex/mod.rs +0 -0
  89. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/codex/parse.rs +0 -0
  90. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/codex/probe.rs +0 -0
  91. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/codex/protocol.rs +0 -0
  92. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/codex/types.rs +0 -0
  93. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/command.rs +0 -0
  94. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/context.rs +0 -0
  95. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/corrections.rs +0 -0
  96. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/cost.rs +0 -0
  97. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/discovery.rs +0 -0
  98. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/facts.rs +0 -0
  99. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/feedback.rs +0 -0
  100. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/filter.rs +0 -0
  101. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/gateway.rs +0 -0
  102. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/ids.rs +0 -0
  103. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/judge.rs +0 -0
  104. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/lib.rs +0 -0
  105. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/literals/command.rs +0 -0
  106. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/literals/corrections.rs +0 -0
  107. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/literals/feedback.rs +0 -0
  108. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/literals/mining.rs +0 -0
  109. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/literals/mod.rs +0 -0
  110. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/literals/protocol.rs +0 -0
  111. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/notifications.rs +0 -0
  112. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/parse.rs +0 -0
  113. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/protocol.rs +0 -0
  114. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/pystr.rs +0 -0
  115. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/query.rs +0 -0
  116. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/render.rs +0 -0
  117. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/rng.rs +0 -0
  118. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/sqlite.rs +0 -0
  119. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/types.rs +0 -0
  120. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/value.rs +0 -0
  121. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/core/src/watch.rs +0 -0
  122. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/Cargo.toml +0 -0
  123. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/build.rs +0 -0
  124. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/data/command_prefix_pins.tsv +0 -0
  125. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/actor_bridge.rs +0 -0
  126. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/bin/stub_gen.rs +0 -0
  127. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/codex.rs +0 -0
  128. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/command.rs +0 -0
  129. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/context.rs +0 -0
  130. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/corrections.rs +0 -0
  131. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/discovery.rs +0 -0
  132. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/feedback.rs +0 -0
  133. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/judge.rs +0 -0
  134. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/lexicon.rs +0 -0
  135. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/lib.rs +0 -0
  136. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/mining.rs +0 -0
  137. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/nlp.rs +0 -0
  138. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/python.rs +0 -0
  139. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/render.rs +0 -0
  140. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/score.rs +0 -0
  141. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/sqlite.rs +0 -0
  142. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/toolcall.rs +0 -0
  143. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/attachment.rs +0 -0
  144. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/blocks.rs +0 -0
  145. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/command.rs +0 -0
  146. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/convert.rs +0 -0
  147. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/dunder.rs +0 -0
  148. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/events.rs +0 -0
  149. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/meta.rs +0 -0
  150. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/mod.rs +0 -0
  151. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/print.rs +0 -0
  152. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/store.rs +0 -0
  153. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/system.rs +0 -0
  154. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/toolcall.rs +0 -0
  155. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/toolresult.rs +0 -0
  156. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/rust/crates/py/src/views/transcript.rs +0 -0
  157. {cc_transcript-14.8.0 → cc_transcript-14.9.0}/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.8.0
3
+ Version: 14.9.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -235,7 +235,15 @@ EXPORTS: dict[str, str] = {
235
235
  ),
236
236
  "cc_transcript.render": ("Budget", "render_session", "render_tool_call", "render_turn"),
237
237
  "cc_transcript.notifications": ("Notifications",),
238
- "cc_transcript.query": ("FileRef", "Session", "SubagentIndex", "SubagentSession", "ToolCallQuery"),
238
+ "cc_transcript.query": (
239
+ "DeepSession",
240
+ "DeepView",
241
+ "FileRef",
242
+ "Session",
243
+ "SubagentIndex",
244
+ "SubagentSession",
245
+ "ToolCallQuery",
246
+ ),
239
247
  "cc_transcript.decisions": ("DECISIONS_DDL", "Action", "Decision", "DecisionLog"),
240
248
  "cc_transcript.disktruth": (
241
249
  "AttributionRange",
@@ -203,6 +203,8 @@ from cc_transcript.parser import (
203
203
  )
204
204
 
205
205
  from cc_transcript.query import (
206
+ DeepSession as DeepSession,
207
+ DeepView as DeepView,
206
208
  FileRef as FileRef,
207
209
  Session as Session,
208
210
  SubagentIndex as SubagentIndex,
@@ -335,6 +337,8 @@ __all__ = [
335
337
  "DECISIONS_DDL",
336
338
  "Decision",
337
339
  "DecisionLog",
340
+ "DeepSession",
341
+ "DeepView",
338
342
  "DiskTruth",
339
343
  "Edit",
340
344
  "EditCall",
@@ -14,13 +14,14 @@ import re
14
14
  from collections.abc import Mapping
15
15
  from dataclasses import dataclass
16
16
  from fnmatch import fnmatch
17
+ from functools import cached_property
17
18
  from pathlib import PurePath
18
19
  from typing import TYPE_CHECKING, ClassVar
19
20
 
20
21
  from cc_transcript.activity import SessionActivity, Turn, event_stamps, native_user_classifier
21
22
  from cc_transcript.discovery import TranscriptExpiredError, resolve, subagent_paths, subagent_transcripts
22
23
  from cc_transcript.filterspec import event_meta, session_id_of
23
- from cc_transcript.ids import SessionId
24
+ from cc_transcript.ids import SessionId, ToolUseId
24
25
  from cc_transcript.models import AssistantEvent, SystemEvent, ToolResultBlock, UserEvent
25
26
  from cc_transcript.notifications import Notifications
26
27
  from cc_transcript.parser import parse
@@ -41,7 +42,7 @@ if TYPE_CHECKING:
41
42
 
42
43
  from cc_transcript.activity import ToolUse, UserClassifier
43
44
  from cc_transcript.command import CommandLine
44
- from cc_transcript.ids import EventUuid, ToolUseId
45
+ from cc_transcript.ids import EventUuid
45
46
  from cc_transcript.models import TranscriptEvent
46
47
 
47
48
 
@@ -246,6 +247,9 @@ class Session:
246
247
  turns: The turns in the window.
247
248
  path: The transcript file the session was loaded from, when known —
248
249
  required for sidechain (subagent) lookups.
250
+ attachments: External transcript files (e.g. codex rollouts) registered
251
+ with this session; :meth:`walk` and :attr:`deep` fold them in at
252
+ depth 1. Empty for a session loaded straight from disk.
249
253
 
250
254
  Example:
251
255
  >>> session.prior().after(tool="Write", file=str(fp)).has_tool("ExitPlanMode")
@@ -253,11 +257,14 @@ class Session:
253
257
 
254
258
  turns: tuple[Turn, ...]
255
259
  path: Path | None = None
260
+ attachments: tuple[Path, ...] = ()
256
261
 
257
262
  @classmethod
258
- def from_activity(cls, activity: SessionActivity, *, path: Path | None = None) -> Session:
263
+ def from_activity(
264
+ cls, activity: SessionActivity, *, path: Path | None = None, attachments: tuple[Path, ...] = ()
265
+ ) -> Session:
259
266
  """Views ``activity``'s full turn range as a session."""
260
- return cls(activity.turns, path)
267
+ return cls(activity.turns, path, attachments)
261
268
 
262
269
  @classmethod
263
270
  def from_path(cls, path: Path, *, user_classifier: UserClassifier = native_user_classifier) -> Session:
@@ -326,6 +333,25 @@ class Session:
326
333
  )
327
334
  )
328
335
 
336
+ def walk(self) -> Iterator[DeepSession]:
337
+ """Every transcript reachable from this session, lazily and depth-first.
338
+
339
+ Yields each descendant sidechain (subagent/teammate) transcript at every
340
+ depth in DFS path order, then each registered attachment at depth 1 —
341
+ never this session itself. A resolved-path seen-set (seeded with
342
+ :attr:`path`) dedupes: the first occurrence of a path wins, so a
343
+ tree-discovered sidechain outranks an equal attachment, and symlink
344
+ cycles terminate. An unreadable transcript is skipped but its children
345
+ are still walked; a structurally malformed line raises, as
346
+ :attr:`subagents` does.
347
+ """
348
+ return deep_sessions(self)
349
+
350
+ @property
351
+ def deep(self) -> DeepView:
352
+ """The recursive union view over this session and every transcript it reaches."""
353
+ return DeepView(self)
354
+
329
355
  def after(self, *, tool: str, file: str | None = None) -> Session:
330
356
  """The window strictly after the last call matching ``tool``.
331
357
 
@@ -369,7 +395,7 @@ class Session:
369
395
  @property
370
396
  def current_turn(self) -> Session:
371
397
  """The one-turn view of the window's last turn."""
372
- return Session(self.turns[-1:], self.path)
398
+ return Session(self.turns[-1:], self.path, self.attachments)
373
399
 
374
400
  @property
375
401
  def user_text(self) -> str:
@@ -394,7 +420,7 @@ class Session:
394
420
  def has_tool(self, name: str, *, subagents: bool = True) -> bool:
395
421
  """Whether any call in the window matches the pipe spec ``name``."""
396
422
  return self.tool_calls.named(name).any() or (
397
- subagents and any(sub.has_tool(name) for sub in sidechain_sessions(self.path))
423
+ subagents and any(deep.session.has_tool(name, subagents=False) for deep in self.walk())
398
424
  )
399
425
 
400
426
  def has_command(self, *argv: str, subagents: bool = True) -> bool:
@@ -405,19 +431,19 @@ class Session:
405
431
  ``sudo git push -f`` and ``cd x && git push`` but not ``echo "git push"``.
406
432
  """
407
433
  return any(cmd.runs(*argv) for line in self.command_lines() for cmd in line) or (
408
- subagents and any(sub.has_command(*argv) for sub in sidechain_sessions(self.path))
434
+ subagents and any(deep.session.has_command(*argv, subagents=False) for deep in self.walk())
409
435
  )
410
436
 
411
437
  def has_edit_to(self, *globs: str, subagents: bool = True) -> bool:
412
438
  """Whether any edit-shaped call in the window targets a file matching any glob."""
413
439
  return any(file.matches(*globs) for file in self.edited_files) or (
414
- subagents and any(sub.has_edit_to(*globs) for sub in sidechain_sessions(self.path))
440
+ subagents and any(deep.session.has_edit_to(*globs, subagents=False) for deep in self.walk())
415
441
  )
416
442
 
417
443
  def has_read(self, pattern: str, *, subagents: bool = True) -> bool:
418
444
  """Whether any Read in the window targets a path containing ``pattern``."""
419
445
  return any(pattern in str(file) for file in self.tool_calls.named("Read").files()) or (
420
- subagents and any(sub.has_read(pattern) for sub in sidechain_sessions(self.path))
446
+ subagents and any(deep.session.has_read(pattern, subagents=False) for deep in self.walk())
421
447
  )
422
448
 
423
449
  def has_skill(self, *names: str, subagents: bool = True) -> bool:
@@ -425,7 +451,7 @@ class Session:
425
451
  return any(
426
452
  isinstance(call := use.call, SkillCall) and call.skill in names
427
453
  for use in self.tool_calls.named("Skill")
428
- ) or (subagents and any(sub.has_skill(*names) for sub in sidechain_sessions(self.path)))
454
+ ) or (subagents and any(deep.session.has_skill(*names, subagents=False) for deep in self.walk()))
429
455
 
430
456
  def has_override(self, token: str, *, invalidated_by: Sequence[str] = ("Edit", "Write")) -> bool:
431
457
  """Whether ``token`` appears in the window without a later invalidating call.
@@ -530,6 +556,69 @@ class SubagentIndex:
530
556
  return bool(self.items)
531
557
 
532
558
 
559
+ @dataclass(frozen=True, slots=True)
560
+ class DeepSession:
561
+ """One transcript reached by :meth:`Session.walk`.
562
+
563
+ Attributes:
564
+ session: The whole-session view of the reached transcript.
565
+ path: The transcript file it was loaded from.
566
+ provider: Its source provider, ``"claude"`` or ``"codex"``.
567
+ depth: Distance from the root; ``1`` is a direct sidechain or attachment.
568
+ spawned_by: The dispatching tool-use id parsed from an ``agent-<id>``
569
+ sidechain stem, or None for an attachment.
570
+ """
571
+
572
+ session: Session
573
+ path: Path
574
+ provider: str
575
+ depth: int
576
+ spawned_by: ToolUseId | None
577
+
578
+
579
+ @dataclass(frozen=True) # non-slots: the cached_property below needs __dict__
580
+ class DeepView:
581
+ """The recursive union of a session and every transcript reachable from it.
582
+
583
+ Sidechain (subagent/teammate) transcripts at every depth and registered
584
+ attachments contribute their tool calls and events to one window-spanning
585
+ view. The root axis respects the session's window; descendants and
586
+ attachments are window-invariant, mirroring how ``has_tool`` already scans
587
+ the whole sidechain tree.
588
+
589
+ Example:
590
+ >>> session.deep.tool_calls.named("Edit|Write").files()
591
+ """
592
+
593
+ root: Session
594
+
595
+ @cached_property
596
+ def sessions(self) -> tuple[DeepSession, ...]:
597
+ """Every reached :class:`DeepSession`, materialized once: DFS, then attachments."""
598
+ return tuple(self.root.walk())
599
+
600
+ @property
601
+ def tool_calls(self) -> ToolCallQuery:
602
+ """The root window's calls, then every descendant's and attachment's calls.
603
+
604
+ Positional, not chronological: root-window order, then DFS path order,
605
+ then attachment registration order — so :meth:`ToolCallQuery.first` and
606
+ :meth:`ToolCallQuery.last` read positionally.
607
+ """
608
+ return ToolCallQuery(
609
+ self.root.tool_calls.all_items
610
+ + tuple(use for deep in self.sessions for use in deep.session.tool_calls.all_items)
611
+ )
612
+
613
+ @property
614
+ def events(self) -> tuple[TranscriptEvent, ...]:
615
+ """Every event across the root window and every reached transcript, in walk order."""
616
+ return self.root.events + tuple(event for deep in self.sessions for event in deep.session.events)
617
+
618
+ def __iter__(self) -> Iterator[DeepSession]:
619
+ return iter(self.sessions)
620
+
621
+
533
622
  def windowed(session: Session, start: int, stop: int) -> Session:
534
623
  turns: list[Turn] = []
535
624
  base = 0
@@ -539,10 +628,47 @@ def windowed(session: Session, start: int, stop: int) -> Session:
539
628
  if lo < hi:
540
629
  turns.append(turn if (lo, hi) == (0, size) else trim_turn(turn, lo, hi))
541
630
  base += size
542
- return Session(tuple(turns), session.path)
631
+ return Session(tuple(turns), session.path, session.attachments)
543
632
 
544
633
 
545
634
  def sidechain_sessions(path: Path | None) -> tuple[Session, ...]:
546
635
  if path is None:
547
636
  return ()
548
637
  return tuple(Session.from_path(entry) for entry in subagent_paths(path))
638
+
639
+
640
+ def deep_sessions(root: Session) -> Iterator[DeepSession]:
641
+ seen: set[Path] = {root.path.resolve()} if root.path is not None else set()
642
+ if root.path is not None:
643
+ yield from descend_sidechains(root.path, 1, seen)
644
+ for attachment in root.attachments:
645
+ yield from visit_transcript(attachment, 1, None, seen)
646
+
647
+
648
+ def descend_sidechains(parent: Path, depth: int, seen: set[Path]) -> Iterator[DeepSession]:
649
+ for child in subagent_paths(parent):
650
+ yield from visit_transcript(child, depth, ToolUseId(child.stem.removeprefix("agent-")), seen)
651
+
652
+
653
+ def visit_transcript(path: Path, depth: int, spawned_by: ToolUseId | None, seen: set[Path]) -> Iterator[DeepSession]:
654
+ if (resolved := path.resolve()) in seen:
655
+ return
656
+ seen.add(resolved)
657
+ if (deep := load_deep_session(path, depth, spawned_by)) is not None:
658
+ yield deep
659
+ yield from descend_sidechains(path, depth + 1, seen)
660
+
661
+
662
+ def load_deep_session(path: Path, depth: int, spawned_by: ToolUseId | None) -> DeepSession | None:
663
+ try:
664
+ transcript = parse(path)
665
+ except OSError:
666
+ return None
667
+ session_id = session_id_of(transcript.events) or SessionId(path.stem)
668
+ return DeepSession(
669
+ session=Session.from_activity(SessionActivity.from_events(session_id, transcript.events), path=path),
670
+ path=path,
671
+ provider=transcript.provider,
672
+ depth=depth,
673
+ spawned_by=spawned_by,
674
+ )
@@ -74,6 +74,7 @@ if TYPE_CHECKING:
74
74
 
75
75
  TOOL_ALIASES: dict[str, str] = {
76
76
  "Bash": "Execute",
77
+ "Edit": "apply_patch",
77
78
  "Write": "Create",
78
79
  "Agent": "Task",
79
80
  "WebFetch": "FetchUrl",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cc-transcript"
3
- version = "14.8.0" # informational — releases stamp the version from the git tag
3
+ version = "14.9.0" # 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"
@@ -22,6 +22,8 @@ use crate::value::{field, field_str, normalized_owned};
22
22
  fn tool_aliases(name: &str) -> &'static [&'static str] {
23
23
  match name {
24
24
  "Bash" => &["Execute", "exec_command"],
25
+ // forward-only: a reverse entry would rekey apply_patch's parse dispatch (see below).
26
+ "Edit" => &["apply_patch"],
25
27
  "Write" => &["Create"],
26
28
  "Agent" => &["Task"],
27
29
  "WebFetch" => &["FetchUrl"],
@@ -1850,6 +1852,20 @@ mod tests {
1850
1852
  assert!(tool_name_matches("exec_command", "Execute"));
1851
1853
  }
1852
1854
 
1855
+ #[test]
1856
+ fn edit_forward_aliases_to_apply_patch_without_rekeying_parse() {
1857
+ let set = expand_tool_names("Edit");
1858
+ assert!(set.contains("Edit") && set.contains("apply_patch"));
1859
+ assert!(tool_name_matches("apply_patch", "Edit|Write"));
1860
+ assert!(tool_name_matches("apply_patch", "Edit"));
1861
+ // Forward-only: a reverse entry would route apply_patch to edit_from_raw.
1862
+ let envelope = "*** Begin Patch\n*** Update File: a.py\n@@\n-x\n+y\n*** End Patch\n";
1863
+ assert!(matches!(
1864
+ parse_tool_call("apply_patch", &str_val(envelope)),
1865
+ ToolCall::ApplyPatch(_)
1866
+ ));
1867
+ }
1868
+
1853
1869
  #[test]
1854
1870
  fn exec_string_parses_as_code_mode() {
1855
1871
  let src = "python3 -c 'print(1)'";
File without changes
File without changes