refactor(cli): deduplicate session list summary logic in list_sessions #3320

Merged
freemo merged 1 commit from fix/session-list-summary-dedup into master 2026-04-05 21:07:27 +00:00
Owner

Summary

Closes #3046.

The list_sessions command was recalculating total_msgs, sorted_sessions, most_recent, and oldest independently, duplicating logic already present in _session_list_dict. This meant the summary values shown in the rich table could theoretically diverge from the JSON output if either code path was updated without updating the other.

Changes

src/cleveragents/cli/commands/session.py

  • Removed the duplicated total_msgs, sorted_sessions, most_recent, and oldest calculations from list_sessions (lines ~327–341 in the original).
  • Replaced with a single summary = data["summary"] lookup — data is already the return value of _session_list_dict(sessions), which is called earlier in the same function for non-rich formats.
  • The rich table summary panel now reads summary["total"], summary["most_recent"], summary["oldest"], summary["total_messages"], and summary["storage"] directly from the centralised dict.

features/session_list_summary_dedup.feature (new)

Six Behave scenarios verifying:

  1. JSON summary total matches the value shown in the rich table.
  2. JSON summary total_messages matches the rich table.
  3. JSON summary most_recent appears in the rich table summary panel.
  4. JSON summary oldest appears in the rich table summary panel.
  5. Rich table summary panel contains all required field labels (Total:, Most Recent:, Oldest:, Total Messages:, Storage:).
  6. JSON output summary dict contains all required keys with correct types.

features/steps/session_list_summary_dedup_steps.py (new)

Step definitions for the above feature. Uses a mock SessionService with two deterministic sessions (one named, one unnamed; different updated_at timestamps) to produce predictable summary values for cross-format comparison.

Quality Gates

  • nox -s lint
  • nox -s typecheck
  • nox -s format
  • nox -s unit_tests -- features/session_list_summary_dedup.feature (6/6 scenarios pass)
  • nox -s unit_tests -- features/session_list_error.feature (10/10 scenarios pass — no regression)
  • nox -s unit_tests -- features/session_cli_coverage_boost.feature (35/35 scenarios pass — no regression)

Automated by CleverAgents Bot
Supervisor: Implementation | Agent: ca-issue-worker

## Summary Closes #3046. The `list_sessions` command was recalculating `total_msgs`, `sorted_sessions`, `most_recent`, and `oldest` independently, duplicating logic already present in `_session_list_dict`. This meant the summary values shown in the rich table could theoretically diverge from the JSON output if either code path was updated without updating the other. ## Changes ### `src/cleveragents/cli/commands/session.py` - **Removed** the duplicated `total_msgs`, `sorted_sessions`, `most_recent`, and `oldest` calculations from `list_sessions` (lines ~327–341 in the original). - **Replaced** with a single `summary = data["summary"]` lookup — `data` is already the return value of `_session_list_dict(sessions)`, which is called earlier in the same function for non-rich formats. - The rich table summary panel now reads `summary["total"]`, `summary["most_recent"]`, `summary["oldest"]`, `summary["total_messages"]`, and `summary["storage"]` directly from the centralised dict. ### `features/session_list_summary_dedup.feature` *(new)* Six Behave scenarios verifying: 1. JSON summary `total` matches the value shown in the rich table. 2. JSON summary `total_messages` matches the rich table. 3. JSON summary `most_recent` appears in the rich table summary panel. 4. JSON summary `oldest` appears in the rich table summary panel. 5. Rich table summary panel contains all required field labels (`Total:`, `Most Recent:`, `Oldest:`, `Total Messages:`, `Storage:`). 6. JSON output `summary` dict contains all required keys with correct types. ### `features/steps/session_list_summary_dedup_steps.py` *(new)* Step definitions for the above feature. Uses a mock `SessionService` with two deterministic sessions (one named, one unnamed; different `updated_at` timestamps) to produce predictable summary values for cross-format comparison. ## Quality Gates - `nox -s lint` ✅ - `nox -s typecheck` ✅ - `nox -s format` ✅ - `nox -s unit_tests -- features/session_list_summary_dedup.feature` ✅ (6/6 scenarios pass) - `nox -s unit_tests -- features/session_list_error.feature` ✅ (10/10 scenarios pass — no regression) - `nox -s unit_tests -- features/session_cli_coverage_boost.feature` ✅ (35/35 scenarios pass — no regression) --- **Automated by CleverAgents Bot** Supervisor: Implementation | Agent: ca-issue-worker
refactor(cli): deduplicate session list summary logic in list_sessions
All checks were successful
CI / lint (pull_request) Successful in 21s
CI / typecheck (pull_request) Successful in 46s
CI / security (pull_request) Successful in 1m1s
CI / quality (pull_request) Successful in 35s
CI / build (pull_request) Successful in 16s
CI / helm (pull_request) Successful in 23s
CI / unit_tests (pull_request) Successful in 7m4s
CI / e2e_tests (pull_request) Successful in 18m8s
CI / integration_tests (pull_request) Successful in 22m40s
CI / docker (pull_request) Successful in 1m19s
CI / coverage (pull_request) Successful in 10m36s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 57m8s
9210fbce33
Refactor list_sessions to reuse the summary dict already computed by
_session_list_dict instead of recalculating total_msgs, sorted_sessions,
most_recent, and oldest independently.

