cc-transcript 14.9.0__tar.gz → 14.10.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.9.0 → cc_transcript-14.10.0}/PKG-INFO +9 -1
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/README.md +8 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/__init__.py +1 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/__init__.pyi +2 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/_native.pyi +22 -1
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/facts.py +7 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/models.py +2 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/render.py +1 -1
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/pyproject.toml +1 -1
- cc_transcript-14.10.0/rust/crates/cli/src/commands/blame.rs +234 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/facts.rs +29 -4
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/grep.rs +44 -10
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/mod.rs +1 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/show.rs +10 -2
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/slice.rs +5 -32
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/stats.rs +33 -6
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/lib.rs +97 -3
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/target.rs +8 -0
- cc_transcript-14.10.0/rust/crates/cli/src/timearg.rs +208 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/activity.rs +4 -1
- cc_transcript-14.10.0/rust/crates/core/src/blame.rs +809 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/command.rs +53 -18
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/discovery.rs +21 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/facts.rs +4 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/lib.rs +1 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/parse.rs +12 -6
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/render.rs +336 -38
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/types.rs +8 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/data/command_prefix_pins.tsv +4 -4
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/bin/stub_gen.rs +10 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/python.rs +2 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/attachment.rs +81 -3
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/mod.rs +1 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/Cargo.lock +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/Cargo.toml +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/LICENSE +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/activity.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/activity_probe.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/builders.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/codex.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/command.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/context.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/corrections.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/cost.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/decisions.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/discovery.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/disktruth.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/evidence.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/extract/__init__.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/extract/correct.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/filterspec.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/heartbeats.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/ids.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/judge/__init__.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/judge/llm.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/judge/similar.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/judge/verdicts.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/ledger.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/literals.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/mining/__init__.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/mining/candidates.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/mining/confidence.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/mining/engine.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/mining/formats.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/mining/sampling.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/mining/signals.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/mining/sourcekind.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/mining/spec.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/mining/store.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/nlp.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/notifications.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/parser.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/py.typed +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/query.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/sentiment/__init__.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/sentiment/buckets.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/sentiment/data/afinn-en-165.tsv +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/sentiment/data/domain_overrides.tsv +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/sentiment/data/en-ewt.udpipe +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/sentiment/engine.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/sentiment/lexicon.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/sentiment/scorespec.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/synthetic.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/tools.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/cc_transcript/watch.py +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/Cargo.toml +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/corrections.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/digest.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/list.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/scratchpad.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/commands/watch.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/main.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/cli/src/output.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/Cargo.toml +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/benches/parse.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/benches/render.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/benches/toolcall.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/actor.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/buckets.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/codex/discovery.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/codex/lower.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/codex/mod.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/codex/parse.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/codex/probe.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/codex/protocol.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/codex/types.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/context.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/corrections.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/cost.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/feedback.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/filter.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/gateway.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/ids.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/judge.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/literals/command.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/literals/corrections.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/literals/feedback.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/literals/mining.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/literals/mod.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/literals/protocol.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/notifications.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/protocol.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/pystr.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/query.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/rng.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/sqlite.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/toolcall.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/value.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/core/src/watch.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/Cargo.toml +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/build.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/actor_bridge.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/codex.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/command.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/context.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/corrections.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/discovery.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/feedback.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/judge.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/lexicon.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/lib.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/mining.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/nlp.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/render.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/score.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/sqlite.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/toolcall.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/blocks.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/command.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/convert.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/dunder.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/events.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/meta.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/print.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/store.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/system.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/toolcall.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/toolresult.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.0}/rust/crates/py/src/views/transcript.rs +0 -0
- {cc_transcript-14.9.0 → cc_transcript-14.10.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.
|
|
3
|
+
Version: 14.10.0
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -120,6 +120,14 @@ print(len(transcript.events), "events after the noise drop")
|
|
|
120
120
|
|
|
121
121
|
`discover()` lists every transcript on disk newest first, `stream` fans a whole corpus across the parse pool, and `resolve` finds one session by UUID. `parse` also reads OpenAI Codex CLI rollouts — hand it a path from `~/.codex/sessions` and the same typed events come back. The [getting-started guide](https://yasyf.github.io/cc-transcript/docs/getting-started/index.html) builds this out to a composed filter and a sentiment score.
|
|
122
122
|
|
|
123
|
+
## JSON output schema
|
|
124
|
+
|
|
125
|
+
`show --json` and `grep --json` emit JSONL, with one object per event. Each event
|
|
126
|
+
uses the envelope `{i, kind, meta, model, text, blocks, stop_reason, usage}`;
|
|
127
|
+
fields that do not apply to its kind are omitted. Every object in `blocks` has
|
|
128
|
+
a `type` discriminator such as `text`, `thinking`, `tool_use`, `tool_result`,
|
|
129
|
+
or `fallback`.
|
|
130
|
+
|
|
123
131
|
## More in the docs
|
|
124
132
|
|
|
125
133
|
- [The Python library](https://yasyf.github.io/cc-transcript/docs/getting-started/index.html) turns a transcript into typed lazy views, a composed filter, and a score in a dozen lines.
|
|
@@ -84,6 +84,14 @@ print(len(transcript.events), "events after the noise drop")
|
|
|
84
84
|
|
|
85
85
|
`discover()` lists every transcript on disk newest first, `stream` fans a whole corpus across the parse pool, and `resolve` finds one session by UUID. `parse` also reads OpenAI Codex CLI rollouts — hand it a path from `~/.codex/sessions` and the same typed events come back. The [getting-started guide](https://yasyf.github.io/cc-transcript/docs/getting-started/index.html) builds this out to a composed filter and a sentiment score.
|
|
86
86
|
|
|
87
|
+
## JSON output schema
|
|
88
|
+
|
|
89
|
+
`show --json` and `grep --json` emit JSONL, with one object per event. Each event
|
|
90
|
+
uses the envelope `{i, kind, meta, model, text, blocks, stop_reason, usage}`;
|
|
91
|
+
fields that do not apply to its kind are omitted. Every object in `blocks` has
|
|
92
|
+
a `type` discriminator such as `text`, `thinking`, `tool_use`, `tool_result`,
|
|
93
|
+
or `fallback`.
|
|
94
|
+
|
|
87
95
|
## More in the docs
|
|
88
96
|
|
|
89
97
|
- [The Python library](https://yasyf.github.io/cc-transcript/docs/getting-started/index.html) turns a transcript into typed lazy views, a composed filter, and a score in a dozen lines.
|
|
@@ -147,6 +147,7 @@ from cc_transcript.models import (
|
|
|
147
147
|
CcVersion as CcVersion,
|
|
148
148
|
CompactBoundary as CompactBoundary,
|
|
149
149
|
ContentBlock as ContentBlock,
|
|
150
|
+
DeferredToolsDelta as DeferredToolsDelta,
|
|
150
151
|
EntryMeta as EntryMeta,
|
|
151
152
|
EventList as EventList,
|
|
152
153
|
FallbackBlock as FallbackBlock,
|
|
@@ -339,6 +340,7 @@ __all__ = [
|
|
|
339
340
|
"DecisionLog",
|
|
340
341
|
"DeepSession",
|
|
341
342
|
"DeepView",
|
|
343
|
+
"DeferredToolsDelta",
|
|
342
344
|
"DiskTruth",
|
|
343
345
|
"Edit",
|
|
344
346
|
"EditCall",
|
|
@@ -28,6 +28,7 @@ __all__ = [
|
|
|
28
28
|
"CommandLine",
|
|
29
29
|
"CommandLineQuery",
|
|
30
30
|
"CompactBoundary",
|
|
31
|
+
"DeferredToolsDelta",
|
|
31
32
|
"EditCall",
|
|
32
33
|
"EditResult",
|
|
33
34
|
"EditSpan",
|
|
@@ -588,6 +589,27 @@ class CompactBoundary:
|
|
|
588
589
|
@property
|
|
589
590
|
def precomputed(self) -> typing.Optional[builtins.bool]: ...
|
|
590
591
|
|
|
592
|
+
@typing.final
|
|
593
|
+
class DeferredToolsDelta(OtherAttachment):
|
|
594
|
+
r"""
|
|
595
|
+
Tools added to and removed from the deferred tool inventory.
|
|
596
|
+
|
|
597
|
+
Attributes:
|
|
598
|
+
added_count: Number of added tools.
|
|
599
|
+
removed_count: Number of removed tools.
|
|
600
|
+
added_names: Added tool names, in transcript order.
|
|
601
|
+
removed_names: Removed tool names, in transcript order.
|
|
602
|
+
"""
|
|
603
|
+
__match_args__: typing.ClassVar[tuple[str, ...]]
|
|
604
|
+
@property
|
|
605
|
+
def added_count(self) -> builtins.int: ...
|
|
606
|
+
@property
|
|
607
|
+
def removed_count(self) -> builtins.int: ...
|
|
608
|
+
@property
|
|
609
|
+
def added_names(self) -> tuple[str, ...]: ...
|
|
610
|
+
@property
|
|
611
|
+
def removed_names(self) -> tuple[str, ...]: ...
|
|
612
|
+
|
|
591
613
|
@typing.final
|
|
592
614
|
class EditCall(ToolCallBase):
|
|
593
615
|
r"""
|
|
@@ -1163,7 +1185,6 @@ class Occurrence:
|
|
|
1163
1185
|
One shell-word spelling of ``text`` that survives every enclosing layer, or None.
|
|
1164
1186
|
"""
|
|
1165
1187
|
|
|
1166
|
-
@typing.final
|
|
1167
1188
|
class OtherAttachment:
|
|
1168
1189
|
r"""
|
|
1169
1190
|
Any attachment whose type has no typed detail, carried verbatim.
|
|
@@ -33,6 +33,7 @@ class ToolFact:
|
|
|
33
33
|
ts: The timestamp of the assistant entry that made the call, or None.
|
|
34
34
|
session_id: The Claude session the call belongs to.
|
|
35
35
|
path: The transcript file the call was lifted from.
|
|
36
|
+
cwd: The working directory recorded on the originating event, or None.
|
|
36
37
|
tool_use_id: The tool-use block id — the join key back to the event stream.
|
|
37
38
|
tool: The tool name exactly as invoked.
|
|
38
39
|
command_prefixes: The permission-style prefixes of each Bash command
|
|
@@ -42,6 +43,8 @@ class ToolFact:
|
|
|
42
43
|
mcp_tool: The MCP tool segment for an ``mcp__server__tool`` call, else None.
|
|
43
44
|
mcp_access: Whether the MCP tool reads or writes, else None.
|
|
44
45
|
file_path: The file the call targets, when it targets one.
|
|
46
|
+
file_paths: Every file the call touches, apply_patch multi-file included;
|
|
47
|
+
``file_path`` mirrors the first entry.
|
|
45
48
|
is_error: Whether the call's result reported a failure.
|
|
46
49
|
denied: Whether the result is a user rejection of the tool use.
|
|
47
50
|
denial_kind: The structured tool-denial kind (``user-rejected`` for a human
|
|
@@ -53,6 +56,7 @@ class ToolFact:
|
|
|
53
56
|
ts: datetime | None
|
|
54
57
|
session_id: SessionId
|
|
55
58
|
path: Path
|
|
59
|
+
cwd: str | None
|
|
56
60
|
tool_use_id: ToolUseId
|
|
57
61
|
tool: str
|
|
58
62
|
command_prefixes: tuple[str, ...]
|
|
@@ -61,6 +65,7 @@ class ToolFact:
|
|
|
61
65
|
mcp_tool: str | None
|
|
62
66
|
mcp_access: Literal["read", "write"] | None
|
|
63
67
|
file_path: str | None
|
|
68
|
+
file_paths: tuple[str, ...]
|
|
64
69
|
is_error: bool
|
|
65
70
|
denied: bool
|
|
66
71
|
denial_kind: str | None
|
|
@@ -73,6 +78,7 @@ def rehydrate_fact(fact: Mapping[str, Any], path: Path) -> ToolFact:
|
|
|
73
78
|
ts=datetime.fromisoformat(fact["ts"]),
|
|
74
79
|
session_id=SessionId(fact["session_id"]),
|
|
75
80
|
path=path,
|
|
81
|
+
cwd=fact["cwd"],
|
|
76
82
|
tool_use_id=ToolUseId(fact["tool_use_id"]),
|
|
77
83
|
tool=fact["tool"],
|
|
78
84
|
command_prefixes=tuple(fact["command_prefixes"]),
|
|
@@ -81,6 +87,7 @@ def rehydrate_fact(fact: Mapping[str, Any], path: Path) -> ToolFact:
|
|
|
81
87
|
mcp_tool=fact["mcp_tool"],
|
|
82
88
|
mcp_access=fact["mcp_access"],
|
|
83
89
|
file_path=fact["file_path"],
|
|
90
|
+
file_paths=tuple(fact["file_paths"]),
|
|
84
91
|
is_error=fact["is_error"],
|
|
85
92
|
denied=fact["denied"],
|
|
86
93
|
denial_kind=fact["denial_kind"],
|
|
@@ -17,6 +17,7 @@ from cc_transcript._native import AttachmentEvent as AttachmentEvent
|
|
|
17
17
|
from cc_transcript._native import Attribution as Attribution
|
|
18
18
|
from cc_transcript._native import CacheCreation as CacheCreation
|
|
19
19
|
from cc_transcript._native import CompactBoundary as CompactBoundary
|
|
20
|
+
from cc_transcript._native import DeferredToolsDelta as DeferredToolsDelta
|
|
20
21
|
from cc_transcript._native import EntryMeta as EntryMeta
|
|
21
22
|
from cc_transcript._native import EventList as EventList
|
|
22
23
|
from cc_transcript._native import FallbackBlock as FallbackBlock
|
|
@@ -121,6 +122,7 @@ AttachmentDetail = (
|
|
|
121
122
|
| HookAdditionalContext
|
|
122
123
|
| AsyncHookResponse
|
|
123
124
|
| QueuedCommand
|
|
125
|
+
| DeferredToolsDelta
|
|
124
126
|
| OtherAttachment
|
|
125
127
|
)
|
|
126
128
|
|
|
@@ -40,7 +40,7 @@ def render_tool_call(call: ToolCall | FallbackCall, *, budget: Budget) -> str:
|
|
|
40
40
|
The single tool-input renderer: Edit renders the path plus ``- old`` /
|
|
41
41
|
``+ new`` lines, MultiEdit renders every span under an ``edit i/n``
|
|
42
42
|
marker, Write renders the path plus content, Bash renders the command,
|
|
43
|
-
and everything else renders
|
|
43
|
+
and everything else renders the tool name with its full compact-JSON input.
|
|
44
44
|
|
|
45
45
|
Example:
|
|
46
46
|
>>> render_tool_call(parse_tool_call("Bash", {"command": "ls"}), budget=Budget())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cc-transcript"
|
|
3
|
-
version = "14.
|
|
3
|
+
version = "14.10.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"
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
//! The file-centric `blame` and `attribute` verbs — native-only, with no click twin.
|
|
2
|
+
//! `blame` lists the sessions that wrote a working-tree file, newest first; `attribute`
|
|
3
|
+
//! classifies how the file came to exist. Both resolve the target's repo root from a
|
|
4
|
+
//! `.git`/`.jj` ancestor and scan only that repo's project dirs unless `--all-projects`.
|
|
5
|
+
|
|
6
|
+
use std::fs;
|
|
7
|
+
use std::path::{Path, PathBuf};
|
|
8
|
+
|
|
9
|
+
use cc_transcript_core::blame::{attribute as classify, blame as rank, session_writes, RepoPaths};
|
|
10
|
+
use cc_transcript_core::discovery::{find_in, mtime_secs, project_dir_name};
|
|
11
|
+
use cc_transcript_core::render::{attribute_json, attribute_lines, blame_json, blame_line};
|
|
12
|
+
use cc_transcript_core::types::Entry;
|
|
13
|
+
use chrono::{DateTime, FixedOffset};
|
|
14
|
+
|
|
15
|
+
use crate::output::{click_error, eline, py_repr, usage_error, CliExit, Out};
|
|
16
|
+
use crate::target::{claude_projects_dir, parse_transcripts, require_dir, Parsed};
|
|
17
|
+
|
|
18
|
+
const BLAME_USAGE: &str = "cc-transcript blame [OPTIONS] <PATH>";
|
|
19
|
+
const BLAME_HELP: &str = "cc-transcript blame";
|
|
20
|
+
const ATTRIBUTE_USAGE: &str = "cc-transcript attribute [OPTIONS] <PATH>";
|
|
21
|
+
const ATTRIBUTE_HELP: &str = "cc-transcript attribute";
|
|
22
|
+
|
|
23
|
+
pub struct BlameArgs {
|
|
24
|
+
pub path: PathBuf,
|
|
25
|
+
pub root: Option<PathBuf>,
|
|
26
|
+
pub all_projects: bool,
|
|
27
|
+
pub since: Option<String>,
|
|
28
|
+
pub until: Option<String>,
|
|
29
|
+
pub limit: Option<usize>,
|
|
30
|
+
pub json: bool,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub struct AttributeArgs {
|
|
34
|
+
pub path: PathBuf,
|
|
35
|
+
pub root: Option<PathBuf>,
|
|
36
|
+
pub all_projects: bool,
|
|
37
|
+
pub json: bool,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// A resolved target: the repo it belongs to, its repo-relative path, and its canonical
|
|
41
|
+
/// on-disk location.
|
|
42
|
+
struct Target {
|
|
43
|
+
repo: RepoPaths,
|
|
44
|
+
rel_target: String,
|
|
45
|
+
canonical: PathBuf,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// The nearest ancestor directory carrying a `.git` or `.jj` entry (file or dir — a
|
|
49
|
+
/// worktree's `.git` is a file), starting from the target's own parent.
|
|
50
|
+
fn marker_root(canonical: &Path) -> Option<&Path> {
|
|
51
|
+
canonical
|
|
52
|
+
.ancestors()
|
|
53
|
+
.skip(1)
|
|
54
|
+
.find(|dir| dir.join(".git").exists() || dir.join(".jj").exists())
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// A worktree's marker root lifts to the main checkout: everything before the
|
|
58
|
+
/// `/.claude/worktrees/` segment. Any other marker root is the repo root as found.
|
|
59
|
+
fn repo_root(marker: &Path) -> String {
|
|
60
|
+
let text = marker.to_string_lossy();
|
|
61
|
+
let marker_segment = format!("/{}", cc_transcript_core::blame::WORKTREES_SEGMENT);
|
|
62
|
+
match text.find(&marker_segment) {
|
|
63
|
+
Some(cut) => text[..cut].to_string(),
|
|
64
|
+
None => text.into_owned(),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn resolve_target(path: &Path, usage: &str, help_path: &str) -> Result<Target, CliExit> {
|
|
69
|
+
let invalid = |what: &str| {
|
|
70
|
+
usage_error(
|
|
71
|
+
usage,
|
|
72
|
+
help_path,
|
|
73
|
+
&format!(
|
|
74
|
+
"Invalid value for 'PATH': File {} {what}.",
|
|
75
|
+
py_repr(&path.to_string_lossy())
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
};
|
|
79
|
+
let canonical = fs::canonicalize(path).map_err(|_| invalid("does not exist"))?;
|
|
80
|
+
if canonical.is_dir() {
|
|
81
|
+
return Err(invalid("is a directory"));
|
|
82
|
+
}
|
|
83
|
+
let marker = marker_root(&canonical).ok_or_else(|| {
|
|
84
|
+
click_error(&format!(
|
|
85
|
+
"{} is not inside a repository (no .git or .jj ancestor)",
|
|
86
|
+
path.display()
|
|
87
|
+
))
|
|
88
|
+
})?;
|
|
89
|
+
let rel_target = canonical
|
|
90
|
+
.strip_prefix(marker)
|
|
91
|
+
.expect("canonical target sits under its marker root")
|
|
92
|
+
.to_string_lossy()
|
|
93
|
+
.into_owned();
|
|
94
|
+
Ok(Target {
|
|
95
|
+
repo: RepoPaths {
|
|
96
|
+
root: repo_root(marker),
|
|
97
|
+
},
|
|
98
|
+
rel_target,
|
|
99
|
+
canonical,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Parse every transcript under a project dir belonging to `repo_root` (or every dir
|
|
104
|
+
/// under `--all-projects`), dropping files older than `min_mtime` (blame's `--since`).
|
|
105
|
+
/// The `{encoded}-` prefix admits prefix-collided siblings (`<root>-rust`); the engine's
|
|
106
|
+
/// per-call cwd check drops their writes.
|
|
107
|
+
fn select_transcripts(
|
|
108
|
+
projects_root: &Path,
|
|
109
|
+
repo_root: &str,
|
|
110
|
+
all_projects: bool,
|
|
111
|
+
min_mtime: Option<f64>,
|
|
112
|
+
usage: &str,
|
|
113
|
+
help_path: &str,
|
|
114
|
+
) -> Result<Vec<Parsed>, CliExit> {
|
|
115
|
+
require_dir(projects_root, "'--root'", usage, help_path)?;
|
|
116
|
+
let encoded = project_dir_name(repo_root);
|
|
117
|
+
let prefix = format!("{encoded}-");
|
|
118
|
+
let pairs: Vec<(PathBuf, f64)> = fs::read_dir(projects_root)
|
|
119
|
+
.into_iter()
|
|
120
|
+
.flatten()
|
|
121
|
+
.flatten()
|
|
122
|
+
.filter(|entry| {
|
|
123
|
+
let name = entry.file_name().to_string_lossy().into_owned();
|
|
124
|
+
all_projects || name == encoded || name.starts_with(&prefix)
|
|
125
|
+
})
|
|
126
|
+
.flat_map(|entry| find_in(&entry.path(), None, None, None))
|
|
127
|
+
.filter(|(_, mtime)| min_mtime.is_none_or(|floor| *mtime >= floor))
|
|
128
|
+
.collect();
|
|
129
|
+
Ok(parse_transcripts(&pairs))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// A file mtime (`os.stat` seconds) as a UTC-offset datetime, for the attribute window.
|
|
133
|
+
fn mtime_datetime(mtime: f64) -> DateTime<FixedOffset> {
|
|
134
|
+
let secs = mtime.floor();
|
|
135
|
+
let nanos = ((mtime - secs) * 1e9) as u32;
|
|
136
|
+
DateTime::from_timestamp(secs as i64, nanos)
|
|
137
|
+
.expect("file mtime is in range")
|
|
138
|
+
.fixed_offset()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
pub fn blame(args: BlameArgs) -> Result<(), CliExit> {
|
|
142
|
+
let target = resolve_target(&args.path, BLAME_USAGE, BLAME_HELP)?;
|
|
143
|
+
let now = chrono::Local::now();
|
|
144
|
+
let since = args
|
|
145
|
+
.since
|
|
146
|
+
.as_deref()
|
|
147
|
+
.map(|value| crate::timearg::parse_time("--since", value, now, BLAME_USAGE, BLAME_HELP))
|
|
148
|
+
.transpose()?;
|
|
149
|
+
let until = args
|
|
150
|
+
.until
|
|
151
|
+
.as_deref()
|
|
152
|
+
.map(|value| crate::timearg::parse_time("--until", value, now, BLAME_USAGE, BLAME_HELP))
|
|
153
|
+
.transpose()?;
|
|
154
|
+
let projects_root = args.root.unwrap_or_else(claude_projects_dir);
|
|
155
|
+
let parsed = select_transcripts(
|
|
156
|
+
&projects_root,
|
|
157
|
+
&target.repo.root,
|
|
158
|
+
args.all_projects,
|
|
159
|
+
since.map(|ts| ts.timestamp() as f64),
|
|
160
|
+
BLAME_USAGE,
|
|
161
|
+
BLAME_HELP,
|
|
162
|
+
)?;
|
|
163
|
+
let mut records = rank(
|
|
164
|
+
parsed
|
|
165
|
+
.iter()
|
|
166
|
+
.filter_map(|p| p.session_id.as_deref().map(|session| (session, p)))
|
|
167
|
+
.flat_map(|(session, p)| {
|
|
168
|
+
session_writes(
|
|
169
|
+
session,
|
|
170
|
+
&p.path.to_string_lossy(),
|
|
171
|
+
&p.entries,
|
|
172
|
+
&target.repo,
|
|
173
|
+
&target.rel_target,
|
|
174
|
+
since,
|
|
175
|
+
until,
|
|
176
|
+
)
|
|
177
|
+
})
|
|
178
|
+
.collect(),
|
|
179
|
+
);
|
|
180
|
+
if let Some(limit) = args.limit {
|
|
181
|
+
records.truncate(limit);
|
|
182
|
+
}
|
|
183
|
+
if records.is_empty() {
|
|
184
|
+
eline(&format!("no sessions wrote {}", target.rel_target));
|
|
185
|
+
return Err(CliExit(1));
|
|
186
|
+
}
|
|
187
|
+
let mut out = Out::new();
|
|
188
|
+
for record in &records {
|
|
189
|
+
out.line(&match args.json {
|
|
190
|
+
true => blame_json(record).dumps(),
|
|
191
|
+
false => blame_line(record),
|
|
192
|
+
})?;
|
|
193
|
+
}
|
|
194
|
+
out.finish()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pub fn attribute(args: AttributeArgs) -> Result<(), CliExit> {
|
|
198
|
+
let target = resolve_target(&args.path, ATTRIBUTE_USAGE, ATTRIBUTE_HELP)?;
|
|
199
|
+
let meta = fs::metadata(&target.canonical)
|
|
200
|
+
.map_err(|e| click_error(&format!("{}: {e}", args.path.display())))?;
|
|
201
|
+
let mtime = mtime_datetime(mtime_secs(&meta));
|
|
202
|
+
let projects_root = args.root.unwrap_or_else(claude_projects_dir);
|
|
203
|
+
let parsed = select_transcripts(
|
|
204
|
+
&projects_root,
|
|
205
|
+
&target.repo.root,
|
|
206
|
+
args.all_projects,
|
|
207
|
+
None,
|
|
208
|
+
ATTRIBUTE_USAGE,
|
|
209
|
+
ATTRIBUTE_HELP,
|
|
210
|
+
)?;
|
|
211
|
+
let owned: Vec<(&str, String, &[Entry])> = parsed
|
|
212
|
+
.iter()
|
|
213
|
+
.filter_map(|p| {
|
|
214
|
+
p.session_id.as_deref().map(|session| {
|
|
215
|
+
(
|
|
216
|
+
session,
|
|
217
|
+
p.path.to_string_lossy().into_owned(),
|
|
218
|
+
p.entries.as_slice(),
|
|
219
|
+
)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
.collect();
|
|
223
|
+
let sessions: Vec<(&str, &str, &[Entry])> = owned
|
|
224
|
+
.iter()
|
|
225
|
+
.map(|(session, path, entries)| (*session, path.as_str(), *entries))
|
|
226
|
+
.collect();
|
|
227
|
+
let verdict = classify(&sessions, &target.repo, &target.rel_target, mtime);
|
|
228
|
+
let mut out = Out::new();
|
|
229
|
+
match args.json {
|
|
230
|
+
true => out.line(&attribute_json(&verdict, &target.rel_target, mtime).dumps())?,
|
|
231
|
+
false => out.lines(attribute_lines(&verdict, &target.rel_target))?,
|
|
232
|
+
}
|
|
233
|
+
out.finish()
|
|
234
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
use std::path::PathBuf;
|
|
4
4
|
|
|
5
5
|
use cc_transcript_core::facts::{command_prefix_counts, mcp_summary, tool_facts, ToolFact};
|
|
6
|
+
use cc_transcript_core::query::FileRef;
|
|
6
7
|
use cc_transcript_core::render::{
|
|
7
8
|
denial_json, denial_line, fact_json, fact_line, render_counts, render_mcp, Json,
|
|
8
9
|
};
|
|
@@ -18,6 +19,7 @@ fn gather(
|
|
|
18
19
|
paths: &[PathBuf],
|
|
19
20
|
discovery: &DiscoveryOpts,
|
|
20
21
|
name: &str,
|
|
22
|
+
min_mtime: Option<f64>,
|
|
21
23
|
) -> Result<(Targets, Vec<ToolFact>), CliExit> {
|
|
22
24
|
let paths: Vec<PathBuf> = paths
|
|
23
25
|
.iter()
|
|
@@ -32,6 +34,7 @@ fn gather(
|
|
|
32
34
|
discovery.project.as_deref(),
|
|
33
35
|
discovery.contains.as_deref(),
|
|
34
36
|
discovery.effective_limit(),
|
|
37
|
+
min_mtime,
|
|
35
38
|
)?;
|
|
36
39
|
let facts: Vec<ToolFact> = parse_transcripts(&targets.paths)
|
|
37
40
|
.iter()
|
|
@@ -54,12 +57,34 @@ pub fn tools(
|
|
|
54
57
|
paths: &[PathBuf],
|
|
55
58
|
discovery: &DiscoveryOpts,
|
|
56
59
|
tool: Option<&str>,
|
|
60
|
+
file: Option<&str>,
|
|
61
|
+
since: Option<&str>,
|
|
62
|
+
until: Option<&str>,
|
|
57
63
|
json: bool,
|
|
58
64
|
) -> Result<(), CliExit> {
|
|
59
|
-
let
|
|
65
|
+
let usage = "cc-transcript tools [OPTIONS] [PATHS]...";
|
|
66
|
+
let help_path = "cc-transcript tools";
|
|
67
|
+
let now = chrono::Local::now();
|
|
68
|
+
let since_ts = since
|
|
69
|
+
.map(|value| crate::timearg::parse_time("--since", value, now, usage, help_path))
|
|
70
|
+
.transpose()?;
|
|
71
|
+
let until_ts = until
|
|
72
|
+
.map(|value| crate::timearg::parse_time("--until", value, now, usage, help_path))
|
|
73
|
+
.transpose()?;
|
|
74
|
+
let min_mtime = since_ts.map(|ts| ts.timestamp() as f64);
|
|
75
|
+
let (targets, facts) = gather(paths, discovery, "tools", min_mtime)?;
|
|
60
76
|
let facts: Vec<&ToolFact> = facts
|
|
61
77
|
.iter()
|
|
62
78
|
.filter(|fact| tool.is_none_or(|t| tool_name_matches(&fact.tool, t)))
|
|
79
|
+
.filter(|fact| {
|
|
80
|
+
file.is_none_or(|glob| {
|
|
81
|
+
fact.file_paths
|
|
82
|
+
.iter()
|
|
83
|
+
.any(|p| FileRef::new(p).matches(&[glob]))
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
.filter(|fact| since_ts.is_none_or(|s| fact.ts >= s))
|
|
87
|
+
.filter(|fact| until_ts.is_none_or(|u| fact.ts < u))
|
|
63
88
|
.collect();
|
|
64
89
|
let mut out = Out::new();
|
|
65
90
|
if json {
|
|
@@ -76,7 +101,7 @@ pub fn tools(
|
|
|
76
101
|
}
|
|
77
102
|
|
|
78
103
|
pub fn commands(paths: &[PathBuf], discovery: &DiscoveryOpts, json: bool) -> Result<(), CliExit> {
|
|
79
|
-
let (targets, facts) = gather(paths, discovery, "commands")?;
|
|
104
|
+
let (targets, facts) = gather(paths, discovery, "commands", None)?;
|
|
80
105
|
let counts = command_prefix_counts(facts.iter());
|
|
81
106
|
let mut out = Out::new();
|
|
82
107
|
if json {
|
|
@@ -103,7 +128,7 @@ pub fn permissions(
|
|
|
103
128
|
discovery: &DiscoveryOpts,
|
|
104
129
|
json: bool,
|
|
105
130
|
) -> Result<(), CliExit> {
|
|
106
|
-
let (targets, facts) = gather(paths, discovery, "permissions")?;
|
|
131
|
+
let (targets, facts) = gather(paths, discovery, "permissions", None)?;
|
|
107
132
|
let denials: Vec<&ToolFact> = facts
|
|
108
133
|
.iter()
|
|
109
134
|
.filter(|fact| fact.denied)
|
|
@@ -124,7 +149,7 @@ pub fn permissions(
|
|
|
124
149
|
}
|
|
125
150
|
|
|
126
151
|
pub fn mcp(paths: &[PathBuf], discovery: &DiscoveryOpts, json: bool) -> Result<(), CliExit> {
|
|
127
|
-
let (targets, facts) = gather(paths, discovery, "mcp")?;
|
|
152
|
+
let (targets, facts) = gather(paths, discovery, "mcp", None)?;
|
|
128
153
|
let summary = mcp_summary(facts.iter());
|
|
129
154
|
let mut out = Out::new();
|
|
130
155
|
if json {
|
|
@@ -5,7 +5,9 @@ use std::path::PathBuf;
|
|
|
5
5
|
|
|
6
6
|
use cc_transcript_core::facts::{tool_facts, ToolFact};
|
|
7
7
|
use cc_transcript_core::filter::event_kind;
|
|
8
|
-
use cc_transcript_core::render::{
|
|
8
|
+
use cc_transcript_core::render::{
|
|
9
|
+
compact_line, event_json, haystack, tool_haystack, transcript_header, Json,
|
|
10
|
+
};
|
|
9
11
|
use cc_transcript_core::toolcall::tool_name_matches;
|
|
10
12
|
use cc_transcript_core::types::{ContentBlock, Entry};
|
|
11
13
|
use regex::{Regex, RegexBuilder};
|
|
@@ -23,6 +25,7 @@ pub struct GrepArgs {
|
|
|
23
25
|
pub discovery: DiscoveryOpts,
|
|
24
26
|
pub kinds: Vec<String>,
|
|
25
27
|
pub tool: Option<String>,
|
|
28
|
+
pub errors: bool,
|
|
26
29
|
pub ignore_case: bool,
|
|
27
30
|
pub wheres: Vec<String>,
|
|
28
31
|
pub context: usize,
|
|
@@ -61,6 +64,28 @@ fn event_facts<'a>(event: &Entry, facts: &'a HashMap<String, ToolFact>) -> Vec<&
|
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
fn error_call_matches(
|
|
68
|
+
event: &Entry,
|
|
69
|
+
facts: &HashMap<String, ToolFact>,
|
|
70
|
+
regex: &Regex,
|
|
71
|
+
tool: Option<&str>,
|
|
72
|
+
where_tools: bool,
|
|
73
|
+
) -> bool {
|
|
74
|
+
where_tools
|
|
75
|
+
&& event.blocks().iter().any(|block| {
|
|
76
|
+
let fact = match block {
|
|
77
|
+
ContentBlock::ToolUse(tool_use) => facts.get(&tool_use.id),
|
|
78
|
+
ContentBlock::ToolResult(result) => facts.get(&result.tool_use_id),
|
|
79
|
+
_ => None,
|
|
80
|
+
};
|
|
81
|
+
fact.is_some_and(|fact| {
|
|
82
|
+
fact.is_error
|
|
83
|
+
&& tool.is_none_or(|name| tool_name_matches(&fact.tool, name))
|
|
84
|
+
&& regex.is_match(&tool_haystack(block))
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
64
89
|
fn result_key(fact: &ToolFact) -> Json {
|
|
65
90
|
Json::Obj(vec![
|
|
66
91
|
("is_error".into(), Json::Bool(fact.is_error)),
|
|
@@ -156,6 +181,7 @@ pub fn run(args: GrepArgs) -> Result<(), CliExit> {
|
|
|
156
181
|
args.discovery.project.as_deref(),
|
|
157
182
|
args.discovery.contains.as_deref(),
|
|
158
183
|
args.discovery.effective_limit(),
|
|
184
|
+
None,
|
|
159
185
|
)?;
|
|
160
186
|
let mut out_lines: Vec<String> = Vec::new();
|
|
161
187
|
let mut files_matched = 0usize;
|
|
@@ -166,6 +192,11 @@ pub fn run(args: GrepArgs) -> Result<(), CliExit> {
|
|
|
166
192
|
break;
|
|
167
193
|
}
|
|
168
194
|
let names = tool_names(&parsed.entries);
|
|
195
|
+
let facts = if args.with_result || args.errors {
|
|
196
|
+
fact_index(&parsed)
|
|
197
|
+
} else {
|
|
198
|
+
HashMap::new()
|
|
199
|
+
};
|
|
169
200
|
let hits: Vec<usize> = parsed
|
|
170
201
|
.entries
|
|
171
202
|
.iter()
|
|
@@ -174,22 +205,25 @@ pub fn run(args: GrepArgs) -> Result<(), CliExit> {
|
|
|
174
205
|
args.kinds.is_empty() || args.kinds.iter().any(|k| k == event_kind(event))
|
|
175
206
|
})
|
|
176
207
|
.filter(|(_, event)| {
|
|
177
|
-
args.
|
|
178
|
-
|
|
179
|
-
|
|
208
|
+
args.errors
|
|
209
|
+
|| args
|
|
210
|
+
.tool
|
|
211
|
+
.as_deref()
|
|
212
|
+
.is_none_or(|tool| uses_tool(event, tool, &names))
|
|
213
|
+
})
|
|
214
|
+
.filter(|(_, event)| {
|
|
215
|
+
if args.errors {
|
|
216
|
+
error_call_matches(event, &facts, ®ex, args.tool.as_deref(), w_tools)
|
|
217
|
+
} else {
|
|
218
|
+
regex.is_match(&haystack(event, w_text, w_thinking, w_tools))
|
|
219
|
+
}
|
|
180
220
|
})
|
|
181
|
-
.filter(|(_, event)| regex.is_match(&haystack(event, w_text, w_thinking, w_tools)))
|
|
182
221
|
.map(|(index, _)| index)
|
|
183
222
|
.take(budget)
|
|
184
223
|
.collect();
|
|
185
224
|
if hits.is_empty() {
|
|
186
225
|
continue;
|
|
187
226
|
}
|
|
188
|
-
let facts = if args.with_result {
|
|
189
|
-
fact_index(&parsed)
|
|
190
|
-
} else {
|
|
191
|
-
HashMap::new()
|
|
192
|
-
};
|
|
193
227
|
files_matched += 1;
|
|
194
228
|
matched += hits.len();
|
|
195
229
|
budget -= hits.len();
|
|
@@ -6,7 +6,7 @@ use std::path::PathBuf;
|
|
|
6
6
|
|
|
7
7
|
use cc_transcript_core::filter::{event_kind, spec_keep, CompiledSpec, NOISE_SPEC, SIGNAL_SPEC};
|
|
8
8
|
use cc_transcript_core::render::{compact_line, event_json};
|
|
9
|
-
use cc_transcript_core::types::Entry;
|
|
9
|
+
use cc_transcript_core::types::{ContentBlock, Entry};
|
|
10
10
|
|
|
11
11
|
use crate::output::{eline, py_repr, usage_error, CliExit, Out};
|
|
12
12
|
use crate::target::{parse_single, py_path, require_files, tool_names};
|
|
@@ -24,6 +24,7 @@ pub struct ShowArgs {
|
|
|
24
24
|
pub kinds: Vec<String>,
|
|
25
25
|
pub signal: bool,
|
|
26
26
|
pub no_junk: bool,
|
|
27
|
+
pub errors: bool,
|
|
27
28
|
pub thinking: bool,
|
|
28
29
|
pub width: usize,
|
|
29
30
|
pub uuids: bool,
|
|
@@ -60,12 +61,19 @@ pub fn filter_rows<'a>(
|
|
|
60
61
|
entries: &'a [Entry],
|
|
61
62
|
kinds: &[String],
|
|
62
63
|
spec: Option<&CompiledSpec>,
|
|
64
|
+
errors: bool,
|
|
63
65
|
) -> Vec<(usize, &'a Entry)> {
|
|
64
66
|
entries
|
|
65
67
|
.iter()
|
|
66
68
|
.enumerate()
|
|
67
69
|
.filter(|(_, event)| kinds.is_empty() || kinds.iter().any(|k| k == event_kind(event)))
|
|
68
70
|
.filter(|(_, event)| spec.is_none_or(|s| spec_keep(s, event)))
|
|
71
|
+
.filter(|(_, event)| {
|
|
72
|
+
!errors
|
|
73
|
+
|| matches!(event, Entry::User(u) if u.blocks().iter().any(
|
|
74
|
+
|block| matches!(block, ContentBlock::ToolResult(result) if result.is_error)
|
|
75
|
+
))
|
|
76
|
+
})
|
|
69
77
|
.collect()
|
|
70
78
|
}
|
|
71
79
|
|
|
@@ -133,7 +141,7 @@ pub fn run(args: ShowArgs) -> Result<(), CliExit> {
|
|
|
133
141
|
} else {
|
|
134
142
|
None
|
|
135
143
|
};
|
|
136
|
-
let rows = filter_rows(&parsed.entries, &args.kinds, spec);
|
|
144
|
+
let rows = filter_rows(&parsed.entries, &args.kinds, spec, args.errors);
|
|
137
145
|
let (selected, notice) = slice_rows(rows, args.head, args.tail, bounds, args.all);
|
|
138
146
|
let mut out = Out::new();
|
|
139
147
|
if args.json {
|