cc-transcript 14.18.0__tar.gz → 14.19.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.
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/PKG-INFO +1 -1
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/activity.py +4 -2
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/query.py +287 -54
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/pyproject.toml +1 -1
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/Cargo.lock +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/Cargo.toml +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/LICENSE +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/README.md +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/__init__.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/__init__.pyi +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/_native.pyi +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/activity_probe.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/builders.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/codex.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/command.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/context.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/corrections.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/cost.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/decision_store.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/decisions.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/discovery.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/disktruth.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/evidence.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/extract/__init__.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/extract/correct.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/facts.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/filterspec.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/heartbeats.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/ids.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/judge/__init__.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/judge/llm.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/judge/similar.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/judge/verdicts.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/ledger.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/literals.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/mining/__init__.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/mining/candidates.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/mining/confidence.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/mining/engine.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/mining/formats.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/mining/sampling.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/mining/signals.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/mining/sourcekind.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/mining/spec.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/mining/store.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/models.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/nlp.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/notifications.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/parser.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/py.typed +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/render.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/sentiment/__init__.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/sentiment/buckets.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/sentiment/data/afinn-en-165.tsv +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/sentiment/data/domain_overrides.tsv +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/sentiment/data/en-ewt.udpipe +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/sentiment/engine.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/sentiment/lexicon.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/sentiment/scorespec.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/synthetic.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/tools.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/watch.py +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/Cargo.toml +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/blame.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/corrections.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/digest.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/facts.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/grep.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/list.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/mod.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/scratchpad.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/show.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/slice.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/stats.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/commands/watch.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/lib.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/main.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/output.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/target.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/cli/src/timearg.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/Cargo.toml +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/benches/parse.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/benches/render.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/benches/toolcall.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/activity.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/actor.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/blame.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/buckets.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/codex/discovery.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/codex/lower.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/codex/mod.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/codex/parse.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/codex/probe.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/codex/protocol.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/codex/types.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/command.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/context.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/corrections.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/cost.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/discovery.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/facts.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/feedback.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/filter.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/gateway.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/ids.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/judge.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/lib.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/literals/command.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/literals/corrections.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/literals/feedback.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/literals/mining.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/literals/mod.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/literals/protocol.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/notifications.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/parse.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/protocol.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/pystr.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/query.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/render.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/rng.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/schema.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/sqlite.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/toolcall.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/types.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/value.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/watch.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/Cargo.toml +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/build.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/data/command_prefix_pins.tsv +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/actor_bridge.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/bin/stub_gen.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/codex.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/command.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/context.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/corrections.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/discovery.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/feedback.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/judge.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/lexicon.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/lib.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/mining.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/nlp.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/python.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/render.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/score.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/sqlite.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/toolcall.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/attachment.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/blocks.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/command.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/convert.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/dunder.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/events.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/meta.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/mod.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/print.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/store.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/system.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/toolcall.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/toolresult.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/views/transcript.rs +0 -0
- {cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/py/src/watch.rs +0 -0
|
@@ -285,8 +285,10 @@ class ActivityLift:
|
|
|
285
285
|
re-derives the session.
|
|
286
286
|
|
|
287
287
|
Feed only the events appended since the previous call: nothing checks
|
|
288
|
-
provenance, so events fed twice lift twice.
|
|
289
|
-
|
|
288
|
+
provenance, so events fed twice lift twice. The contract is append-only:
|
|
289
|
+
a cursor's transcript is assumed never rewritten behind the events it has
|
|
290
|
+
been fed, and a transcript rewritten in place, rather than appended to,
|
|
291
|
+
needs a new cursor. Claude Code and Codex transcripts satisfy it.
|
|
290
292
|
|
|
291
293
|
The classifier is read once, at construction, and must be deterministic
|
|
292
294
|
and event-only: its answer for an event has to stay the same as later
|
|
@@ -10,17 +10,19 @@ predicates compose over progressively narrower windows.
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
import os
|
|
13
14
|
import re
|
|
14
15
|
import threading
|
|
15
16
|
from collections import OrderedDict
|
|
16
17
|
from collections.abc import Mapping
|
|
17
|
-
from dataclasses import dataclass, field
|
|
18
|
+
from dataclasses import dataclass, field, replace
|
|
18
19
|
from fnmatch import fnmatch
|
|
19
20
|
from functools import cached_property
|
|
20
21
|
from pathlib import PurePath
|
|
22
|
+
from time import monotonic
|
|
21
23
|
from typing import TYPE_CHECKING, ClassVar
|
|
22
24
|
|
|
23
|
-
from cc_transcript.activity import SessionActivity, Turn, event_stamps, native_user_classifier
|
|
25
|
+
from cc_transcript.activity import ActivityLift, SessionActivity, Turn, event_stamps, native_user_classifier
|
|
24
26
|
from cc_transcript.discovery import TranscriptExpiredError, resolve, subagent_paths, subagent_transcripts
|
|
25
27
|
from cc_transcript.filterspec import event_meta, session_id_of
|
|
26
28
|
from cc_transcript.ids import SessionId, ToolUseId
|
|
@@ -45,45 +47,79 @@ if TYPE_CHECKING:
|
|
|
45
47
|
from cc_transcript.activity import ToolUse, UserClassifier
|
|
46
48
|
from cc_transcript.command import CommandLine
|
|
47
49
|
from cc_transcript.ids import EventUuid
|
|
48
|
-
from cc_transcript.models import TranscriptEvent
|
|
50
|
+
from cc_transcript.models import Transcript, TranscriptEvent
|
|
49
51
|
|
|
50
52
|
|
|
51
53
|
DEEP_LIFT_BUDGET = 1024 * 1024 * 1024
|
|
54
|
+
DEEP_LIFT_FENCE = 64
|
|
55
|
+
IDLE_SECONDS_BEFORE_RELEASE = 900
|
|
52
56
|
|
|
53
|
-
type LiftStamp = tuple[int, int, int]
|
|
57
|
+
type LiftStamp = tuple[int, int, int, int]
|
|
54
58
|
type LiftKey = tuple[Path, int, ToolUseId | None]
|
|
55
59
|
|
|
56
60
|
|
|
61
|
+
def stamp_of(stat: os.stat_result) -> LiftStamp:
|
|
62
|
+
return (stat.st_size, stat.st_mtime_ns, stat.st_ctime_ns, stat.st_ino)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True, slots=True)
|
|
66
|
+
class DeepLift:
|
|
67
|
+
stamp: LiftStamp
|
|
68
|
+
deep: DeepSession
|
|
69
|
+
lift: ActivityLift
|
|
70
|
+
consumed: int
|
|
71
|
+
fence: bytes
|
|
72
|
+
found: SessionId | None
|
|
73
|
+
provider: str | None
|
|
74
|
+
grown_at: float
|
|
75
|
+
|
|
76
|
+
|
|
57
77
|
@dataclass(slots=True)
|
|
58
78
|
class LiftCache:
|
|
59
|
-
held: OrderedDict[LiftKey,
|
|
79
|
+
held: OrderedDict[LiftKey, DeepLift] = field(default_factory=OrderedDict)
|
|
60
80
|
size: int = 0
|
|
61
81
|
guard: threading.Lock = field(default_factory=threading.Lock)
|
|
62
82
|
|
|
63
|
-
def get(self, key: LiftKey, stamp: LiftStamp) ->
|
|
83
|
+
def get(self, key: LiftKey, stamp: LiftStamp) -> DeepLift | None:
|
|
64
84
|
with self.guard:
|
|
65
85
|
match self.held.get(key):
|
|
66
|
-
case
|
|
67
|
-
return None
|
|
68
|
-
case (held_stamp, deep) if held_stamp == stamp:
|
|
86
|
+
case DeepLift(stamp=held_stamp) as lift if held_stamp == stamp:
|
|
69
87
|
self.held.move_to_end(key)
|
|
70
|
-
return
|
|
88
|
+
return lift
|
|
71
89
|
case _:
|
|
72
|
-
self.drop(key)
|
|
73
90
|
return None
|
|
74
91
|
|
|
75
|
-
def
|
|
92
|
+
def idle_release(self, key: LiftKey, stamp: LiftStamp) -> None:
|
|
93
|
+
"""Releases ``key``'s cursor once its file has gone :data:`IDLE_SECONDS_BEFORE_RELEASE` unwritten.
|
|
94
|
+
|
|
95
|
+
A held cursor amortizes the next growth of a live sidechain; a finished one earns
|
|
96
|
+
nothing. Expiry is elapsed inactivity, not a hit count, so a burst of predicate
|
|
97
|
+
traffic over a cursor that just grew never releases it — the clock is read from the
|
|
98
|
+
entry's last growth. Any growth replaces the entry, resetting ``grown_at``.
|
|
99
|
+
"""
|
|
100
|
+
with self.guard:
|
|
101
|
+
match self.held.get(key):
|
|
102
|
+
case DeepLift(stamp=held_stamp, grown_at=at) if held_stamp == stamp:
|
|
103
|
+
if monotonic() - at >= IDLE_SECONDS_BEFORE_RELEASE:
|
|
104
|
+
self.drop(key)
|
|
105
|
+
|
|
106
|
+
def take(self, key: LiftKey) -> DeepLift | None:
|
|
107
|
+
with self.guard:
|
|
108
|
+
return self.drop(key)
|
|
109
|
+
|
|
110
|
+
def put(self, key: LiftKey, lift: DeepLift) -> DeepLift:
|
|
76
111
|
with self.guard:
|
|
77
112
|
self.drop(key)
|
|
78
|
-
self.held[key] =
|
|
79
|
-
self.size += stamp[0]
|
|
113
|
+
self.held[key] = lift
|
|
114
|
+
self.size += lift.stamp[0]
|
|
80
115
|
while self.size > DEEP_LIFT_BUDGET:
|
|
81
116
|
self.drop(next(iter(self.held)))
|
|
82
|
-
return
|
|
117
|
+
return lift
|
|
83
118
|
|
|
84
|
-
def drop(self, key: LiftKey) -> None:
|
|
85
|
-
if (
|
|
86
|
-
self.size -=
|
|
119
|
+
def drop(self, key: LiftKey) -> DeepLift | None:
|
|
120
|
+
if (lift := self.held.pop(key, None)) is not None:
|
|
121
|
+
self.size -= lift.stamp[0]
|
|
122
|
+
return lift
|
|
87
123
|
|
|
88
124
|
def clear(self) -> None:
|
|
89
125
|
with self.guard:
|
|
@@ -95,14 +131,51 @@ class LiftCache:
|
|
|
95
131
|
|
|
96
132
|
|
|
97
133
|
DEEP_LIFTS = LiftCache()
|
|
98
|
-
"""Lifted sidechain transcripts, keyed by tree position and stamped
|
|
134
|
+
"""Lifted sidechain transcripts, keyed by tree position and stamped ``(size, mtime_ns, ctime_ns, inode)``.
|
|
99
135
|
|
|
100
136
|
A least-recently-used hold bounded at :data:`DEEP_LIFT_BUDGET` source bytes. One resident
|
|
101
137
|
process serves many lead sessions, so the tree walked most recently stays held while an
|
|
102
|
-
idle lead's sidechains age out
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
138
|
+
idle lead's sidechains age out. A lifted session costs about 2.3 times its source bytes in
|
|
139
|
+
resident memory, so the default holds roughly 2.3 GiB. A single tree larger than the budget
|
|
140
|
+
reparses the part that no longer fits on every walk.
|
|
141
|
+
|
|
142
|
+
Each entry keeps the :class:`~cc_transcript.activity.ActivityLift` cursor behind its lift and
|
|
143
|
+
the byte offset it has consumed, which always ends on a newline. A Claude transcript that has
|
|
144
|
+
only grown — same inode, larger, its last held bytes unchanged — is extended by its appended
|
|
145
|
+
lines alone: the tail past the last newline is parsed for the session but never fed to the
|
|
146
|
+
cursor. Only Claude is extended incrementally, because its parse is line-compositional; a
|
|
147
|
+
Codex rollout lowers against whole-session context (echo sets, the model timeline), so its
|
|
148
|
+
provider is pinned on the cold parse and any growth cold-relifts, as does a growth whose
|
|
149
|
+
appended chunk parses as a different provider than the file was pinned to. An empty file pins
|
|
150
|
+
no provider until a line lands, extending while its appended lines parse as Claude and
|
|
151
|
+
cold-relifting the moment a Codex line completes. A shrink, a replaced or rewritten file, a
|
|
152
|
+
short read, or a session id that surfaces only in the appended lines lifts the file afresh.
|
|
153
|
+
|
|
154
|
+
The contract is append-only: a held transcript is assumed never rewritten behind its consumed
|
|
155
|
+
offset. Growth is recognized by the same inode, a larger size, and the last
|
|
156
|
+
:data:`DEEP_LIFT_FENCE` consumed bytes still in place, with the descriptor's stat rechecked
|
|
157
|
+
around the read; a shrink, a replaced inode, a same-size rewrite, or a fence mismatch triggers
|
|
158
|
+
a cold relift. An in-place rewrite of earlier bytes that keeps the fence and grows the file is
|
|
159
|
+
not detected and is folded in as growth. Claude Code and Codex transcripts satisfy the contract.
|
|
160
|
+
Every read validates the open descriptor's stat after reading against the stat that routed it
|
|
161
|
+
— a replacement between the routing stat and the read moves the inode and ctime — so a growth
|
|
162
|
+
that reads spliced bytes falls to a cold relift, and a cold read whose descriptor moved under
|
|
163
|
+
it answers the request but is published to no cache — not here, not :data:`SIDECHAIN_INPUTS`,
|
|
164
|
+
not :data:`UNREADABLE` — and every publication is keyed by the descriptor stamp the read
|
|
165
|
+
verified rather than the routing stat. The cursor is mutable, so a stamp miss takes the
|
|
166
|
+
entry out of the hold before extending it and puts the grown entry back: a concurrent reader
|
|
167
|
+
never sees a cursor mid-extension, and two threads that miss together each produce a correct
|
|
168
|
+
entry, the later put replacing the earlier. A dropped entry releases its cursor with it.
|
|
169
|
+
|
|
170
|
+
:meth:`Session.walk` holds every transcript it reaches; the ``has_*`` predicates hold only a
|
|
171
|
+
transcript they have had to lift twice, since a sidechain that changed once is a running
|
|
172
|
+
subagent whose next growth would otherwise cost a whole relift, while a finished one is
|
|
173
|
+
lifted once and its :class:`PredicateInputs` kept instead. A held cursor is released once its
|
|
174
|
+
file has gone :data:`IDLE_SECONDS_BEFORE_RELEASE` unwritten — elapsed inactivity since its
|
|
175
|
+
last growth, not a count of walks, so aggregate predicate traffic from many readers never
|
|
176
|
+
expires a cursor that just grew — so resident cursors track the set of sidechains still being
|
|
177
|
+
written rather than every one that ever grew; a released file that grows again pays one relift
|
|
178
|
+
and is re-admitted. :data:`DEEP_LIFT_BUDGET` stays the upper bound over that live set.
|
|
106
179
|
"""
|
|
107
180
|
|
|
108
181
|
|
|
@@ -131,6 +204,37 @@ class PredicateInputsCache:
|
|
|
131
204
|
self.held.popitem(last=False)
|
|
132
205
|
return inputs
|
|
133
206
|
|
|
207
|
+
def holds(self, path: Path) -> bool:
|
|
208
|
+
with self.guard:
|
|
209
|
+
return path in self.held
|
|
210
|
+
|
|
211
|
+
def clear(self) -> None:
|
|
212
|
+
with self.guard:
|
|
213
|
+
self.held.clear()
|
|
214
|
+
|
|
215
|
+
def __len__(self) -> int:
|
|
216
|
+
return len(self.held)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass(slots=True)
|
|
220
|
+
class StampSet:
|
|
221
|
+
held: OrderedDict[Path, LiftStamp] = field(default_factory=OrderedDict)
|
|
222
|
+
guard: threading.Lock = field(default_factory=threading.Lock)
|
|
223
|
+
|
|
224
|
+
def has(self, path: Path, stamp: LiftStamp) -> bool:
|
|
225
|
+
with self.guard:
|
|
226
|
+
if self.held.get(path) != stamp:
|
|
227
|
+
return False
|
|
228
|
+
self.held.move_to_end(path)
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def put(self, path: Path, stamp: LiftStamp) -> None:
|
|
232
|
+
with self.guard:
|
|
233
|
+
self.held[path] = stamp
|
|
234
|
+
self.held.move_to_end(path)
|
|
235
|
+
while len(self.held) > SIDECHAIN_INPUTS_LIMIT:
|
|
236
|
+
self.held.popitem(last=False)
|
|
237
|
+
|
|
134
238
|
def clear(self) -> None:
|
|
135
239
|
with self.guard:
|
|
136
240
|
self.held.clear()
|
|
@@ -149,6 +253,21 @@ process re-answering the same predicates per event neither reparses nor holds th
|
|
|
149
253
|
"""
|
|
150
254
|
|
|
151
255
|
|
|
256
|
+
UNREADABLE = StampSet()
|
|
257
|
+
"""Transcripts whose typed parse failed, keyed by path and stamped like :data:`DEEP_LIFTS`.
|
|
258
|
+
|
|
259
|
+
A sidechain carrying one line the typed parser rejects — schema drift Claude Code has not
|
|
260
|
+
caught up to — cannot be lifted, so a walk skips it. Without this a resident process re-read
|
|
261
|
+
and re-parsed the whole file on every walk forever. Only a deterministic parse failure
|
|
262
|
+
(:class:`UnparseableTranscript`) is held, never a transient filesystem error, which must
|
|
263
|
+
retry. The failure is held by full stamp — the stamp of the descriptor the rejected bytes were
|
|
264
|
+
read from, never the stat that routed the read, and only when that read settled — so an
|
|
265
|
+
unchanged bad file is skipped at stamp-check cost, any change — a growth that completes the
|
|
266
|
+
line, a rewrite — clears the miss and retries, and a file that was swapped under the read is
|
|
267
|
+
never held against the file the path names next time.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
|
|
152
271
|
SIDECHAIN_LISTINGS: OrderedDict[Path, tuple[tuple[int, int], tuple[tuple[Path, bool], ...]]] = OrderedDict()
|
|
153
272
|
"""Each non-empty ``subagents`` listing and whether each file is a symlink, stamped ``(inode, mtime_ns)``.
|
|
154
273
|
|
|
@@ -465,13 +584,16 @@ class Session:
|
|
|
465
584
|
never this session itself. A resolved-path seen-set (seeded with
|
|
466
585
|
:attr:`path`) dedupes: the first occurrence of a path wins, so a
|
|
467
586
|
tree-discovered sidechain outranks an equal attachment, and symlink
|
|
468
|
-
cycles terminate. An unreadable transcript is skipped
|
|
469
|
-
are still walked
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
or a resident process that re-walks per event —
|
|
587
|
+
cycles terminate. An unreadable or unparseable transcript is skipped
|
|
588
|
+
but its children are still walked.
|
|
589
|
+
|
|
590
|
+
Each reached transcript is parsed and lifted once per ``(size, mtime,
|
|
591
|
+
ctime, inode)`` stamp and memoized across walks, and one that has only
|
|
592
|
+
grown is extended by its appended lines, so a predicate that walks
|
|
593
|
+
repeatedly — or a resident process that re-walks per event — parses
|
|
594
|
+
only what was appended. Transcripts are assumed append-only: a
|
|
595
|
+
rewrite of earlier bytes that keeps the file's consumed tail and
|
|
596
|
+
grows it is read as growth, as :data:`DEEP_LIFTS` documents.
|
|
475
597
|
"""
|
|
476
598
|
return deep_sessions(self)
|
|
477
599
|
|
|
@@ -578,9 +700,11 @@ class Session:
|
|
|
578
700
|
def deep_inputs(self) -> Iterator[PredicateInputs]:
|
|
579
701
|
"""This window's :class:`PredicateInputs`, then those of every transcript :meth:`walk` reaches.
|
|
580
702
|
|
|
581
|
-
The walk order and dedupe match :meth:`walk`, but
|
|
582
|
-
one's inputs are held per ``(size, mtime, inode)`` stamp,
|
|
583
|
-
once its stamp moves
|
|
703
|
+
The walk order and dedupe match :meth:`walk`, but a reached transcript stays lifted only
|
|
704
|
+
once it has changed: each one's inputs are held per ``(size, mtime, ctime, inode)`` stamp,
|
|
705
|
+
a transcript is lifted again only once its stamp moves, and from then on one that has only
|
|
706
|
+
grown is extended by its appended lines under the append-only contract :data:`DEEP_LIFTS`
|
|
707
|
+
documents. Prefer it to :meth:`walk` for any predicate these inputs can answer.
|
|
584
708
|
"""
|
|
585
709
|
yield self.predicate_inputs
|
|
586
710
|
for path, depth, spawned_by in reachable_transcripts(self):
|
|
@@ -906,17 +1030,18 @@ def sidechain_listing(parent: Path) -> tuple[tuple[Path, Path], ...]:
|
|
|
906
1030
|
|
|
907
1031
|
def load_predicate_inputs(path: Path, depth: int, spawned_by: ToolUseId | None) -> PredicateInputs | None:
|
|
908
1032
|
try:
|
|
909
|
-
|
|
1033
|
+
stamp = stamp_of(path.stat())
|
|
910
1034
|
except OSError:
|
|
911
1035
|
return None
|
|
912
|
-
stamp = (stat.st_size, stat.st_mtime_ns, stat.st_ino)
|
|
913
1036
|
if (held := SIDECHAIN_INPUTS.get(path, stamp)) is not None:
|
|
1037
|
+
DEEP_LIFTS.idle_release((path, depth, spawned_by), stamp)
|
|
914
1038
|
return held
|
|
915
1039
|
try:
|
|
916
|
-
deep =
|
|
1040
|
+
deep, read, settled = deep_session_at(path, depth, spawned_by, stamp, hold=SIDECHAIN_INPUTS.holds(path))
|
|
917
1041
|
except OSError:
|
|
918
1042
|
return None
|
|
919
|
-
|
|
1043
|
+
inputs = PredicateInputs.of(deep.session)
|
|
1044
|
+
return SIDECHAIN_INPUTS.put(path, read, inputs) if settled else inputs
|
|
920
1045
|
|
|
921
1046
|
|
|
922
1047
|
def deep_sessions(root: Session) -> Iterator[DeepSession]:
|
|
@@ -925,28 +1050,136 @@ def deep_sessions(root: Session) -> Iterator[DeepSession]:
|
|
|
925
1050
|
yield deep
|
|
926
1051
|
|
|
927
1052
|
|
|
928
|
-
def lift_deep_session(path: Path, depth: int, spawned_by: ToolUseId | None) -> DeepSession:
|
|
929
|
-
transcript = parse(path)
|
|
930
|
-
session_id = session_id_of(transcript.events) or SessionId(path.stem)
|
|
931
|
-
return DeepSession(
|
|
932
|
-
session=Session.from_activity(SessionActivity.from_events(session_id, transcript.events), path=path),
|
|
933
|
-
path=path,
|
|
934
|
-
provider=transcript.provider,
|
|
935
|
-
depth=depth,
|
|
936
|
-
spawned_by=spawned_by,
|
|
937
|
-
)
|
|
938
|
-
|
|
939
|
-
|
|
940
1053
|
def load_deep_session(path: Path, depth: int, spawned_by: ToolUseId | None) -> DeepSession | None:
|
|
941
1054
|
try:
|
|
942
|
-
|
|
1055
|
+
return deep_session_at(path, depth, spawned_by, stamp_of(path.stat()), hold=True)[0]
|
|
943
1056
|
except OSError:
|
|
944
1057
|
return None
|
|
945
|
-
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
def deep_session_at(
|
|
1061
|
+
path: Path, depth: int, spawned_by: ToolUseId | None, stamp: LiftStamp, *, hold: bool
|
|
1062
|
+
) -> tuple[DeepSession, LiftStamp, bool]:
|
|
946
1063
|
key = (path, depth, spawned_by)
|
|
947
1064
|
if (held := DEEP_LIFTS.get(key, stamp)) is not None:
|
|
948
|
-
return held
|
|
1065
|
+
return held.deep, stamp, True
|
|
1066
|
+
if UNREADABLE.has(path, stamp):
|
|
1067
|
+
raise UnparseableTranscript(path, stamp, True)
|
|
1068
|
+
stale = DEEP_LIFTS.take(key)
|
|
949
1069
|
try:
|
|
950
|
-
|
|
951
|
-
|
|
1070
|
+
if stale is not None and (grown := grown_lift(stale, path, stamp)) is not None:
|
|
1071
|
+
return DEEP_LIFTS.put(key, grown).deep, stamp, True
|
|
1072
|
+
lifted, settled = lift_deep_session(path, depth, spawned_by)
|
|
1073
|
+
except UnparseableTranscript as failure:
|
|
1074
|
+
if failure.settled:
|
|
1075
|
+
UNREADABLE.put(path, failure.stamp)
|
|
1076
|
+
raise
|
|
1077
|
+
if settled and (hold or stale is not None):
|
|
1078
|
+
DEEP_LIFTS.put(key, lifted)
|
|
1079
|
+
return lifted.deep, lifted.stamp, settled
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def lift_deep_session(path: Path, depth: int, spawned_by: ToolUseId | None) -> tuple[DeepLift, bool]:
|
|
1083
|
+
with path.open("rb") as handle:
|
|
1084
|
+
stamp = stamp_of(before := os.fstat(handle.fileno()))
|
|
1085
|
+
raw = handle.read(before.st_size)
|
|
1086
|
+
settled = stamp_of(os.fstat(handle.fileno())) == stamp
|
|
1087
|
+
events = list((transcript := parsed(path, raw, stamp, settled)).events)
|
|
1088
|
+
session_id = (found := session_id_of(events)) or SessionId(path.stem)
|
|
1089
|
+
cut = raw.rfind(b"\n") + 1
|
|
1090
|
+
committed = len(events) if cut == len(raw) else len(events) - len(parsed(path, raw[cut:], stamp, settled).events)
|
|
1091
|
+
activity = (lift := ActivityLift(session_id)).extend(events[:committed])
|
|
1092
|
+
return DeepLift(
|
|
1093
|
+
stamp=stamp,
|
|
1094
|
+
deep=DeepSession(
|
|
1095
|
+
session=Session.from_activity(
|
|
1096
|
+
activity if committed == len(events) else SessionActivity.from_events(session_id, events), path=path
|
|
1097
|
+
),
|
|
1098
|
+
path=path,
|
|
1099
|
+
provider=transcript.provider,
|
|
1100
|
+
depth=depth,
|
|
1101
|
+
spawned_by=spawned_by,
|
|
1102
|
+
),
|
|
1103
|
+
lift=lift,
|
|
1104
|
+
consumed=cut,
|
|
1105
|
+
fence=raw[max(cut - DEEP_LIFT_FENCE, 0) : cut],
|
|
1106
|
+
found=found,
|
|
1107
|
+
provider=transcript.provider if events else None,
|
|
1108
|
+
grown_at=monotonic(),
|
|
1109
|
+
), settled
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
def grown_lift(held: DeepLift, path: Path, stamp: LiftStamp) -> DeepLift | None:
|
|
1113
|
+
if (
|
|
1114
|
+
held.provider not in (None, "claude")
|
|
1115
|
+
or stamp[3] != held.stamp[3]
|
|
1116
|
+
or stamp[0] <= held.stamp[0]
|
|
1117
|
+
or stamp[0] < held.consumed
|
|
1118
|
+
):
|
|
1119
|
+
return None
|
|
1120
|
+
if (read := appended_bytes(path, held.consumed - len(held.fence), stamp[0], stamp)) is None or not read.startswith(
|
|
1121
|
+
held.fence
|
|
1122
|
+
):
|
|
952
1123
|
return None
|
|
1124
|
+
appended = read[len(held.fence) :]
|
|
1125
|
+
cut = appended.rfind(b"\n") + 1
|
|
1126
|
+
if (committed_chunk := parsed(path, appended[:cut], stamp, True)).provider != "claude":
|
|
1127
|
+
return None
|
|
1128
|
+
if cut != len(appended) and (tail_chunk := parsed(path, appended[cut:], stamp, True)).provider != "claude":
|
|
1129
|
+
return None
|
|
1130
|
+
committed = list(committed_chunk.events)
|
|
1131
|
+
tail = [] if cut == len(appended) else list(tail_chunk.events)
|
|
1132
|
+
found = held.found or session_id_of([*committed, *tail])
|
|
1133
|
+
if (found or SessionId(path.stem)) != held.lift.session_id:
|
|
1134
|
+
return None
|
|
1135
|
+
activity = held.lift.extend(committed)
|
|
1136
|
+
session = Session.from_activity(
|
|
1137
|
+
activity
|
|
1138
|
+
if not tail
|
|
1139
|
+
else SessionActivity.from_events(
|
|
1140
|
+
held.lift.session_id, [*(event for turn in activity.turns for event in turn.events), *tail]
|
|
1141
|
+
),
|
|
1142
|
+
path=path,
|
|
1143
|
+
)
|
|
1144
|
+
return DeepLift(
|
|
1145
|
+
stamp=stamp,
|
|
1146
|
+
deep=replace(held.deep, session=session),
|
|
1147
|
+
lift=held.lift,
|
|
1148
|
+
consumed=held.consumed + cut,
|
|
1149
|
+
fence=(held.fence + appended[:cut])[-DEEP_LIFT_FENCE:],
|
|
1150
|
+
found=found,
|
|
1151
|
+
provider=held.provider or ("claude" if committed or tail else None),
|
|
1152
|
+
grown_at=monotonic(),
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
def appended_bytes(path: Path, start: int, stop: int, stamp: LiftStamp) -> bytes | None:
|
|
1157
|
+
with path.open("rb") as handle:
|
|
1158
|
+
handle.seek(start)
|
|
1159
|
+
read = handle.read(stop - start)
|
|
1160
|
+
settled = stamp_of(os.fstat(handle.fileno())) == stamp
|
|
1161
|
+
return read if len(read) == stop - start and settled else None
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
class UnparseableTranscript(OSError):
|
|
1165
|
+
"""A transcript a typed parse rejected deterministically, not a filesystem failure.
|
|
1166
|
+
|
|
1167
|
+
Raised only for a line the parser cannot type — schema drift — so a walk can hold the
|
|
1168
|
+
failure by stamp, where a transient ``OSError`` (EACCES, EIO, an ENOENT race) must
|
|
1169
|
+
retry. An ``OSError`` subclass so both still skip the transcript rather than raise.
|
|
1170
|
+
Carries the stamp of the descriptor the rejected bytes were read from and whether that
|
|
1171
|
+
read settled, so the failure is held under the bytes actually parsed, never under the
|
|
1172
|
+
stat that routed the read, and never when the file moved under it.
|
|
1173
|
+
"""
|
|
1174
|
+
|
|
1175
|
+
def __init__(self, path: Path, stamp: LiftStamp, settled: bool) -> None:
|
|
1176
|
+
super().__init__(f"unreadable transcript: {path}")
|
|
1177
|
+
self.stamp = stamp
|
|
1178
|
+
self.settled = settled
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def parsed(path: Path, raw: bytes, stamp: LiftStamp, settled: bool) -> Transcript:
|
|
1182
|
+
try:
|
|
1183
|
+
return parse(raw)
|
|
1184
|
+
except (KeyError, ValueError):
|
|
1185
|
+
raise UnparseableTranscript(path, stamp, settled) from None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cc-transcript"
|
|
3
|
-
version = "14.
|
|
3
|
+
version = "14.19.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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/sentiment/data/afinn-en-165.tsv
RENAMED
|
File without changes
|
{cc_transcript-14.18.0 → cc_transcript-14.19.0}/cc_transcript/sentiment/data/domain_overrides.tsv
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cc_transcript-14.18.0 → cc_transcript-14.19.0}/rust/crates/core/src/literals/corrections.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|