The rich table summary panel now reads directly from data["summary"],
ensuring JSON and rich table output are always consistent and the
summary logic lives in exactly one place (_session_list_dict).

Add Behave feature (session_list_summary_dedup.feature) with 6 scenarios
verifying that JSON and rich table summary values are consistent across
all required fields: total, most_recent, oldest, total_messages, storage.

ISSUES CLOSED: #3046
Author
Owner

🔒 Review claimed by reviewer-pool-1 [claim-token: reviewer-pool-1-3320-1775375100]


Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-continuous-pr-reviewer

🔒 Review claimed by reviewer-pool-1 [claim-token: reviewer-pool-1-3320-1775375100] --- **Automated by CleverAgents Bot** Supervisor: PR Review | Agent: ca-continuous-pr-reviewer
Author
Owner

🔒 Review claimed by reviewer-pool-1 [claim-token: reviewer-pool-1-3320-1775373400]


Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-continuous-pr-reviewer

🔒 Review claimed by reviewer-pool-1 [claim-token: reviewer-pool-1-3320-1775373400] --- **Automated by CleverAgents Bot** Supervisor: PR Review | Agent: ca-continuous-pr-reviewer
freemo left a comment

Review Summary

Reviewed PR #3320 with focus on resource-management, memory-leaks, and cleanup-patterns.

This is a clean, well-scoped refactoring that eliminates duplicated summary logic in list_sessions. The rich table summary panel now reads from the same data["summary"] dict already computed by _session_list_dict(), ensuring JSON and rich output can never diverge.

Reviewer verdict: APPROVED (posted as COMMENT due to Forgejo self-review restriction)

Specification Compliance

The change correctly centralises the summary computation in _session_list_dict(), which is the single source of truth for session list output. The rich table still renders all required fields (Total, Most Recent, Oldest, Total Messages, Storage) per spec.

Resource Management (Deep Dive — Focus Area)

Given special attention to resource management, memory leaks, and cleanup patterns:

  • Net resource improvement: The PR removes a redundant sorted() call and temporary list allocation that existed in the old list_sessions code. The summary is now computed once (in _session_list_dict) rather than twice. This eliminates an O(n log n) sort and associated temporary memory.
  • No new allocations: summary = data["summary"] is a dict reference, not a copy. No new resources are created.
  • Test cleanup is correct: Step definitions use patch() as a context manager, ensuring mocks are properly restored after each step. No leaked state between scenarios.
  • Module-level CliRunner(): Lightweight, stateless — appropriate for test infrastructure. No resource concern.
  • No file handles, connections, or external resources are introduced or left unclosed by this change.

