Skip to content

Add durable bookmarks across CLI and TUI - #121

Open
tony wants to merge 18 commits into
issue-80-deterministic-identitiesfrom
issue-79-bookmarks
Open

Add durable bookmarks across CLI and TUI#121
tony wants to merge 18 commits into
issue-80-deterministic-identitiesfrom
issue-79-bookmarks

Conversation

@tony

@tony tony commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Add durable, private bookmarks keyed by canonical content, stored-occurrence, or thread handles without copying prompt bodies or modifying source histories.

  • Add agentgrep bookmark add|remove|list with idempotent human and JSON workflows, a versioned 200-entry snapshot, private XDG storage, atomic replacement, and symlink-safe locking.
  • Add a focus-safe HUD b binding, /bookmark [record|content|thread], and a compact /bookmarks recall list with resolved and unresolved entries.
  • Reopen recalled records in detail without replacing the current results or sticky filter.
  • Keep bookmark load, mutation, resolution, identity, and large detail/find work off the Textual message pump. Rich rendering is bounded to 64 KiB while full-source match offsets and JSON syntax color remain exact.
  • Keep storage and UI diagnostics free of prompt bodies and local paths.

Relationship

This PR targets the branch for #111 because deterministic record identities are its prerequisite. It is independent of the export PR and can be retargeted to master after #111 lands.

Closes #79

Tested

  • Full repository gate: Ruff, formatting, ty, 2,769 passed, 3 skipped, 3 expected failures, and Sphinx.
  • Adversarial bookmark/detail/non-blocking review, including races, stale generations, capacity, symlink refusal, multi-megabyte plain and JSON bodies, active find, resize, teardown, and all three scopes.
  • Live Textual pane: focus-safe input, add/recall/filter/reopen, starred identity, narrow and zoomed layout, persistence across restart, unresolved-scan cancellation, private modes, unchanged source stores, and clean exit.

Goal

Idempotent completion condition for /goal. It asserts an end state, not an action: if the state already holds, the first evaluation passes and nothing is edited.

