feat: Extend zev with the history flag that allows users to reuse previously selected commands from the local history#1
Conversation
…viously selected commands from the local history
There was a problem hiding this comment.
Pull request overview
Adds a new “selected command history” feature to zev, enabling users to browse previously selected command suggestions from local storage via a new CLI flag.
Changes:
- Introduces a
selected_command_historymodule (types/repository/service/UI) that persists selected commands to a local file and renders an interactive selector. - Records selections when the user chooses a command from zev’s suggestions (including the original query).
- Adds
--history/-Hto the CLI help and routes the new flag to the history UI.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/zev/utils.py | Updates CLI help text to document the new history flag. |
| src/zev/selected_command_history/ui.py | New interactive Questionary UI for browsing selected command history. |
| src/zev/selected_command_history/types.py | New Pydantic model representing a selected command entry. |
| src/zev/selected_command_history/service.py | New service for recording and retrieving selected command history. |
| src/zev/selected_command_history/repository.py | New file-backed repository implementation for persistence. |
| src/zev/selected_command_history/config.py | Defines the default history file location. |
| src/zev/selected_command_history/init.py | Exposes selected-command history types/service. |
| src/zev/main.py | Adds --history routing to the new UI and passes original query into selection flow. |
| src/zev/command_selector.py | Records selected commands into the new history store. |
| src/zev/command_history.py | Passes original query through when reusing “recent query” history. |
| .gitignore | Broadens egg-info ignore patterns. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| self._encoding = encoding | ||
| self._ensure_file_exists() | ||
|
|
||
| def _ensure_file_exists(self) -> None: |
There was a problem hiding this comment.
Path.touch() will fail if the parent directory doesn't exist. Since create_repository() accepts an arbitrary file_path, consider ensuring self._file_path.parent.mkdir(parents=True, exist_ok=True) before touching the file so the repository works with non-home paths (e.g., during tests or future config changes).
| def _ensure_file_exists(self) -> None: | |
| def _ensure_file_exists(self) -> None: | |
| # Ensure the parent directory exists before creating the file, | |
| # so arbitrary file paths with non-existent parents work correctly. | |
| self._file_path.parent.mkdir(parents=True, exist_ok=True) |
| if command == "--history" or command == "-H": | ||
| show_selected_command_history() | ||
| return True |
There was a problem hiding this comment.
args[0] is lowercased (command = args[0].lower()), so passing -H becomes -h and this branch will never match. As a result, zev -H will trigger the help case instead of history. Consider preserving the original case for short flags, or compare against the lowercased value and choose a non-conflicting short option (or switch to argparse).
| """Get command history, most recent first.""" | ||
| entries = self._repository.get_all() | ||
| entries.reverse() | ||
| if limit: |
There was a problem hiding this comment.
if limit: treats 0 as "no limit" and returns the full history. If limit is intended to be an optional int, consider using if limit is not None: (and possibly validating that limit >= 0) so limit=0 correctly returns an empty list.
| if limit: | |
| if limit is not None: | |
| if limit < 0: | |
| raise ValueError("limit must be a non-negative integer or None") |
| def _display_history_selector( | ||
| entries: list[SelectedCommandEntry], | ||
| show_limit: int, | ||
| ) -> Optional[SelectedCommandEntry]: | ||
| """Display the history selection menu.""" |
There was a problem hiding this comment.
The return type is annotated as Optional[SelectedCommandEntry], but this function can also return the string values "show_more"/"Cancel" (via the questionary.select(...).ask() result). Update the annotation (e.g., SelectedCommandEntry | Literal["Cancel"] | None) and/or normalize the return value so callers don't need to handle mixed types.
| def _display_full_history(entries: list[SelectedCommandEntry]) -> Optional[SelectedCommandEntry]: | ||
| """Display all history entries.""" |
There was a problem hiding this comment.
This function can return the string "Cancel" (based on the choice value), but the return type is Optional[SelectedCommandEntry]. Align the type annotation with actual return values (or map "Cancel" to None) to avoid mixed-type control flow.
| try: | ||
| entries.append(SelectedCommandEntry.model_validate_json(line)) | ||
| except Exception: | ||
| continue |
There was a problem hiding this comment.
Catching Exception here will also swallow unexpected errors beyond JSON/validation problems, making file corruption or other issues hard to diagnose. Consider catching the specific exceptions raised by model_validate_json (e.g., pydantic.ValidationError / ValueError) and optionally surfacing/logging a warning when a line is skipped.
No description provided.