Code Correctness

  • The or "" fallback on summary["most_recent"] and summary["oldest"] is defensive but harmless — these values cannot be None when sessions is non-empty (the empty case returns early at line ~280).
  • The refactoring preserves identical output for all code paths.

Commit & PR Metadata

  • Commit message follows Conventional Changelog format: refactor(cli): deduplicate session list summary logic in list_sessions
  • Footer contains ISSUES CLOSED: #3046
  • PR body contains Closes #3046
  • Type/Refactor label present
  • Single atomic commit

Test Quality

Six well-structured BDD scenarios covering:

  1. Cross-format consistency for total, total_messages, most_recent, oldest
  2. Rich table field presence (all 5 required labels)
  3. JSON summary field presence with type assertions

Tests use deterministic fixtures (fixed ULIDs, fixed timestamps) ensuring reproducible results. The mock pattern (inline patch + MagicMock(spec=SessionService)) follows the established codebase convention.

Minor Suggestions (Non-blocking)

  1. Missing milestone: The linked issue #3046 is assigned to milestone v3.7.0, but this PR has no milestone set. Per CONTRIBUTING.md, PRs should be assigned to the same milestone as their linked issue. The implementation worker should set this before merge.

  2. Pre-existing: duplicate print in show command (not introduced by this PR): The show command has two identical console.print("[green bold]✓ OK[/green bold] Session details loaded") lines at the end. Consider filing a separate issue to clean this up.

  3. Pre-existing: contextlib.suppress(Exception) in create command (not introduced by this PR): The create command silently swallows all exceptions from the facade dispatch call. This conflicts with the project's "errors must not be suppressed" principle. Consider filing a separate issue.

Decision: APPROVED

The code change is correct, well-tested, and improves resource efficiency by eliminating redundant computation. The missing milestone is a metadata issue that should be fixed before merge but does not warrant blocking the review.


Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-pr-self-reviewer

## Review Summary Reviewed PR #3320 with focus on **resource-management**, **memory-leaks**, and **cleanup-patterns**. This is a clean, well-scoped refactoring that eliminates duplicated summary logic in `list_sessions`. The rich table summary panel now reads from the same `data["summary"]` dict already computed by `_session_list_dict()`, ensuring JSON and rich output can never diverge. **Reviewer verdict: APPROVED** ✅ (posted as COMMENT due to Forgejo self-review restriction) ### ✅ Specification Compliance The change correctly centralises the summary computation in `_session_list_dict()`, which is the single source of truth for session list output. The rich table still renders all required fields (Total, Most Recent, Oldest, Total Messages, Storage) per spec. ### ✅ Resource Management (Deep Dive — Focus Area) Given special attention to resource management, memory leaks, and cleanup patterns: - **Net resource improvement**: The PR removes a redundant `sorted()` call and temporary list allocation that existed in the old `list_sessions` code. The summary is now computed once (in `_session_list_dict`) rather than twice. This eliminates an O(n log n) sort and associated temporary memory. - **No new allocations**: `summary = data["summary"]` is a dict reference, not a copy. No new resources are created. - **Test cleanup is correct**: Step definitions use `patch()` as a context manager, ensuring mocks are properly restored after each step. No leaked state between scenarios. - **Module-level `CliRunner()`**: Lightweight, stateless — appropriate for test infrastructure. No resource concern. - **No file handles, connections, or external resources** are introduced or left unclosed by this change. ### ✅ Code Correctness - The `or ""` fallback on `summary["most_recent"]` and `summary["oldest"]` is defensive but harmless — these values cannot be `None` when `sessions` is non-empty (the empty case returns early at line ~280). - The refactoring preserves identical output for all code paths. ### ✅ Commit & PR Metadata - Commit message follows Conventional Changelog format: `refactor(cli): deduplicate session list summary logic in list_sessions` ✅ - Footer contains `ISSUES CLOSED: #3046` ✅ - PR body contains `Closes #3046` ✅ - `Type/Refactor` label present ✅ - Single atomic commit ✅ ### ✅ Test Quality Six well-structured BDD scenarios covering: 1. Cross-format consistency for `total`, `total_messages`, `most_recent`, `oldest` 2. Rich table field presence (all 5 required labels) 3. JSON summary field presence with type assertions Tests use deterministic fixtures (fixed ULIDs, fixed timestamps) ensuring reproducible results. The mock pattern (inline `patch` + `MagicMock(spec=SessionService)`) follows the established codebase convention. ### Minor Suggestions (Non-blocking) 1. **Missing milestone**: The linked issue #3046 is assigned to milestone `v3.7.0`, but this PR has no milestone set. Per CONTRIBUTING.md, PRs should be assigned to the same milestone as their linked issue. The implementation worker should set this before merge. 2. **Pre-existing: duplicate print in `show` command** (not introduced by this PR): The `show` command has two identical `console.print("[green bold]✓ OK[/green bold] Session details loaded")` lines at the end. Consider filing a separate issue to clean this up. 3. **Pre-existing: `contextlib.suppress(Exception)` in `create` command** (not introduced by this PR): The `create` command silently swallows all exceptions from the facade dispatch call. This conflicts with the project's "errors must not be suppressed" principle. Consider filing a separate issue. **Decision: APPROVED** ✅ The code change is correct, well-tested, and improves resource efficiency by eliminating redundant computation. The missing milestone is a metadata issue that should be fixed before merge but does not warrant blocking the review. --- **Automated by CleverAgents Bot** Supervisor: PR Review | Agent: ca-pr-self-reviewer
freemo added this to the v3.7.0 milestone 2026-04-05 17:47:30 +00:00
freemo left a comment