/goal PR #121 is green on its own base and its ADR stops claiming a number. Work ONLY in the worktree agentgrep-issue-79-bookmarks; check out no branch in the shared master tree, never push. Report FIRST: git rev-parse --abbrev-ref HEAD (prints issue-79-bookmarks; judge local HEAD, not a remote ref), git -c rerere.enabled=false merge-tree --write-tree --name-only issue-80-deterministic-identities HEAD, NO_COLOR=1 AGENTGREP_TUI_WATCHDOG=1 PATH=$PWD/.venv/bin:$PATH env -u VIRTUAL_ENV -u UV_NO_SYNC ./.venv/bin/python -m pytest -m '' --reruns 0 -q, uv run ruff check ., uv run ty check, and grep -A3 '^## Status' docs/dev/adr/*durable-bookmarks.md. End state: merge-tree prints zero CONFLICT lines against that LOCAL base tip as it stands, the other three exit 0, and that Status line records the ADR's NUMBER as provisional pending merge-time allocation. If that tip moved (#111 may rebase it), rebase onto it once rather than waiting on or reverting anything. Renumber and reslug nothing; numbering is a merge-time call. Without the PATH prefix the shell resolves agentgrep to the master checkout, which has no bookmark verb, so six docs/cli/bookmark.md examples fail spuriously. Non-goals: no rebase onto master, no PR retarget, no landing #111. Preserve ADR 0011: bookmark load, mutation, resolution and identity stay off the pump, with no file open, flock wait, sqlite, subprocess, network, json load/dump or whole-record CPU reachable from a pump callable one hop down (the hook sees neither, so trace by hand), pump entrypoints @pump_only, workers @offload. Persisted entries keep full prefixed agc1:/agr1:/agt1: values plus schema_version; store no bare digest, mint no second recipe, claim no retention over any store, index or future corpus. Or stop after 12 turns and report what remains.
  • Proofgit rev-parse --abbrev-ref HEAD && git -c rerere.enabled=false merge-tree --write-tree --name-only issue-80-deterministic-identities HEAD && grep -A3 '^## Status' docs/dev/adr/*durable-bookmarks.md && NO_COLOR=1 AGENTGREP_TUI_WATCHDOG=1 PATH=$PWD/.venv/bin:$PATH env -u VIRTUAL_ENV -u UV_NO_SYNC ./.venv/bin/python -m pytest -m '' --reruns 0 -q
  • Idempotence — After the Status line carries the provisional-number note the whole proof is read-only: merge-tree stays clean against the local base tip and the suite stays green, so a re-run edits nothing; if Expose deterministic IDs and tighten TUI controls #111 has since rebased that base, exactly one rebase onto the current tip restores the same read-only steady state.
  • Blocked by — nothing (landing Expose deterministic IDs and tighten TUI controls #111 is an explicit non-goal; the branch is judged against its own local base tip, whatever that tip currently is)
  • Blocks — nothing
  • Preserves — Retargeting onto master is deferred, not foreclosed, so the identity spine can land first; persisted entries keep the full versioned id prefix plus schema_version so a later recipe migrates rather than invalidates; the bookmark file stays a reference, never a retention claim, leaving Durable prompt corpus and derived search indexes (ADR 0019) #142's corpus and Persistent SQLite DB index with cache-aware search #47's index open; ADR 0011 placement (pump_only entrypoints, offload workers, flock and file I/O off the pump) is retained; the ADR number stays available to the other branches claiming it.

@tony
tony force-pushed the issue-80-deterministic-identities branch from adcf5e0 to cb6377d Compare July 12, 2026 20:14
@tony
tony force-pushed the issue-79-bookmarks branch from bb2c2c8 to 9a9190e Compare July 12, 2026 20:15
@tony
tony force-pushed the issue-80-deterministic-identities branch 9 times, most recently from a68c683 to 5ffd718 Compare July 14, 2026 03:56
@tony
tony force-pushed the issue-79-bookmarks branch from 9a9190e to e1497d2 Compare July 14, 2026 04:27
@tony
tony force-pushed the issue-80-deterministic-identities branch from 5ffd718 to 4e49bbd Compare July 14, 2026 23:36
@tony
tony force-pushed the issue-79-bookmarks branch from e1497d2 to beef6e6 Compare July 14, 2026 23:37
@tony
tony force-pushed the issue-80-deterministic-identities branch from 4e49bbd to c755ba0 Compare July 18, 2026 11:40
@tony
tony force-pushed the issue-79-bookmarks branch from beef6e6 to 77c1873 Compare July 18, 2026 11:49
@tony
tony force-pushed the issue-79-bookmarks branch from 45034fe to 6bac02f Compare July 18, 2026 13:51
@tony
tony force-pushed the issue-80-deterministic-identities branch from c755ba0 to a0c10d6 Compare July 19, 2026 01:06
@tony
tony force-pushed the issue-79-bookmarks branch from 6bac02f to 086d9f7 Compare July 19, 2026 01:10
@tony
tony force-pushed the issue-80-deterministic-identities branch from a0c10d6 to 68db741 Compare July 19, 2026 01:42
tony added 18 commits August 9, 2026 07:30
why: Canonical records need a bounded, privacy-minimal bookmark layer that survives CLI and later TUI sessions without mutating agent-owned stores.

what:
- Add strict versioned bookmark values and transactional XDG persistence.
- Add idempotent add/remove/list CLI actions with deterministic output.
- Cover validation, durability, permissions, atomicity, and safe errors.
why: Existing snapshots could retain permissive modes, direct callers
could receive path-bearing filesystem errors, and custom stores changed
caller-owned parent permissions.

what:
- Repair snapshot modes through the descriptor held under the stable lock.
- Translate storage failures into generic path-free, unchained errors.
- Preserve existing explicit parent modes while securing owned directories.
- Cover each review finding with focused regression tests.
why: Canonical bookmarks need a keyboard-first HUD surface.
Saved targets must resolve without freezing Textual or replacing active
search state.

what:
- Add bookmark slash commands, focus-safe toggling, and a compact recall
  modal.
- Offload bookmark loading, identity work, transactions, and target
  resolution.
- Gate writes and callbacks while preserving results and marking saved
  records.
- Cover modal behavior, races, privacy, pump bounds, and guards.
why: Recall could snapshot entries before an accepted write became durable, while a concurrent toggle left an older resolver able to open a stale modal.

what:
- Reject recall while a bookmark transaction is pending.
- Cancel and invalidate active resolution before launching a write.
- Cover both mutation and resolution race interleavings.
why: First-hit resolver coverage and tiny modal fixtures could miss regressions at the fixed 200-entry boundary.

what:
- Mount and navigate a full-capacity filtered recall modal.
- Exercise target-last and target-absent resolver scans under the watchdog.
- Assert every candidate hashes off-pump without a heartbeat stall.
why: A find query opened while large-body preparation was pending searched a bounded prefix but retained those incomplete matches after the final body arrived.

what:
- Recompute live find state when worker-prepared detail becomes visible.
- Cover a match beyond the pending prefix and retain existing pending behavior.
why: Durable bookmarks span CLI and HUD behavior and need one
privacy-conscious contract for canonical targets, local state, and
current-store recall.

what:
- Add the canonical scope, storage, and CLI guide with ADR 0016.
- Document HUD toggles, filtering, resolution, and unresolved retention.
- Add the release deliverable, indexes, and documentation contract
  tests.
why: Record additions require a content validator while removals do not.
A re-add also depends on matching the saved validator, even at capacity.

what:
- Scope --content-id to record add and document remove syntax.
- Explain matching re-adds and mismatched-validator exit behavior.
- Pin the distinction in the bookmark documentation contract.
why: Long canonical IDs made the record-add example exceed the command
width used by the documentation.

what:
- Put the content-ID flag and record target on continuation lines.
- Keep the console block as one copyable shell command.
why: Bookmark titles and bodies may contain Textual markup-like text. Parsing
that content could alter the preview or abort modal mounting.

what:
- Disable markup parsing for the bookmark preview widget.
- Mount a malformed-markup title and assert it renders literally.
why: Following a sidecar-lock symlink could change and lock an unrelated
file. Non-regular descriptors are also invalid lock transaction anchors.

what:
- Open the lock without following symlinks and verify its descriptor type.
- Refuse unsafe locks before chmod or flock with path-free public errors.
- Preserve a symlink target byte-for-byte and cover non-regular descriptors.
why: The bookmark TUI depended on the package facade in violation of the
module boundary, while the public Task 1 dispatcher was not re-exported
like its sibling command dispatchers.

what:
- Import bookmarks from its owning module in the HUD.
- Re-export the bookmark dispatcher through the package facade.
- Pin only BookmarkArgs and run_bookmark_command in the facade snapshot.
why: Inserting the bookmark parser among required ParserBundle fields broke
callers that construct the longstanding four-parser bundle positionally or
by keyword.

what:
- Append the bookmark parser as an optional compatibility field.
- Keep internally-created bundles wired to the bookmark-specific parser.
- Cover the original positional and keyword constructor forms.
why: A mutation may finish after the live detail record has been rebuilt as
a distinct Python object with the same canonical identity. Object comparison
left that equivalent record unstarred.

what:
- Match mutation refreshes against the cached canonical record ID.
- Keep unrelated live selections untouched without hashing on the pump.
- Mount equivalent and unrelated records around a mutation regression.
why: Wall-clock heartbeat gaps conflate a blocked Textual pump with host
CPU descheduling, making off-pump bookmark tests flaky on shared runners.

what:
- Hold resolver and detail workers on GIL-releasing events.
- Prove pump progress through a scheduled callback and live key handling.
- Keep watchdog runtime and dedicated watchdog tests unchanged.
why: The rebased bookmark test no longer matches the repository Ruff
formatting.

what:
- Reflow the resolver fixture comprehension without changing behavior.
why: The original fixture included its query in the raw body, so it
could not prove that async rendering recomputes live detail-find
matches.

what:
- Use minified JSON whose pretty form introduces the queried spacing.
- Assert the match appears only after the render worker completes.
why: Ruff's format gate rejected the focused bookmark tests after the
stack was replayed onto the updated identity branch.

what:
- Apply Ruff's canonical wrapping to CLI and command tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant