Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 1.0.88

- Feat: aim the search — field-scoped terms, PR references in any spelling, `is:live`, and a persisted enrichment cache ([#140](https://github.com/grimmerk/codev/issues/140), [#134](https://github.com/grimmerk/codev/issues/134))
- **Operators**: `title:x` `branch:x` `msg:x` `project:x` `account:x` `recap:x` search one field; `has:pr|title|branch|recap`; `is:live|pinned`; `after:7d` / `after:2026-09-01` / `after:today` and `before:…` by last activity; `"two words"` keeps a phrase together. Bare words keep today's meaning. Every term must hold. A `?` chip beside the search box shows the list; an operator with an unreadable value is reported under the box and ignored rather than silently matching nothing
- **A pull request in any spelling**: `#147`, `pr:147`, `owner/repo#147` and the GitHub URL all find each other, in your prompts and in what the assistant said. Measured on the reference machine's prompts: 80.6% of PR mentions were reachable by only one form before. Delimited forms only — `#147` never hits `#1475` or `15980`. Three levels of strictness, set by the first live test: `#147` is broad (any mention, any repo); `pr:147` counts only a session's own PR badge or a repo-qualified mention (`owner/repo#147` or the URL, in any repo); a repo in the query (`owner/repo#147`, the URL, `pr:owner/repo#147`) accepts a bare `#147` only in sessions whose own badge or references name that repo. (That test listed eight sessions for `pr:151`; most were the miner reading `#151` out of longer identifiers such as the hex colour `#151e2b` — fixed by a right-side boundary on both sides — and the strict levels stay because every repo does have its own 151.) Claude Code's `[Image #N]` marker for a pasted screenshot is not a PR reference (found matching a session whose only "#151" was `[Image #151]`)
- **The assistant's replies are mined for the PRs it mentioned** (its text and the commands it ran; never tool output, so a session that ran `gh pr list` did not "work on" twenty PRs), so "the PR you opened for me" is findable by number. Measured: 91 transcripts / 739MB mined in 2.0s cold, then incrementally from the byte where the last pass stopped; 3,889 references across 65 sessions
- **One matcher, two callers**: the main-side full-prompt search and the renderer's filter now compile the same query into one matcher and differ only in what each side can put on the target (`is:` is judged in the renderer, everything else on the main side, which also sees title / branch / recap for every session now). Search stays behind the 180ms debounce: 7–10ms per keystroke for words, ~20ms for a PR reference, over 560 sessions
- **The enrichment cache survives a restart** (`~/.config/codev/enrichment-cache.json`): title, branch, PR badge, recap, mined references and per-file mtime+size, written a few seconds after a scan and flushed on quit. A second launch starts warm and re-reads only transcripts that changed. A bad file is a cold start, never an error. **One background pass over every session** runs 20s after launch, in chunks with pauses, so operators see every session and not just the loaded window; measured 6.8s cold for the whole corpus, stat-only afterwards
- Feat: `▶ open N` on a saved list — resume the members that are **not** running ([#145](https://github.com/grimmerk/codev/issues/145))
- For the moments that want the whole set back: after a reboot or macOS update, after closing everything to reclaim memory, or to move the set to another terminal app (change the terminal in Settings, then press it). Reverses 1.0.87's "deliberately no open all" — same reasoning, different moments: the cost is real, so it is shown on the button (`open 12 · ~1.7GB?`, from the mean size of the processes currently running) and confirmed with a second click
- Opens only members without a running process by the `ps` join, so pressing it twice opens nothing new; launches one session every 0.7s rather than all in one tick; reports members skipped because their project folder or transcript is gone. Under the embedded CodeV terminal it explains itself instead of opening twelve sessions into one pane
- Fix on the way: each terminal launch now writes its own temp AppleScript file — the shared name meant one launch's cleanup could delete the script the next osascript was about to read once launches were 0.7s apart
- 30 new unit tests (query tokenizer / parser / matcher, PR-reference boundaries, cache round-trip, the miner against real-shaped records) — 168 total

## 1.0.87

- Feat: saved session lists and a live-process view, on the Session Buddy model ([#145](https://github.com/grimmerk/codev/issues/145), [#94](https://github.com/grimmerk/codev/issues/94))
Expand Down
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,46 @@ Press `⌃+⌘+R` or click the menu bar icon to launch the Quick Switcher. Searc

CodeV can list, search, and resume Claude Code sessions. Press `⌃+⌘+R` to open the Quick Switcher, then `Tab` to toggle to Sessions mode. Live status dots show session state: working (orange pulse), idle (green), needs attention (orange blink).

Search covers **every session and every user prompt you ever typed** (not just the ~100 most recent sessions shown in the list) plus titles, branches, PR links, last AI replies, and the **session id** (a prefix of four or more hex characters — type the id your terminal status line shows to find that exact session, then `⌘D` to pin it; the row shows an `id 4ed7505a` marker since the id is not otherwise on screen). When a match sits in the middle of a conversation, the row shows an amber `match #N` snippet with the surrounding context, and every capped line — title, first/last message, branch, last reply — **moves its window to the match** so you can see *why* the row is there. Long titles are shortened **from the middle** (`head … tail`), so a title written as an `A -> B > C` chain keeps its newest step; hover for the full title. Closed one-shot sessions (≤2 messages, untitled, no PR) fold into an expandable "minor sessions" row to keep the list scannable.
Search covers **every session and every user prompt you ever typed** (not just the ~100 most recent sessions shown in the list) plus titles, branches, PR links, last AI replies, the recap line, and the **session id** (a prefix of four or more hex characters — type the id your terminal status line shows to find that exact session, then `⌘D` to pin it; the row shows an `id 4ed7505a` marker since the id is not otherwise on screen). When a match sits in the middle of a conversation, the row shows an amber `match #N` snippet with the surrounding context, and every capped line — title, first/last message, branch, last reply — **moves its window to the match** so you can see *why* the row is there. Long titles are shortened **from the middle** (`head … tail`), so a title written as an `A -> B > C` chain keeps its newest step; hover for the full title. Closed one-shot sessions (≤2 messages, untitled, no PR) fold into an expandable "minor sessions" row to keep the list scannable.

Bare words search everything; **operators aim the query** (the `?` chip beside the search box shows this list):

| Term | Matches |
|---|---|
| `title:x` `branch:x` `msg:x` `project:x` `account:x` `recap:x` | only that field (`msg:` = your prompts); `"two words"` keeps a phrase together, also after an operator |
| `has:pr` `has:title` `has:branch` `has:recap` | sessions that carry the thing |
| `is:live` `is:pinned` | sessions with a running process (by the `ps` join below) / pinned ones |
| `after:7d` `after:2026-09-01` `after:today` `before:…` | by the session's last activity (`Nh` `Nd` `Nw`, a date, `today`, `yesterday`) |
| `#137` `pr:137` `owner/repo#137` `owner/repo/pull/137` `https://github.com/owner/repo/pull/137` | **a pull request in any spelling** — the query form and the form in the text no longer have to agree (measured: 80.6% of PR mentions in prompts used only one form). Three levels of strictness; see *Finding a pull request* below |

Every term must hold. An operator with an unreadable value (`after:soon`) is reported under the box and ignored rather than silently matching nothing.

#### Finding a pull request

A PR can be found from two kinds of evidence, both indexed:

- **(a) Claude Code's own record.** When a PR is created inside a session, Claude Code writes a `pr-link` record into that session's transcript — `{"type":"pr-link","prNumber":137,"prUrl":"https://github.com/grimmerk/codev/pull/137","prRepository":"grimmerk/codev"}` — and that is the `PR #137` badge on the row. Only a session that actually opened the PR has one (39 of 91 transcripts on the reference machine).
- **(b) Mentions in the conversation.** The URL, `owner/repo#137` or a bare `#137` in **your prompts**, and the same forms in **the assistant's replies and the commands it ran**, mined from the transcript (never from tool output, so a session that ran `gh pr list` did not "work on" twenty PRs). So "the PR you opened for me" is findable by number even when you never typed it.

A **bare mention** is a `#137` with nothing around it saying which repo it belongs to — "take a look at #137", or `Fixes #137` in a commit message. A **repo-qualified mention** is `owner/repo#137` or the URL; the badge counts as one too.

The query form decides how much evidence is required:

| What the session contains | `#137` | `pr:137` | `grimmerk/codev#137`, the URL, or `pr:` + either |
|---|---|---|---|
| The badge: this session opened codev PR #137 | ✓ | ✓ | ✓ |
| You pasted `https://github.com/grimmerk/codev/pull/137` | ✓ | ✓ | ✓ |
| The assistant wrote `grimmerk/codev#137` | ✓ | ✓ | ✓ |
| A codev session where you typed "how is #137 going" — no badge, no URL anywhere | ✓ | ✗ | ✗ — nothing in the session proves which repo that 137 is; use `project:codev #137` |
| A codev session whose badge is **PR #151**, and you typed "also check #137" | ✓ | ✗ (137 is neither the badge nor repo-qualified) | ✓ (the badge proves the session's repo is grimmerk/codev, so its bare #137 counts) |
| A fred-service session where the assistant wrote `Fixes #137` | ✓ | ✗ | ✗ |
| The assistant pasted `https://github.com/firefliesai/fred-service/pull/137` | ✓ | ✓ (any repo, as long as the mention names one) | ✗ |
| Only `#137abc`, or the hex colour `#137e2b` | ✗ | ✗ | ✗ |
| Only the paste marker `[Image #137]` | ✗ | ✗ | ✗ |

In words: `#137` accepts any mention; `pr:137` requires that *someone said which repo's 137 it is* (badge, URL or `owner/repo#137`), in any repo; a repo in the query requires the same repo on qualified mentions and accepts a bare `#137` only in sessions whose own badge or references prove they belong to that repo. Never a bare number: `#137` does not hit `1375`, `#1375`, `#137abc` or `notgithub.com/…/pull/137`, and a leading zero (`#0137`) is not a reference.

Practical rule: to find *the codev PR 137*, paste the URL or type `grimmerk/codev#137`; if that session never saw a URL and Claude did not open the PR there, use `project:codev #137`.

**Pin** the sessions you keep coming back to (hover 📌 on a row, or `⌘D` on the selected row): they **move into** a **📌 Pinned** zone at the top, ordered by recency like the rest of the list — works even for old sessions found via deep search. **Hide** one-offs you never want in the main flow (hover ⊘, or `⇧⌘D`): they move into the minor-sessions fold, stay searchable, and can be unhidden from inside the fold (they carry a persistent ⊘ marker there). Pins and hides live in `~/.config/codev/session-marks.json`, shared across accounts.

Expand All @@ -45,7 +84,7 @@ Two chips beside the search box, on the [Session Buddy](https://sessionbuddy.com
| `save list…` | Appears whenever the list is scoped (`● live`, `only`, or a search): **saves exactly what is on screen as a named list**. The default name is today's `MMDD`, then `MMDD-2`, `MMDD-3` — a label, not an identity. |
| `🗂 N` | Shows the saved lists. Click one to view its members **in the order they were captured** and resume any of them; `✎` renames, `✕` (then `delete?`) deletes. |

A saved member stores what you recognise a session by — title, branch, pin state at capture, the last messages, and the **recap** line Claude Code writes into the transcript (the `※ recap:` "where we are, what's next" line), which replaces the last-reply line on the member's row; a recap much older than the session's last activity is marked `⏱`, since its "next step" may already be done. A member whose transcript is gone still reads as the session it was. Opening a session from a list or from the live scope **leaves you in that scope** when you come back (only the search box is cleared), so you can work through a set one session at a time. There is deliberately no "open all": 22 sessions is a few GB of processes, which is the very thing a saved list exists to relieve. Lists live in `~/.config/codev/session-lists.json`; a file that cannot be trusted as written is reported at load, never rewritten.
A saved member stores what you recognise a session by — title, branch, pin state at capture, the last messages, and the **recap** line Claude Code writes into the transcript (the `※ recap:` "where we are, what's next" line), which replaces the last-reply line on the member's row; a recap much older than the session's last activity is marked `⏱`, since its "next step" may already be done. A member whose transcript is gone still reads as the session it was. Opening a session from a list or from the live scope **leaves you in that scope** when you come back (only the search box is cleared), so you can work through a set one session at a time. A list's header also carries **`▶ open N`** — resume the N members that are **not** running, for the moments that call for the whole set: after a reboot or a macOS update, after closing everything to reclaim memory, or to move the set to another terminal app (change the terminal in Settings, then press it). It asks once, showing the projected cost (`open 12 · ~1.7GB?`, from the mean size of the processes currently running), opens only what is not already running (so pressing it twice opens nothing new), launches one session every 0.7s rather than all at once, and reports members it skipped because their project folder or transcript is gone. Lists live in `~/.config/codev/session-lists.json`; a file that cannot be trusted as written is reported at load, never rewritten.

**Simple rule**: when running multiple sessions in the same project directory at the same time, give each running session a name. Closed sessions don't need names — they won't cause issues.

Expand Down
Loading
Loading