Review Summary (Independent Review — Error Handling & Edge Cases Focus)

Reviewed PR #3320 with focus on error-handling-patterns, edge-cases, and boundary-conditions.

Reviewer verdict: APPROVED (posted as COMMENT due to Forgejo self-review restriction)

This is a well-scoped refactoring that eliminates duplicated summary computation in list_sessions. The rich table summary panel now reads from the centralised data["summary"] dict already computed by _session_list_dict(), ensuring JSON and rich output can never diverge.

Specification Compliance

The change correctly centralises the summary computation in _session_list_dict(), which becomes the single source of truth for session list output. The rich table still renders all five required fields (Total, Most Recent, Oldest, Total Messages, Storage) per spec.

Error Handling Patterns (Deep Dive — Focus Area)

Given special attention to error handling:

  • No new unhandled error paths introduced. The refactoring replaces inline computation with dict lookups on data["summary"]. Since _session_list_dict() always returns a dict with a "summary" key containing all required sub-keys (total, most_recent, oldest, total_messages, storage), there is no KeyError risk.
  • Existing error handling preserved. The DatabaseError and SessionNotFoundError catch blocks in list_sessions are untouched. The early return for empty sessions (if not sessions: ... return) still guards against the None-valued summary fields.
  • Defensive or "" fallback is safe. The summary["most_recent"] or "" and summary["oldest"] or "" expressions are defensive — when sessions is non-empty (the only case that reaches this code), _session_list_dict always produces non-None string values for these keys. The fallback can never trigger in practice, but it's harmless and guards against future changes to _session_list_dict.

Edge Cases & Boundary Conditions (Deep Dive — Focus Area)

Examined the following edge cases:

  1. Single session: When len(sessions) == 1, sorted_sessions[0] and sorted_sessions[-1] are the same object. Both most_recent and oldest resolve to the same session name/ID. This is correct and unchanged by the refactoring.
  2. Sessions with name=None: Falls back to session_id[:8] in _session_list_dict. The test fixture includes one named session and one unnamed session (name=None), which exercises this fallback path for the oldest field. Good coverage.
  3. Sessions with identical updated_at: Python's sorted() is stable, so ordering is deterministic. Unchanged.
  4. Empty sessions list: Returns early at the if not sessions: guard before _session_list_dict is called. The empty-list JSON output ({"sessions": [], "total": 0}) is preserved. Note: this output structure differs from _session_list_dict's return shape (which uses "summary" instead of "total"), but this is pre-existing behavior, not introduced by this PR.
  5. message_count computation: _session_list_dict sums s.message_count across all sessions. The test fixture uses sessions with 3 and 2 messages respectively, so total_messages should be 5. This is verified by the cross-format consistency test.

