feat(memory): add --timeline + list_projects to recall CLI#80
Conversation
recent_chats.py: --timeline mode — uncapped, body-less, oldest-first session index (per-row project_path, exchange/tool counts, title). Bypasses the 50-result cap so a project's full arc can be reconstructed. list_projects.py: resolve project name -> path + key + session counts + date span (--match TOKEN). Disambiguates non-unique project names (e.g. two 'EzyCopy'). Both additive and backward-compatible; recall-conversations unaffected. Built for the mine-origin-story skill.
|
Claude finished @gupsammy's task in 2m 29s —— View job PR Review: feat(memory): add --timeline + list_projects to recall CLITasks
What looks good ✅
Should fix before merge
User input is placed directly into a LIKE pattern via Inline comment with a suggested fix posted. Fix this → Optional / low-priority
Both flags are registered via
The no-LIMIT design is correct and clearly motivated. The risk is that |
| params: list = [] | ||
| if match: | ||
| sql += " WHERE p.name LIKE ? OR p.path LIKE ?" | ||
| params.extend([f"%{match}%", f"%{match}%"]) |
There was a problem hiding this comment.
SQL LIKE metacharacters in --match are unescaped: _ matches any single character, % matches any sequence. A search for --match my_project also hits my-project, myXproject, etc. — not the literal substring the docstring promises.
| params.extend([f"%{match}%", f"%{match}%"]) | |
| sql += " WHERE p.name LIKE ? ESCAPE '\\' OR p.path LIKE ? ESCAPE '\\'" | |
| escaped = match.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") | |
| params.extend([f"%{escaped}%", f"%{escaped}%"]) |
| parser.add_argument("--timeline", action="store_true", | ||
| help="Compact UNCAPPED session index (no message bodies, no 50-limit) — " | ||
| "the triage primitive for reconstructing a project's full arc. " | ||
| "Pair with --sort-order asc for oldest-first.") |
There was a problem hiding this comment.
--timeline silently discards --verbose and --summary. Both flags are registered by add_common_args, so they're accepted without error, but neither affects timeline output. A user who runs --timeline --verbose will get no tool breakdown despite verbose being listed in args. Either add a warning or explicitly exclude the flags from the timeline argument group.
| "Pair with --sort-order asc for oldest-first.") | |
| parser.add_argument("--timeline", action="store_true", | |
| help="Compact UNCAPPED session index (no message bodies, no 50-limit) — " | |
| "the triage primitive for reconstructing a project's full arc. " | |
| "Pair with --sort-order asc for oldest-first. " | |
| "Note: --verbose and --summary are ignored in timeline mode.") |
| params.extend(projects) | ||
|
|
||
| order = "DESC" if sort_order == "desc" else "ASC" | ||
| sql += f" ORDER BY b.started_at {order}" # no LIMIT — the whole arc, by design |
There was a problem hiding this comment.
No LIMIT is intentional and documented, but cursor.fetchall() on a DB with thousands of sessions will materialise the entire result set in memory in one call. For users with large memory stores this could be slow and expensive. Consider streaming with cursor.execute(sql, params) + iterating the cursor directly, or at least documenting a soft self-protection path (e.g. --before/--after to window the range).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 87bc1e4e8d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if projects: | ||
| placeholders = ",".join("?" * len(projects)) | ||
| sql += f" AND p.name IN ({placeholders})" | ||
| params.extend(projects) |
There was a problem hiding this comment.
Filter timeline by project path, not basename
When the current CWD auto-detects a project whose basename is shared by another project, resolve_scope() returns only the name (for example ['EzyCopy']), and this new timeline query then selects every projects row with that name. The new list_projects.py doc explicitly calls out duplicate project names as expected, so recent_chats.py --timeline can silently merge unrelated project histories instead of returning the single project arc the user asked for; the timeline should carry/use a path or key filter rather than p.name IN (...) for disambiguated scopes.
Useful? React with 👍 / 👎.
…ored flags in --timeline help
|
Addressed review feedback in Fixed
Acknowledged, not changed (out of scope for this PR)
|
|
Claude finished @gupsammy's task in 1m 4s —— View job Re-review after
|
…eline rows resolve_scope now returns a ScopeFilter(column, values): auto-detected scope filters by path (unambiguous) so projects sharing a basename are never merged into one arc; explicit --project keeps name-based filtering unchanged. Applies to recent, timeline, and search. get_timeline streams off the cursor instead of fetchall() to halve peak memory on large stores. Addresses Codex P2 + fetchall notes on PR #80.
|
Addressed both remaining optionals in Codex P2 — name-vs-path merge (root fix) Uncapped fetchall Verification
|
|
Claude finished @gupsammy's task in 2m 54s —— View job Re-review after
|
Summary
Changes
.claude-plugin/marketplace.jsonREADME.mdplugins/claude-memory/.claude-plugin/plugin.jsonplugins/claude-memory/README.mdplugins/claude-memory/skills/recall-conversations/scripts/list_projects.pyplugins/claude-memory/skills/recall-conversations/scripts/recent_chats.pyCommits
87bc1e4feat(memory): add --timeline + list_projects to recall CLIDiff Stat