Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
claude_code_log/renderer.py (1)
1904-1907:⚠️ Potential issue | 🟠 MajorPreserve string
queue-operation removesteering payloads.Line 2004 only helps queue operations that already survived
_filter_messages, but Line 1904 treats stringcontentas empty and drops it. Since the model allowsQueueOperationTranscriptEntry.contentto be astr, USER_ONLY can still lose real steering prompts.🐛 Proposed fix
+def _queue_operation_content_items( + message: QueueOperationTranscriptEntry, +) -> list[ContentItem]: + content = message.content + if isinstance(content, list): + return content + if isinstance(content, str) and content.strip(): + return [TextContent(type="text", text=content)] + return [] + + def _filter_messages(messages: list[TranscriptEntry]) -> list[TranscriptEntry]: @@ message_content: list[ContentItem] if isinstance(message, QueueOperationTranscriptEntry): - content = message.content - message_content = content if isinstance(content, list) else [] + message_content = _queue_operation_content_items(message) else: message_content = message.message.content @@ # Get message content if isinstance(message, QueueOperationTranscriptEntry): - message_content = message.content if message.content else [] + message_content = _queue_operation_content_items(message) else: message_content = message.message.content # type: ignoreAlso applies to: 2004-2021
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@claude_code_log/renderer.py` around lines 1904 - 1907, The code treats QueueOperationTranscriptEntry.content strings as empty and drops steering payloads; update the logic around QueueOperationTranscriptEntry handling (where message_content is assigned) so that if content is a list it remains as-is, if content is a str it is preserved by wrapping it into a single-element list (e.g., message_content = content if isinstance(content, list) else [content] if isinstance(content, str) else []), and apply the same fix to the similar block later (the 2004-2021 region) so string `queue-operation remove` payloads are retained.test/test_output_paths.py (1)
50-55:⚠️ Potential issue | 🔴 CriticalThe regex pattern must also accept hyphens to match
.user-onlyfilenames.Lines 54–55 test suffix generation correctly, but the discovery regex at
claude_code_log/utils.py:44uses[a-z]+which rejects hyphens. The pattern currently fails to matchcombined_transcripts.user-only.html, causing the variant discovery in_enumerate_project_variants()to silently miss USER_ONLY outputs.Two changes required:
- Update regex pattern to accept hyphens: change
[a-z]+to[a-z-]+(or similar)- Add test case to lines 77–87 to cover
.user-onlyRegex fix
-VARIANT_ENTRY_RE = re.compile(r"^combined_transcripts((?:\.[a-z]+)*)\.html$") +VARIANT_ENTRY_RE = re.compile(r"^combined_transcripts((?:\.[a-z-]+)*)\.html$")Test matrix addition
[ ("combined_transcripts.html", ""), ("combined_transcripts.low.html", ".low"), ("combined_transcripts.high.html", ".high"), ("combined_transcripts.low.compact.html", ".low.compact"), ("combined_transcripts.minimal.html", ".minimal"), + ("combined_transcripts.user-only.html", ".user-only"), ],🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/test_output_paths.py` around lines 50 - 55, The discovery regex used in _enumerate_project_variants (the pattern defined in utils.py that currently uses [a-z]+ to match variant suffixes) rejects hyphenated variants like "user-only"; update that pattern to accept hyphens (e.g., change [a-z]+ to [a-z-]+ or an equivalent class) so filenames like combined_transcripts.user-only.html are matched, and add a unit test in test_output_paths (the test matrix around the existing cases) asserting that variant_suffix(DetailLevel.USER_ONLY, ..., "html") and discovery recognise ".user-only" outputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@claude_code_log/renderer.py`:
- Around line 1904-1907: The code treats QueueOperationTranscriptEntry.content
strings as empty and drops steering payloads; update the logic around
QueueOperationTranscriptEntry handling (where message_content is assigned) so
that if content is a list it remains as-is, if content is a str it is preserved
by wrapping it into a single-element list (e.g., message_content = content if
isinstance(content, list) else [content] if isinstance(content, str) else []),
and apply the same fix to the similar block later (the 2004-2021 region) so
string `queue-operation remove` payloads are retained.
In `@test/test_output_paths.py`:
- Around line 50-55: The discovery regex used in _enumerate_project_variants
(the pattern defined in utils.py that currently uses [a-z]+ to match variant
suffixes) rejects hyphenated variants like "user-only"; update that pattern to
accept hyphens (e.g., change [a-z]+ to [a-z-]+ or an equivalent class) so
filenames like combined_transcripts.user-only.html are matched, and add a unit
test in test_output_paths (the test matrix around the existing cases) asserting
that variant_suffix(DetailLevel.USER_ONLY, ..., "html") and discovery recognise
".user-only" outputs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b2bc18f9-44d1-4337-b3b3-e5df5021aa28
📒 Files selected for processing (6)
README.mdclaude_code_log/cli.pyclaude_code_log/models.pyclaude_code_log/renderer.pytest/test_detail_levels.pytest/test_output_paths.py
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
claude_code_log/renderer.py (1)
1904-1907:⚠️ Potential issue | 🟠 MajorMake steering retention complete and hierarchy-consistent.
QueueOperationTranscriptEntry.removecan carry steering as a plain string, but Line 1906 converts non-list content to[], so those valid steering messages are dropped before the new allowlist runs. Also, allowing queue operations only for MINIMAL/USER_ONLY makes MINIMAL contain user steering that LOW/HIGH still drop, which breaks the documentedfull > high > low > minimal > user-onlyordering.🐛 Proposed fix
- if isinstance(message, QueueOperationTranscriptEntry): - content = message.content - message_content = content if isinstance(content, list) else [] + if isinstance(message, QueueOperationTranscriptEntry): + content = message.content + if isinstance(content, list): + message_content = content + elif isinstance(content, str) and content.strip(): + message_content = [TextContent(type="text", text=content)] + else: + message_content = [] else: message_content = message.message.content- # MINIMAL and USER_ONLY keep queue-operation entries so their + # All non-FULL detail levels keep queue-operation entries so their # UserSteeringMessage survives — steering prompts carry real user - # intent and belong in any view that claims to preserve the user's - # side of the conversation. HIGH/LOW continue to drop them. - allowed_types: tuple[type, ...] = (UserTranscriptEntry, AssistantTranscriptEntry) - if detail in (DetailLevel.MINIMAL, DetailLevel.USER_ONLY): - allowed_types = (*allowed_types, QueueOperationTranscriptEntry) + # intent and belong in views that preserve the user's side of the + # conversation. + allowed_types: tuple[type, ...] = ( + UserTranscriptEntry, + AssistantTranscriptEntry, + QueueOperationTranscriptEntry, + )if isinstance(message, QueueOperationTranscriptEntry): - message_content = message.content if message.content else [] + content = message.content + if isinstance(content, list): + message_content = content + elif isinstance(content, str) and content.strip(): + message_content = [TextContent(type="text", text=content)] + else: + message_content = [] else: message_content = message.message.content # type: ignoreAlso applies to: 2004-2021, 2161-2166
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@claude_code_log/renderer.py` around lines 1904 - 1907, QueueOperationTranscriptEntry can carry steering as a plain string but the current branch drops non-list content by setting message_content = []; change that to preserve single-string steering by converting non-list content into a single-element list (message_content = [content] when content is not None). Also fix the retention allowlist logic so it respects the documented hierarchy (full > high > low > minimal > user-only) by comparing retention levels ordinally rather than checking equality for MINIMAL/USER_ONLY only — use the enum/order comparison (e.g., retention_level >= MINIMAL or numeric enum comparison) wherever queue operation allowance is decided (references: QueueOperationTranscriptEntry, message_content, and the retention_level/FULL/HIGH/LOW/MINIMAL/USER_ONLY checks in the rendering/allowlist code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@claude_code_log/renderer.py`:
- Around line 1904-1907: QueueOperationTranscriptEntry can carry steering as a
plain string but the current branch drops non-list content by setting
message_content = []; change that to preserve single-string steering by
converting non-list content into a single-element list (message_content =
[content] when content is not None). Also fix the retention allowlist logic so
it respects the documented hierarchy (full > high > low > minimal > user-only)
by comparing retention levels ordinally rather than checking equality for
MINIMAL/USER_ONLY only — use the enum/order comparison (e.g., retention_level >=
MINIMAL or numeric enum comparison) wherever queue operation allowance is
decided (references: QueueOperationTranscriptEntry, message_content, and the
retention_level/FULL/HIGH/LOW/MINIMAL/USER_ONLY checks in the
rendering/allowlist code).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 291c77f8-64f4-48ca-b941-22292f553933
📒 Files selected for processing (2)
claude_code_log/renderer.pytest/test_detail_levels.py
✅ Files skipped from review due to trivial changes (1)
- test/test_detail_levels.py
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
claude_code_log/renderer.py (1)
2111-2128:⚠️ Potential issue | 🟠 MajorScrub assistant-derived metadata for
USER_ONLY.Dropping
AssistantTextMessageonly after session metadata/header creation can still leave assistant-derived summaries, title text, token summaries, or empty assistant-only session headers inuser-onlyoutput. Please add aUSER_ONLY-specific scrub/drop step forSessionHeaderMessage/navigation metadata, or move assistant exclusion earlier while preserving any needed structural headers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@claude_code_log/renderer.py` around lines 2111 - 2128, When DetailLevel.USER_ONLY is selected, currently assistant-derived types are only filtered later which can leave assistant-only session headers/metadata (e.g., SessionHeaderMessage, navigation/title/token summaries) in the output; update the USER_ONLY branch to specifically remove or transform SessionHeaderMessage and any assistant-derived metadata types before building headers by extending _USER_ONLY_EXCLUDE_CLASSES or adding an explicit scrub step that drops instances of SessionHeaderMessage, AssistantTextMessage, and any navigation/summary message classes from the messages iterable (refer to DetailLevel, _USER_ONLY_EXCLUDE_CLASSES, SessionHeaderMessage, AssistantTextMessage, messages, and TemplateMessage) so assistant-only metadata never appears in user-only outputs while preserving needed structural headers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@claude_code_log/renderer.py`:
- Around line 2111-2128: When DetailLevel.USER_ONLY is selected, currently
assistant-derived types are only filtered later which can leave assistant-only
session headers/metadata (e.g., SessionHeaderMessage, navigation/title/token
summaries) in the output; update the USER_ONLY branch to specifically remove or
transform SessionHeaderMessage and any assistant-derived metadata types before
building headers by extending _USER_ONLY_EXCLUDE_CLASSES or adding an explicit
scrub step that drops instances of SessionHeaderMessage, AssistantTextMessage,
and any navigation/summary message classes from the messages iterable (refer to
DetailLevel, _USER_ONLY_EXCLUDE_CLASSES, SessionHeaderMessage,
AssistantTextMessage, messages, and TemplateMessage) so assistant-only metadata
never appears in user-only outputs while preserving needed structural headers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f6ff615a-6f72-4c14-a39a-b598fde2481e
📒 Files selected for processing (2)
claude_code_log/renderer.pytest/test_detail_levels.py
✅ Files skipped from review due to trivial changes (1)
- test/test_detail_levels.py
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
claude_code_log/renderer.py (2)
613-615:⚠️ Potential issue | 🟠 MajorPrevent session summaries from leaking into
user-onlyoutput.
USER_ONLYremoves assistant messages, but session summaries are collected before detail filtering and later attached to survivingSessionHeaderMessages. That can still expose summary/assistant-derived text in output intended for downstream agents as user-authored content only.🛡️ Proposed fix
- session_summaries = prepare_session_summaries(messages) + session_summaries = ( + {} + if detail == DetailLevel.USER_ONLY + else prepare_session_summaries(messages) + )Also applies to: 2111-2113
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@claude_code_log/renderer.py` around lines 613 - 615, The session summaries are generated from the full messages list before the USER_ONLY filter and so assistant-derived content can be re-attached to SessionHeaderMessage outputs; to fix, run prepare_session_summaries only after applying the USER_ONLY/detail filtering (or pass the already-filtered messages into prepare_session_summaries) so that session_summaries are derived solely from surviving user messages; update the code around log_timing("Session summary processing", t_start) to call prepare_session_summaries(messages_filtered) (or move the filtering earlier) and ensure any attachment logic for SessionHeaderMessage uses the filtered set.
2181-2207:⚠️ Potential issue | 🟡 MinorTreat steering messages as user-authored session starters.
QueueOperationTranscriptEntrycontent is now normalized and rendered asUserSteeringMessage, but session preview/navigation still depends onas_user_entry(message). A session containing only steering content can be rendered while being skipped byprepare_session_navigation()becausefirst_user_messageremains empty.🧭 Proposed fix
text_content = extract_text_content(message_content) + is_user_authored = ( + isinstance(message, QueueOperationTranscriptEntry) + or as_user_entry(message) is not None + ) # Get session info session_id = getattr(message, "sessionId", "unknown") @@ # Get first user message content for preview first_user_message = "" - if as_user_entry(message) and should_use_as_session_starter(text_content): + if is_user_authored and should_use_as_session_starter(text_content): first_user_message = create_session_preview(text_content) @@ - elif as_user_entry(message) and not sessions[session_id]["first_user_message"]: + elif is_user_authored and not sessions[session_id]["first_user_message"]: if should_use_as_session_starter(text_content): sessions[session_id]["first_user_message"] = create_session_preview( text_content )Also applies to: 2223-2228
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@claude_code_log/renderer.py` around lines 2181 - 2207, The session-starter logic currently only uses as_user_entry(message) so messages normalized from QueueOperationTranscriptEntry into UserSteeringMessage are ignored; update the check that decides first_user_message/session initialization to treat steering messages as user-authored by either extending as_user_entry to return true for QueueOperationTranscriptEntry or by checking message_content (from _queue_op_content_as_list) for any UserSteeringMessage items and treating that as a user entry; then call should_use_as_session_starter(text_content) and create_session_preview(text_content) the same way for steering messages. Apply the same change to the analogous block referenced around 2223-2228 so steering-only sessions produce a preview and are included in prepare_session_navigation().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@claude_code_log/renderer.py`:
- Around line 613-615: The session summaries are generated from the full
messages list before the USER_ONLY filter and so assistant-derived content can
be re-attached to SessionHeaderMessage outputs; to fix, run
prepare_session_summaries only after applying the USER_ONLY/detail filtering (or
pass the already-filtered messages into prepare_session_summaries) so that
session_summaries are derived solely from surviving user messages; update the
code around log_timing("Session summary processing", t_start) to call
prepare_session_summaries(messages_filtered) (or move the filtering earlier) and
ensure any attachment logic for SessionHeaderMessage uses the filtered set.
- Around line 2181-2207: The session-starter logic currently only uses
as_user_entry(message) so messages normalized from QueueOperationTranscriptEntry
into UserSteeringMessage are ignored; update the check that decides
first_user_message/session initialization to treat steering messages as
user-authored by either extending as_user_entry to return true for
QueueOperationTranscriptEntry or by checking message_content (from
_queue_op_content_as_list) for any UserSteeringMessage items and treating that
as a user entry; then call should_use_as_session_starter(text_content) and
create_session_preview(text_content) the same way for steering messages. Apply
the same change to the analogous block referenced around 2223-2228 so
steering-only sessions produce a preview and are included in
prepare_session_navigation().
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ce539286-3487-4859-887b-bbafcf68ded7
📒 Files selected for processing (1)
claude_code_log/renderer.py
|
Thanks for the two follow-up findings. Acknowledging both, declining both, reasoning below: 1. "Prevent session summaries from leaking into
|
|
Tip For best results, initiate chat on the files or code changes.
I'll resolve the open comments now. [resolve] ✏️ Learnings added
|
Inspired by #26 (requirements extraction): a fifth detail level that keeps only the user's own conversational input, making the output directly feedable to a downstream agent that builds a requirements doc, changelog from intent, etc. Everything that came from Claude Code — replies, thinking, tool_use, tool_result, bash input/output, slash commands, hooks, system, memory — is excluded. - `DetailLevel.USER_ONLY = "user-only"` added to the enum (between MINIMAL and the end, giving the documented ordering full > high > low > minimal > user-only). - `_USER_ONLY_EXCLUDE_CLASSES` extends `_MINIMAL_EXCLUDE_CLASSES` with `AssistantTextMessage`. Wired into `_filter_template_by_detail` and mirrored in the pre-render `_filter_by_detail` (same stripping of Thinking/ToolUse/ToolResult content items, same sidechain drop). - `_filter_by_detail` additionally allows `QueueOperationTranscriptEntry` through at USER_ONLY so queue-operation 'remove' entries survive to become UserSteeringMessage — steering prompts carry real user precisions ("actually, wait — use Postgres not MySQL"). Other levels continue to drop them; the MINIMAL asymmetry is pre-existing and left untouched to keep this change scoped. - CLI `--detail` choice list gets `user-only`; help text mentions the downstream-agent use case. - README level list updated (ordered smallest → largest as existing). - Output filename: `variant_suffix(DetailLevel.USER_ONLY)` returns `.user-only` automatically since the enum value already has that string form — no helper change needed. `TestUserOnlyTemplateMessages` in test_detail_levels.py covers the six axes: assistant-text drop, steering retention, tool/thinking/ bash/slash drop, sidechain drop, session-header preservation, and size-vs-MINIMAL invariant. `TestUserOnlyCli` runs the CLI end-to-end and checks the `.user-only.html` filename is emitted. `TestVariantSuffix` gains two USER_ONLY asserts (html + md). All 921 unit tests pass (7 new).
The previous commit added a USER_ONLY-specific carve-out in `_filter_by_detail` so QueueOperationTranscriptEntry entries survive to become UserSteeringMessages, and noted the resulting asymmetry with MINIMAL. Per review: MINIMAL was dropping steering by omission and that's the bug, not USER_ONLY being special. Promote the carve-out to also apply at MINIMAL. Both levels now keep queue-operation entries so steering prompts survive. HIGH/LOW continue to drop them (their exclude chain is explicit about signal-vs-noise and steering-less was the status quo; leaving them alone avoids scope creep). Add `test_minimal_keeps_user_steering` mirroring the USER_ONLY test. The pre-existing `test_minimal_representative_data` already had `user-steering` in its allowed-type set but no fixture actually exercised the path — the new test closes that gap.
Two fixes from monk's review (msg 2224):
## Regex blocker
VARIANT_ENTRY_RE uses `\.[a-z]+` per suffix segment, rejecting the
hyphen in `user-only`. Files land on disk as
`combined_transcripts.user-only.html`, are globbed by
`_enumerate_project_variants`, then silently filtered out by the
regex — the variant disappears from the index card. Fix:
`[a-z]+` → `[a-z-]+` (hyphen at the end of the class so it stays a
literal and not a range boundary).
Regression coverage:
- `TestVariantEntryRegex::test_accepts_entry_points` gains
`user-only` + `user-only.compact` parameter cases.
- `TestEnumerateProjectVariants::test_lists_all_variants_default_first`
now plants a `combined_transcripts.user-only.html` file alongside
the others and asserts it survives the glob→regex pipeline,
catching any future re-tightening of the regex.
## Label polish
`_variant_label_from_suffix(".user-only")` used to render
"User-only" (capitalize only touches the first character). UI
reads more naturally as "User only" (space). One-line change in
the comprehension: `.capitalize().replace("-", " ")` on non-
`compact` segments. Asserted in the enumeration test above.
CLI help, enum values, and filenames keep the hyphenated
`user-only` form — the space is purely UI rendering.
CodeRabbit flagged two remaining issues on top of 16596be: 1. The MINIMAL/USER_ONLY-only carve-out for `QueueOperationTranscriptEntry` broke the documented `full > high > low > minimal > user-only` hierarchy: MINIMAL kept steering but LOW/HIGH still dropped it, which is backwards (each higher level should keep at least what the level below keeps). The post-render exclude chains (_HIGH/_LOW/_MINIMAL/_USER_ONLY) never listed `UserSteeringMessage`, so the pre-render allowlist was the only thing filtering it out. 2. `QueueOperationTranscriptEntry.content` is declared `Optional[Union[list[ContentItem], str]]`. Two filter sites (`_filter_messages` line 1904, session-info collection line 2161) coerced non-list content to `[]`, silently dropping steering prompts that arrive as plain strings. Higher-level fix instead of patch-over-patch: - `_filter_by_detail` allows `QueueOperationTranscriptEntry` at every non-FULL level unconditionally. Steering is user-authored content; every view claiming to preserve the user's side must keep it. - New `_queue_op_content_as_list` helper wraps a non-empty string in `[TextContent(type="text", text=content)]` so downstream chunkers see a uniform list. Both filter sites use it. ## Tests - `TestSteeringHierarchy` (parametrised over HIGH/LOW/MINIMAL/USER_ONLY): asserts UserSteeringMessage survives at every non-FULL level. - `TestSteeringStringContent`: asserts a plain-string queue-operation content produces a `UserSteeringMessage` (regression for the `message_content = []` coercion bug). 929 unit tests pass (+5 over the previous submission — 4 hierarchy params + 1 string-content).
Monk's round-3 review flagged two `# type: ignore` comments that ty reports as "unused blanket `type: ignore` directive" — both slated for removal to match the zero-diagnostic baseline `ty` reports on main. 1. `renderer.py:1873` — `return content # type: ignore[return-value]` in `_queue_op_content_as_list`. The `isinstance(content, list)` narrow already types the return as `list`, so the ignore is redundant. Delete it. 2. `renderer.py:2187` — `extract_text_content(message_content) # type: ignore[arg-type]` in `_collect_session_info`. Removing the ignore exposes that `message_content` was untyped because the else branch used a blanket `# type: ignore` on `message.message.content`. Fix the root cause: add a `message_content: list[ContentItem]` annotation and replace the blanket ignore with an explicit `cast(UserTranscriptEntry | AssistantTranscriptEntry, message)` that narrows the union to the two members that actually reach this branch (System / Summary / Passthrough are filtered out upstream in `_filter_messages`). Result: both `uv run pyright` and `uv run ty check` now pass with zero diagnostics on the modified file, matching the main baseline.
2ce983d to
8c32255
Compare
Fifth detail level inspired by #26 ("have claude to collect all stated requirements into a formal requirements.md file"): a terser output than
--detail minimalthat keeps only content the user authored, so the transcript can be fed directly to a downstream agent that extracts requirements, builds a changelog-by-intent, etc.Survivors at
--detail user-onlyUserTextMessage— real user promptsUserSteeringMessage—queue-operation 'remove'entries whose payload carries user precisions ("actually, wait — use Postgres not MySQL")SessionHeaderMessage— orientation markers so per-session boundaries remain visibleEverything else drops via
_USER_ONLY_EXCLUDE_CLASSES = (*_MINIMAL_EXCLUDE_CLASSES, AssistantTextMessage): assistant text, thinking, tool_use/tool_result, bash input/output, slash commands, command output, compacted summaries, memory, hooks, system.Level ordering
As documented in
DetailLevel:full > high > low > minimal > user-only(smallest output last). Steering is user-authored content and now survives at every non-FULL level — each level keeps at least what the smaller level keeps.Output filename
Automatic —
variant_suffix()already concatenates the enum value, so the new variant lands as*.user-only.html/*.user-only.md. Covered byTestVariantSuffix.UserSteeringMessage retention across levels (hierarchy fix)
_filter_by_detailused to drop everyQueueOperationTranscriptEntrybefore the post-render filter got to look at it, silently removingUserSteeringMessagefrom HIGH/LOW/MINIMAL output. The post-render exclude chains (_HIGH/_LOW/_MINIMAL/_USER_ONLY_EXCLUDE_CLASSES) never listedUserSteeringMessage, so this was the only thing filtering it out.Fix:
_filter_by_detailnow allowsQueueOperationTranscriptEntryat every non-FULL level unconditionally. Steering is user-authored content — every view that claims to preserve the user's side of the conversation keeps it.TestSteeringHierarchyis parametrised over HIGH/LOW/MINIMAL/USER_ONLY to lock the behaviour.String-content queue-operation fix
QueueOperationTranscriptEntry.contentisOptional[Union[list[ContentItem], str]]. Two pre-render sites (_filter_messagesline 1904, session-info collection line 2161) were coercing non-list content to[], silently dropping steering that arrives as a plain string.Fix: new
_queue_op_content_as_listhelper wraps non-empty strings in a singleTextContentso downstream filters see a uniform list.TestSteeringStringContentcovers the regression.Index enumeration fix (regex)
VARIANT_ENTRY_REinutils.pyused\.[a-z]+per suffix segment, rejecting the hyphen inuser-only. The.user-only.htmlfile was written to disk and picked up by_enumerate_project_variants' glob, but then silently dropped by the regex.Fix:
[a-z]+→[a-z-]+(hyphen at end of class, literal). Covered byTestVariantEntryRegex::test_accepts_entry_points(two new param cases) andTestEnumerateProjectVariants::test_lists_all_variants_default_first(now plants a user-only fixture file).UI label polish
_variant_label_from_suffix(".user-only")now renders as"User only"(space) rather than"User-only"(hyphen). The hyphenated form is kept for the CLI flag, enum value, and on-disk filename — only the human-facing label is polished.Tests
TestUserOnlyTemplateMessages(6 tests): assistant-text drop, steering retention, tools/thinking/bash/slash drop, sidechain drop, session-header preservation, size-vs-MINIMAL invariant.TestUserOnlyCli: CLI accepts--detail user-onlyand emits*.user-only.html.TestMinimalTemplateMessages::test_minimal_keeps_user_steering: MINIMAL path for steering retention.TestSteeringHierarchy(parametrised over HIGH/LOW/MINIMAL/USER_ONLY): hierarchy consistency.TestSteeringStringContent: string-content queue-operation produces a UserSteeringMessage.TestVariantSuffix: USER_ONLY suffix correct for html + md.TestVariantEntryRegex::test_accepts_entry_points: hyphenated suffix cases.TestEnumerateProjectVariants::test_lists_all_variants_default_first: user-only fixture + rendered-label assertion.Full
just test-equivalent run (skipping pre-existing pagination SQLite failures in my local env): 929 passed, 7 skipped.ruff format+ruff check+pyrightclean on modified files.Refs #26.
Summary by CodeRabbit
New Features
Documentation
Output
Tests