Code Correctness

  • The refactoring is semantically equivalent to the original code. The same values are displayed in the same order.
  • summary = data["summary"] is a dict reference (not a copy), so no unnecessary allocations.
  • The redundant sorted() call and temporary list from the old code are eliminated, providing a minor performance improvement.

Commit & PR Metadata

  • Commit message follows Conventional Changelog: refactor(cli): deduplicate session list summary logic in list_sessions
  • Footer: ISSUES CLOSED: #3046
  • PR body: Closes #3046
  • Type/Refactor label
  • Milestone: v3.7.0 (matches issue)
  • Single atomic commit
  • No # type: ignore suppressions

Test Quality

Six well-structured BDD scenarios covering:

  1. Cross-format consistency for total, total_messages, most_recent, oldest (4 scenarios)
  2. Rich table field presence — all 5 required labels (1 scenario)
  3. JSON summary field presence with type assertions (1 scenario)

Tests use deterministic fixtures (fixed ULIDs, fixed timestamps 2025-08-01 and 2025-07-01) ensuring reproducible results. The mock pattern (patch() context manager + MagicMock(spec=SessionService)) follows established codebase conventions.

Minor Suggestions (Non-blocking)

  1. Missing edge-case test for empty sessions list: While the empty case is handled by the early return and isn't affected by this refactoring, a scenario verifying that list_sessions with zero sessions produces the correct empty-list output would strengthen the test suite. Consider adding in a follow-up.

  2. Pre-existing: duplicate "✓ OK" print in show command (not introduced by this PR): The show command on the branch has two identical console.print("[green bold]✓ OK[/green bold] Session details loaded") lines. This appears to be a pre-existing issue — consider filing a separate bug.

  3. Pre-existing: contextlib.suppress(Exception) in create command (not introduced by this PR): The create command silently swallows all exceptions from the facade dispatch call. This conflicts with the project's fail-fast error handling principle. Consider filing a separate issue.

Decision: APPROVED

The refactoring is correct, well-tested, and achieves its stated goal of eliminating duplicated summary logic. Error handling is preserved, edge cases are properly handled, and the test coverage is meaningful.


Automated by CleverAgents Bot
Supervisor: PR Review | Agent: ca-pr-self-reviewer

## Review Summary (Independent Review — Error Handling & Edge Cases Focus) Reviewed PR #3320 with focus on **error-handling-patterns**, **edge-cases**, and **boundary-conditions**. **Reviewer verdict: APPROVED** ✅ (posted as COMMENT due to Forgejo self-review restriction) This is a well-scoped refactoring that eliminates duplicated summary computation in `list_sessions`. The rich table summary panel now reads from the centralised `data["summary"]` dict already computed by `_session_list_dict()`, ensuring JSON and rich output can never diverge. ### ✅ Specification Compliance The change correctly centralises the summary computation in `_session_list_dict()`, which becomes the single source of truth for session list output. The rich table still renders all five required fields (Total, Most Recent, Oldest, Total Messages, Storage) per spec. ### ✅ Error Handling Patterns (Deep Dive — Focus Area) Given special attention to error handling: - **No new unhandled error paths introduced.** The refactoring replaces inline computation with dict lookups on `data["summary"]`. Since `_session_list_dict()` always returns a dict with a `"summary"` key containing all required sub-keys (`total`, `most_recent`, `oldest`, `total_messages`, `storage`), there is no `KeyError` risk. - **Existing error handling preserved.** The `DatabaseError` and `SessionNotFoundError` catch blocks in `list_sessions` are untouched. The early return for empty sessions (`if not sessions: ... return`) still guards against the None-valued summary fields. - **Defensive `or ""` fallback is safe.** The `summary["most_recent"] or ""` and `summary["oldest"] or ""` expressions are defensive — when `sessions` is non-empty (the only case that reaches this code), `_session_list_dict` always produces non-None string values for these keys. The fallback can never trigger in practice, but it's harmless and guards against future changes to `_session_list_dict`. ### ✅ Edge Cases & Boundary Conditions (Deep Dive — Focus Area) Examined the following edge cases: 1. **Single session**: When `len(sessions) == 1`, `sorted_sessions[0]` and `sorted_sessions[-1]` are the same object. Both `most_recent` and `oldest` resolve to the same session name/ID. This is correct and unchanged by the refactoring. 2. **Sessions with `name=None`**: Falls back to `session_id[:8]` in `_session_list_dict`. The test fixture includes one named session and one unnamed session (`name=None`), which exercises this fallback path for the `oldest` field. Good coverage. 3. **Sessions with identical `updated_at`**: Python's `sorted()` is stable, so ordering is deterministic. Unchanged. 4. **Empty sessions list**: Returns early at the `if not sessions:` guard before `_session_list_dict` is called. The empty-list JSON output (`{"sessions": [], "total": 0}`) is preserved. Note: this output structure differs from `_session_list_dict`'s return shape (which uses `"summary"` instead of `"total"`), but this is pre-existing behavior, not introduced by this PR. 5. **`message_count` computation**: `_session_list_dict` sums `s.message_count` across all sessions. The test fixture uses sessions with 3 and 2 messages respectively, so `total_messages` should be 5. This is verified by the cross-format consistency test. ### ✅ Code Correctness - The refactoring is semantically equivalent to the original code. The same values are displayed in the same order. - `summary = data["summary"]` is a dict reference (not a copy), so no unnecessary allocations. - The redundant `sorted()` call and temporary list from the old code are eliminated, providing a minor performance improvement. ### ✅ Commit & PR Metadata - Commit message follows Conventional Changelog: `refactor(cli): deduplicate session list summary logic in list_sessions` ✅ - Footer: `ISSUES CLOSED: #3046` ✅ - PR body: `Closes #3046` ✅ - `Type/Refactor` label ✅ - Milestone: v3.7.0 (matches issue) ✅ - Single atomic commit ✅ - No `# type: ignore` suppressions ✅ ### ✅ Test Quality Six well-structured BDD scenarios covering: 1. Cross-format consistency for `total`, `total_messages`, `most_recent`, `oldest` (4 scenarios) 2. Rich table field presence — all 5 required labels (1 scenario) 3. JSON summary field presence with type assertions (1 scenario) Tests use deterministic fixtures (fixed ULIDs, fixed timestamps `2025-08-01` and `2025-07-01`) ensuring reproducible results. The mock pattern (`patch()` context manager + `MagicMock(spec=SessionService)`) follows established codebase conventions. ### Minor Suggestions (Non-blocking) 1. **Missing edge-case test for empty sessions list**: While the empty case is handled by the early return and isn't affected by this refactoring, a scenario verifying that `list_sessions` with zero sessions produces the correct empty-list output would strengthen the test suite. Consider adding in a follow-up. 2. **Pre-existing: duplicate "✓ OK" print in `show` command** (not introduced by this PR): The `show` command on the branch has two identical `console.print("[green bold]✓ OK[/green bold] Session details loaded")` lines. This appears to be a pre-existing issue — consider filing a separate bug. 3. **Pre-existing: `contextlib.suppress(Exception)` in `create` command** (not introduced by this PR): The `create` command silently swallows all exceptions from the facade dispatch call. This conflicts with the project's fail-fast error handling principle. Consider filing a separate issue. **Decision: APPROVED** ✅ The refactoring is correct, well-tested, and achieves its stated goal of eliminating duplicated summary logic. Error handling is preserved, edge cases are properly handled, and the test coverage is meaningful. --- **Automated by CleverAgents Bot** Supervisor: PR Review | Agent: ca-pr-self-reviewer
freemo merged commit 13eb2a7c8c into master 2026-04-05 21:07:24 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveragents-core!3320
No description provided.