{"owner":"atuinsh","repo":"atuin","hasSkills":true,"totalSkillsCount":3,"totalTokensCount":6969,"categories":["root-instruction","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md",".claude/skills/hunk/SKILL.md",".atuin/skills/release/SKILL.md"],"skills":{"AGENTS.md":"# Atuin\n\nShell history tool. Replaces your shell's built-in history with a SQLite database, adds context (cwd, exit code, duration, hostname), and optionally syncs across machines with end-to-end encryption.\n\n## AI disclosure (REQUIRED)\n\nIf you are an AI agent, you MUST follow this rule every time you write a GitHub pull request\ndescription, issue, or comment for this repository.\n\n1. Run `git config user.email`.\n2. If the output ends with `@atuin.sh`, stop here. This rule does not apply to you.\n3. In every other case, including when the output is empty or the command fails, the LAST line of\n   every pull request description, issue, and comment you write MUST be exactly this line, with a\n   blank line before it:\n\n   ```markdown\n   <!-- atuin-pr-template-necessary-for-ci: v2 -->\n   ```\n\nThat line marks the text as written by an AI agent. Copy it character for character. Do not\nreword it, translate it, or move it somewhere else. GitHub hides HTML comments when it renders the\ntext. That is expected: keep the line even though it does not show up, and do not delete it when\nyou clean up template comments. When you edit a pull request description, issue, or comment, keep\nthe line at the end.\n\nExample of a correct pull request description:\n\n```markdown\nFixes #1234.\n\nThe server added the `ATUIN_*` environment before `server.toml`, so the file always won.\n\n<!-- atuin-pr-template-necessary-for-ci: v2 -->\n```\n\n## Workspace crates\n\n```\natuin                  CLI binary + TUI (clap, ratatui, crossterm)\natuin-client           Client library: local DB, encryption, sync, settings, client-facing domain types\natuin-common           Low-level cross-crate utilities and API models (not a home for client-facing domain types)\natuin-daemon           Background gRPC daemon (tonic) for shell hooks\natuin-dotfiles         Legacy read-only alias/var listing via record store\natuin-history          Sorting algorithms, stats\natuin-kv               Key-value store (synced)\natuin-scripts          Script management (minijinja)\natuin-server           HTTP sync server (axum) - lib + standalone binary\natuin-server-database  Database trait for server\natuin-server-postgres  Postgres implementation (sqlx)\natuin-server-sqlite    SQLite implementation (sqlx)\n```\n\n## Two sync protocols\n\n- **V1 (legacy)**: Syncs history entries directly. Removed.\n- **V2 (current)**: Record store abstraction. All data types (history, KV, aliases, vars, scripts) share the same sync infrastructure using tagged records. Envelope-encrypted with PASETO V4 and per-record CEKs.\n\n## Encryption\n\n- **V1**: XSalsa20Poly1305 (secretbox). Key at `~/.atuin/data/key`.\n- **V2**: PASETO V4 Local (XChaCha20-Poly1305 + Blake2b). Envelope encryption: each record gets a random CEK wrapped with the master key. Record metadata (id, idx, version, tag, host) is authenticated as implicit assertions.\n\n## Databases\n\n- **Client**: SQLite everywhere. Separate DBs for history, record store, KV, scripts. All use sqlx + WAL mode.\n- **Server**: Postgres (primary) or SQLite. Auto-detected from URI prefix.\n- Migrations live alongside each crate. Never modify existing migrations, only add new ones.\n  - Exception: the AI session sidecar's migrations (`atuin-client/src/ai_session/migrations`) were squashed into one `0001_init.sql` before release, and the sidecar file was renamed (`ai_harness_sessions.db` → `ai_session_sidecar.db`) so no old sidecar meets the new history. It is now `threads.db`; the daemon renames `ai_session_sidecar.db` on start. Don't add back compatibility with the old migrations; from here on, add new ones as usual.\n\n## Hot paths\n\n`history start`, `history end`, and `init` skip database initialization for latency. Don't add DB calls to these without good reason.\n\n## Conventions\n\n- Crate placement: anything client-facing -- domain types users' code touches and the stores over\n  them -- lives in `atuin-client`. `atuin-common` is for low-level, cross-crate utilities only; do\n  not put client-facing domain types there.\n- Rust 2024 edition, toolchain 1.98.0.\n- Errors: `eyre::Result` in binaries, `thiserror` for typed errors in libraries.\n- Derive boilerplate: `derive_more` (workspace dep) for `Display`, `From`, `Into`, `AsRef`, `Deref`, `Debug` on newtypes and simple enums. Prefer `derive_more` over manual `impl` when the formatting/conversion is a straight delegation. Use `thiserror` (not `derive_more`) for error types. Use `#[as_ref(str)]` on string newtypes for `AsRef<str>`.\n- Async: tokio. Client uses `current_thread`; server uses `multi_thread`.\n- `#![deny(unsafe_code)]` on client/common, `#![forbid(unsafe_code)]` on server.\n- Clippy: `pedantic` + `nursery` on main crate. CI enforces `-D warnings`, on both the default targets and `--tests`.\n- Rustdoc: CI runs `cargo doc --document-private-items --no-deps --workspace` with `RUSTDOCFLAGS=-D warnings`. Broken intra-doc links fail the build.\n- Format: `cargo +nightly fmt`. `.rustfmt.toml` uses nightly-only options, so formatting requires the nightly toolchain even though the project builds on stable 1.97.0.\n- IDs: UUIDv7 (time-ordered), newtype wrappers (`HistoryId`, `RecordId`, `HostId`).\n- Paths: when the original owned `PathBuf` isn't needed afterwards, use\n  `atuin_common::path::PathBufExt::joined` (import `PathBufExt as _`) instead of `.join()`. This\n  always applies to the second and later joins of a chain (`a.join(x).joined(y)`) and to owned\n  temporaries (`home_dir().joined(x)`). Also prefer it over `let mut p = ..; p.push(..);` when\n  that's simpler. Clippy can't flag this, so watch for it.\n- Serialization: MessagePack for encrypted payloads, JSON for API, TOML for config.\n- Storage traits: `Database` (client), `Store` (record store), `Database` (server) -- all `async_trait`.\n- History builders: `HistoryImported`, `HistoryCaptured`, `HistoryFromDb` with compile-time field validation.\n- Feature flags: `client`, `sync`, `daemon`, `clipboard`, `check-update`.\n\n## Testing\n\n- Unit tests inline with `#[cfg(test)]`. Use `rstest` for every test — `#[rstest]`, never a\n  bare `#[test]` (async: `#[rstest]` + `#[tokio::test]`); migrate plain `#[test]`s in files you touch.\n- Lean on `#[fixture]`s for shared setup and compose them; when a test needs teardown, return an\n  RAII guard from the fixture (e.g. a temp dir removed on `Drop`) rather than cleaning up by hand.\n- Parametrize with `#[case(...)]` (input/expected tables) and `#[values(...)]` (cross-products of\n  independent parameters) instead of near-duplicate tests.\n- Reach for `proptest` when a property holds across many inputs — round-trips (encode/decode, serde,\n  parse/display), invariants, idempotence; keep targeted `#[case]`s for known edge cases and regressions.\n- Server integration tests in `crates/atuin-server/tests/` need Postgres (`ATUIN_DB_URI` env var).\n- E2e tests use temporary homes and private daemon sockets. Add shell setups in\n  `crates/atuin/tests/shells/*.toml`. See `crates/atuin/tests/README.md` for dependencies\n  and how to run them.\n- Use `rstest` for tests, especially when they can be made simpler using `case`s and `fixture`s.\n- Use `\":memory:\"` SQLite for unit tests needing a database.\n- Runner: `cargo nextest`.\n\n## Build and check\n\n```sh\ncargo build\ncargo test\ncargo clippy -- -D warnings\ncargo clippy --tests -- -D warnings\ncargo +nightly fmt --check\nRUSTDOCFLAGS=\"-D warnings\" cargo doc --document-private-items --no-deps --workspace\n```\n",".claude/skills/hunk/SKILL.md":"---\nname: hunk-review\ndescription: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files and hunks, reloads session contents, and adds inline review comments. Use when the user has a Hunk session running or wants to review diffs interactively.\n---\n\n# Hunk Review\n\nHunk is an interactive terminal diff viewer. The TUI is for the user -- do NOT run `hunk diff`, `hunk show`, or other interactive commands directly. Use `hunk session *` CLI commands to inspect and control live sessions through the local daemon.\n\nIf no session exists, ask the user to launch Hunk in their terminal first.\n\n## Workflow\n\n```text\n1. hunk session list                                    # find live sessions\n2. hunk session get --repo .                            # inspect path / repo / source\n3. hunk session review --repo . --json                  # inspect file/hunk structure first\n4. hunk session review --repo . --include-patch --json  # opt into raw diff text only when needed\n5. hunk session context --repo .                        # check current focus when needed\n6. hunk session navigate ...                            # move to the right place\n7. hunk session reload -- <command>                     # swap contents if needed\n8. hunk session comment add ...                         # leave one review note\n9. hunk session comment apply ...                       # apply many agent notes in one stdin batch\n```\n\n## Session selection\n\nMost session commands accept:\n\n- `--repo <path>` -- match the live session by its current loaded repo root (most common)\n- `<session-id>` -- match by exact ID (use when multiple sessions share a repo)\n- If only one session exists, it auto-resolves\n\n`reload` also supports:\n\n- `--session-path <path>` -- match the live Hunk window by its current working directory\n- `--source <path>` -- load the replacement `diff` / `show` command from a different directory\n\nUse `--source` only for advanced reloads where the live session you want to control is not already associated with the checkout you want to load next. For a normal worktree session, prefer selecting it directly with `--repo /path/to/worktree`.\n\n## Commands\n\n### Inspect\n\n```bash\nhunk session list [--json]\nhunk session get (--repo . | <id>) [--json]\nhunk session context (--repo . | <id>) [--json]\nhunk session review (--repo . | <id>) [--json] [--include-patch]\n```\n\n- `get` shows the session `Path`, `Repo`, and `Source`, which helps when choosing between `--repo` and `--session-path`\n- `Repo` is what `--repo` matches; `Path` is what `--session-path` matches\n- `review --json` returns file and hunk structure by default; add `--include-patch` only when a caller truly needs raw unified diff text\n\n### Navigate\n\nAbsolute navigation requires `--file` and exactly one of `--hunk`, `--new-line`, or `--old-line`:\n\n```bash\nhunk session navigate --repo . --file src/App.tsx --hunk 2\nhunk session navigate --repo . --file src/App.tsx --new-line 372\nhunk session navigate --repo . --file src/App.tsx --old-line 355\n```\n\nRelative comment navigation jumps between annotated hunks and does not require `--file`:\n\n```bash\nhunk session navigate --repo . --next-comment\nhunk session navigate --repo . --prev-comment\n```\n\n- `--hunk <n>` is 1-based\n- `--new-line` / `--old-line` are 1-based line numbers on that diff side\n- Use either `--next-comment` or `--prev-comment`, not both\n\n### Reload\n\nSwaps the live session's contents. Pass a Hunk review command after `--`:\n\n```bash\nhunk session reload --repo . -- diff\nhunk session reload --repo . -- diff main...feature -- src/ui\nhunk session reload --repo . -- show HEAD~1\nhunk session reload --repo . -- show HEAD~1 -- README.md\nhunk session reload --repo /path/to/worktree -- diff\nhunk session reload --session-path /path/to/live-window --source /path/to/other-checkout -- diff\n```\n\n- Always include `--` before the nested Hunk command\n- `--repo` or `<session-id>` usually selects the session you want\n- `--source` is advanced: it does not select the session; it only changes where the replacement review command runs\n- If the live session is already showing the target worktree, prefer `hunk session reload --repo /path/to/worktree -- diff`\n- `--session-path` targets the live window when you need to keep session selection separate from reload source\n\n### Comments\n\n```bash\nhunk session comment add --repo . --file README.md --new-line 103 --summary \"Tighten this wording\" [--rationale \"...\"] [--author \"agent\"] [--focus]\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' | hunk session comment apply --repo . --stdin [--focus]\nhunk session comment list --repo . [--file README.md]\nhunk session comment rm --repo . <comment-id>\nhunk session comment clear --repo . --yes [--file README.md]\n```\n\n- `comment add` is best for one note; `comment apply` is best when an agent already has several notes ready\n- `comment add` requires `--file`, `--summary`, and exactly one of `--old-line` or `--new-line`\n- `comment apply` payload items require `filePath`, `summary`, and exactly one target such as `hunk`, `hunkNumber`, `oldLine`, or `newLine`\n- `comment apply` reads a JSON batch from stdin and validates the full batch before mutating the live session\n- Pass `--focus` when you want to jump to the new note or the first note in a batch\n- `comment list` and `comment clear` accept optional `--file`\n- Quote `--summary` and `--rationale` defensively in the shell\n\n## New files in working-tree reviews\n\n`hunk diff` includes untracked files by default. If the user wants tracked changes only, reload with `--exclude-untracked`:\n\n```bash\nhunk session reload --repo . -- diff --exclude-untracked\n```\n\n## Guiding a review\n\nThe user may ask you to walk them through a changeset or review code using Hunk. Start with `hunk session review --json` to understand the file/hunk structure without inflating agent context, then use `--include-patch` only for the files you truly need to read in raw diff form. Use `context` and `navigate` to line up the user's current view before adding comments.\n\nYour role is to narrate: steer the user's view to what matters and leave comments that explain what they're looking at.\n\nTypical flow:\n\n1. Load the right content (`reload` if needed)\n2. Navigate to the first interesting file / hunk\n3. Add a comment explaining what's happening and why\n4. If you already have several notes ready, prefer one `comment apply` batch over many separate shell invocations\n5. Summarize when done\n\nGuidelines:\n\n- Work in the order that tells the clearest story, not necessarily file order\n- Navigate before commenting so the user sees the code you're discussing\n- Use `comment apply` for agent-generated batches and `comment add` for one-off notes\n- Use `--focus` sparingly when the note itself should actively steer the review\n- Keep comments focused: intent, structure, risks, or follow-ups\n- Don't comment on every hunk -- highlight what the user wouldn't spot themselves\n\n## Common errors\n\n- **\"No visible diff file matches ...\"** -- the file is not in the loaded review. Check `context`, then `reload` if needed.\n- **\"No active Hunk sessions\"** -- ask the user to open Hunk in their terminal.\n- **\"Multiple active sessions match\"** -- pass `<session-id>` explicitly.\n- **\"No active Hunk session matches session path ...\"** -- for advanced split-path reloads, verify the live window `Path` via `hunk session get` or `list`, then use `--session-path`.\n- **\"Pass the replacement Hunk command after `--`\"** -- include `--` before the nested `diff` / `show` command.\n- **\"Pass --stdin to read batch comments from stdin JSON.\"** -- `comment apply` only reads its batch payload from stdin.\n- **\"Specify exactly one navigation target\"** -- pick one of `--hunk`, `--old-line`, or `--new-line`.\n- **\"Specify either --next-comment or --prev-comment, not both.\"** -- choose one comment-navigation direction.\n",".atuin/skills/release/SKILL.md":"---\nname: release\ndescription: >\n  Orchestrate a multi-step Atuin CLI release — version bumping, changelog\n  generation, PR creation, tagging, and crates.io publishing. Invoke with\n  /release or /release <version>.\ndisable-model-invocation: true\nargument-hint: \"[version]\"\n---\n\n# Atuin CLI Release\n\nYou are orchestrating a release of the Atuin CLI. Follow the steps below\n**in order**, pausing at each checkpoint for user confirmation. Do not skip\nsteps or combine them.\n\n## Current State\n\n- Workspace version: !`sed -n '/^\\[workspace\\.package\\]/,/^\\[/s/^version = \"\\(.*\\)\"/\\1/p' Cargo.toml`\n- Latest tag: !`git describe --tags --abbrev=0 2>/dev/null || echo \"none\"`\n- Suggested next version: !`git-cliff --bumped-version 2>/dev/null | sed 's/^v//' || echo \"(unknown)\"`\n\n---\n\n## Step 1 — Check Dependencies\n\nVerify these tools are installed: `git`, `cargo`, `gh`, `git-cliff`.\n\nOn macOS, also verify `gsed`. On Linux, `sed` is already GNU sed.\n\nUse `command -v` for each. If any are missing, report which ones and stop.\n\nSet the `SED` variable for later use: if on macOS, `SED=gsed`; on Linux, `SED=sed`.\n\n---\n\n## Step 2 — Determine Version\n\nThe target version may be provided as `$ARGUMENTS`. If it's empty, use\nAskUserQuestion to ask for the new version (show the current state above\nfor reference).\n\nAfter determining the version:\n- If it contains a `-` (e.g. `18.15.0-beta.1`), it is a **prerelease**.\n  Note this — it affects changelog and publish behavior later.\n- Show the user: `current → new` and whether it's a prerelease.\n- **Checkpoint:** Ask the user to confirm before proceeding.\n\n---\n\n## Step 2.5 — Survey Release-Labeled PRs\n\nTwo labels mark PRs whose merge has to be timed around the release. They are\ntypically PRs touching `docs/` or `install.sh` — changes that deploy on merge to\n`main` rather than shipping inside the release artifact.\n\n| Label | Merged at | In the tag & artifact? |\n|-------|-----------|------------------------|\n| `merge-immediately-before-release` | Step 7.5 — after the prep PR merges, before the tag | Yes |\n| `merge-immediately-after-release` | Step 8.5 — after the release workflow uploads assets | No |\n\nCheck both:\n\n```bash\ngh pr list --repo atuinsh/atuin --state open \\\n    --label merge-immediately-before-release \\\n    --json number,title,url\ngh pr list --repo atuinsh/atuin --state open \\\n    --label merge-immediately-after-release \\\n    --json number,title,url\n```\n\nIf both come back empty, say so and continue to Step 3. No checkpoint needed.\n\nThe two labels are mutually exclusive: they name opposite sides of the tag. If a\nPR number comes back in **both** lists, that is a labelling mistake — the two\nqueries are independent, so it would otherwise be approved twice, merged at Step\n7.5, then merged again at Step 8.5 against an already-closed PR. **Stop** and ask\nthe user which single timing applies, then treat it as carrying only that label\nfor the rest of the release. Do not guess, and do not carry it into both steps.\n\nIf either is non-empty:\n\n1. Show the user every PR found, grouped by label, with its number, title, and\n   the merge point from the table above.\n2. **Checkpoint:** Use AskUserQuestion (multiSelect) to ask which of these PRs\n   should in fact be merged at their labeled time.\n3. Record the approved set — it carries to Step 7.5 and Step 8.5. A PR the user\n   declines is left alone for the remainder of the release: don't merge it,\n   don't raise it again.\n\nTake each label at face value. It records the PR author's decision about timing;\nit is not a suggestion for you to re-evaluate:\n\n- An `after-release` PR is **not** in the tag and **not** in the release\n  artifact. That is the intended outcome, not a problem to solve. The label is\n  for changes that deploy the moment they merge, so artifact inclusion is\n  irrelevant to them. An author who needed the change in the artifact would have\n  labeled it `merge-immediately-before-release`.\n- Never merge an `after-release` PR early to get it into the release, and never\n  reclassify a PR from one label to the other.\n\n---\n\n## Step 3 — Set Up Working Directory\n\nClone a fresh copy into a temp directory:\n\n```bash\nWORKDIR=$(mktemp -d)\ngit clone git@github.com:atuinsh/atuin.git \"$WORKDIR\"\n```\n\nPrint the working directory path so the user can find it if needed.\n\nNOTE:\nALL subsequent Bash commands run from `$WORKDIR`.\n\n---\n\n## Step 4 — Create Branch & Update Versions\n\n1. Create a release branch named after the version (no `v` prefix):\n   `git checkout -b <VERSION>`\n\n2. Replace the old version with the new one in all `Cargo.toml` files.\n   **Escape dots** in the old version so sed treats them literally:\n\n   ```bash\n   VERSION_PATTERN=\"${OLD_VERSION//./\\\\.}\"\n   find . -type f -name 'Cargo.toml' -not -path './.git/*' \\\n       -exec $SED -i \"s/$VERSION_PATTERN/$NEW_VERSION/g\" {} \\;\n   ```\n\n3. Run `cargo check` to update `Cargo.lock`.\n\n4. Show `git diff --stat` and the version-related lines from the diff:\n   ```bash\n   git diff --unified=0 -- '*.toml' | grep '^\\+.*version' | grep -vF '+++'\n   ```\n   Remember to use macOS grep arguments on macOS systems.\n\n5. Verify the workspace version was actually updated by re-reading it\n   from `Cargo.toml`.\n\n6. **Checkpoint:** Show the diff summary and ask the user to confirm the\n   version changes look correct.\n\n---\n\n## Step 5 — Update Changelog\n\nThe changelog strategy differs for prereleases vs stable releases:\n\n- **Prerelease:** Maintain a running `## [unreleased]` section containing\n  all changes since the last stable release. Use:\n  `git-cliff --unreleased --strip all`\n  (cliff.toml's `ignore_tags` already ignores beta/alpha tags, so\n  `--unreleased` spans back to the last stable release automatically.)\n\n- **Stable release:** Generate a versioned entry that replaces the\n  `[unreleased]` section. Use:\n  `git-cliff --unreleased --tag \"v<VERSION>\" --strip all`\n\nThen update `CHANGELOG.md`:\n\n1. If an existing `## [unreleased]` or `## [Unreleased]` section exists,\n   **remove it entirely** (the heading and all content up to the next\n   `## ` heading).\n\n2. Insert the new entry before the first existing `## ` version heading.\n\n3. **Checkpoint:** Read and display the new changelog entry to the user.\n   Ask if they want any edits. If so, make the requested changes using\n   the Edit tool. Repeat until they're satisfied.\n\n---\n\n## Step 6 — Commit & Push\n\nStage all changes and commit:\n\n```\nchore(release): prepare for release <VERSION>\n```\n\nPush the branch with `--set-upstream origin`.\n\n---\n\n## Step 7 — Create PR & Wait for Merge\n\n### Create the PR\n\nExtract the changelog entry body (everything between the new `## ` heading\nand the next one) for the PR description.\n\nFor prereleases, the heading to match is `## [unreleased]`.\nFor stable releases, it's `## <VERSION>` (escape dots in the awk pattern).\n\nCreate the PR:\n```bash\ngh pr create \\\n    --title \"chore(release): prepare for release <VERSION>\" \\\n    --body \"<body with changelog>\" \\\n    --repo atuinsh/atuin \\\n    --draft\n```\n\nShow the PR URL to the user. Tell the user to go review the PR, mark it ready,\nand comment `/merge` on it.\n\nWhen the PR shows as merged, proceed to the next step.\n\n---\n\n## Step 7.5 — Merge `merge-immediately-before-release` PRs\n\nSkip to Step 8 unless Step 2.5 recorded approved PRs with this label.\n\nThe prep PR has merged and the tag does not exist yet. This is the only window\nin which these PRs land in `v<VERSION>` and its artifacts.\n\nFor each approved PR:\n\n1. Check it is still mergeable and its checks are green:\n   ```bash\n   gh pr view <N> --repo atuinsh/atuin \\\n       --json mergeable,mergeStateStatus,statusCheckRollup\n   ```\n   If it is not mergeable, or checks are red, report that and ask the user how\n   to proceed. Do not force it through.\n\n2. **Checkpoint:** Show the PR number and title and ask the user to confirm this\n   specific merge. Step 2.5 approved the *timing*; this confirms merging *now*.\n\n3. Merge, then wait until the PR shows as merged:\n   ```bash\n   gh pr comment <N> --repo atuinsh/atuin --body /merge\n   gh pr view <N> --repo atuinsh/atuin --json state --jq .state  # until MERGED\n   ```\n\nThese commits land in the tag but are absent from the changelog generated in\nStep 5, which was cut before they merged. That is expected — do not regenerate\nthe changelog to include them.\n\n---\n\n## Step 8 — Tag Release\n\nBack in the working directory:\n\n```bash\ngit checkout main\ngit pull\ngit tag \"v<VERSION>\"\ngit push --tags\n```\n\nTell the user the tag was pushed and the release CI workflow has been\ntriggered.\n\n---\n\n## Step 8.5 — Merge `merge-immediately-after-release` PRs\n\nSkip to Step 9 unless Step 2.5 recorded approved PRs with this label.\n\n\"After the release\" means **after the release workflow has finished and the\nassets exist** — not merely after the tag was pushed. These PRs deploy the\ninstant they merge, so a docs change announcing the new version must not go\nlive while the release it points at is still building.\n\n1. Wait for the release workflow the tag just triggered:\n   ```bash\n   gh run list --repo atuinsh/atuin --workflow release.yml \\\n       --branch \"v<VERSION>\" --limit 1\n   gh run watch <run-id> --repo atuinsh/atuin --exit-status\n   ```\n   `--exit-status` is what makes a failed run exit non-zero. Without it\n   `gh run watch` prints the failure but still exits `0`, and a broken release\n   sails through into the merges below. If it exits non-zero, **stop** here —\n   do not go on to the release lookup.\n\n2. Confirm the release exists and has assets attached:\n   ```bash\n   gh release view \"v<VERSION>\" --repo atuinsh/atuin --json assets,isDraft\n   ```\n   If the release is missing, is still a draft, or has no assets, **stop**.\n   Report it and merge nothing — these PRs keep until the release is actually\n   out. A release object alone is not evidence: it may predate this run or have\n   been left half-built by a failed workflow. What clears this gate is a\n   non-draft release with assets attached.\n\n3. Re-check that each approved PR is still mergeable and its checks are green:\n   ```bash\n   gh pr view <N> --repo atuinsh/atuin \\\n       --json mergeable,mergeStateStatus,statusCheckRollup\n   ```\n   Step 2.5 surveyed these PRs before the release was even built, so its\n   findings are now many minutes stale — long enough for a PR to pick up a\n   conflict or a red check. If it is not mergeable, or checks are red, report\n   that and ask the user how to proceed. Do not force it through. These PRs\n   deploy the instant they merge, so there is no CI gate downstream to catch a\n   bad one.\n\n4. **Checkpoint:** For each approved PR, show its number and title and ask the\n   user to confirm this specific merge. Step 2.5 approved the *timing*; this\n   confirms merging *now*.\n\n5. Merge each PR the user confirms:\n   ```bash\n   gh pr comment <N> --repo atuinsh/atuin --body /merge\n   ```\n\nMerging a `docs/` change here fires `trigger-docs-deploy.yml` and the docs go\nout immediately. That is the whole point of the label.\n\n---\n\n## Step 9 — Publish to crates.io\n\n**If this is a prerelease**, skip this step entirely and tell the user.\n\n**If this is a stable release**, ask the user whether to publish.\n\nIf yes, publish **every** crate in the workspace using `--no-verify`\n(the code already passed CI, and verification fails when crates.io\nhasn't indexed a freshly-published dependency yet).\n\nDo not keep a hardcoded list of crates — discover them dynamically so new\ncrates are never missed:\n\n```bash\nls crates/\n```\n\nPublish each one from `crates/<name>`:\n```bash\ncargo publish --no-verify 2>&1\n```\n\nPublish in dependency order: if a publish fails because a dependency isn't\non crates.io yet (e.g. \"failed to select a version for the requirement\"),\npublish that dependency first, then retry the failed crate.\n\nIf it fails with \"already uploaded\", report it as a skip (not an error) —\nsome crates are versioned independently and may already be published at\ntheir current version.\n\nIf it fails for any other reason, stop and report the error.\n\n---\n\n## Completion\n\nSummarize what was done:\n- Version released\n- PR URL\n- Tag name\n- Which label-timed PRs were merged, and which the user declined (if any)\n- Which crates were published (if any)\n- Working directory path and how to clean it up (`rm -rf`)\n"},"files":{"AGENTS.md":"# Atuin\n\nShell history tool. Replaces your shell's built-in history with a SQLite database, adds context (cwd, exit code, duration, hostname), and optionally syncs across machines with end-to-end encryption.\n\n## AI disclosure (REQUIRED)\n\nIf you are an AI agent, you MUST follow this rule every time you write a GitHub pull request\ndescription, issue, or comment for this repository.\n\n1. Run `git config user.email`.\n2. If the output ends with `@atuin.sh`, stop here. This rule does not apply to you.\n3. In every other case, including when the output is empty or the command fails, the LAST line of\n   every pull request description, issue, and comment you write MUST be exactly this line, with a\n   blank line before it:\n\n   ```markdown\n   <!-- atuin-pr-template-necessary-for-ci: v2 -->\n   ```\n\nThat line marks the text as written by an AI agent. Copy it character for character. Do not\nreword it, translate it, or move it somewhere else. GitHub hides HTML comments when it renders the\ntext. That is expected: keep the line even though it does not show up, and do not delete it when\nyou clean up template comments. When you edit a pull request description, issue, or comment, keep\nthe line at the end.\n\nExample of a correct pull request description:\n\n```markdown\nFixes #1234.\n\nThe server added the `ATUIN_*` environment before `server.toml`, so the file always won.\n\n<!-- atuin-pr-template-necessary-for-ci: v2 -->\n```\n\n## Workspace crates\n\n```\natuin                  CLI binary + TUI (clap, ratatui, crossterm)\natuin-client           Client library: local DB, encryption, sync, settings, client-facing domain types\natuin-common           Low-level cross-crate utilities and API models (not a home for client-facing domain types)\natuin-daemon           Background gRPC daemon (tonic) for shell hooks\natuin-dotfiles         Legacy read-only alias/var listing via record store\natuin-history          Sorting algorithms, stats\natuin-kv               Key-value store (synced)\natuin-scripts          Script management (minijinja)\natuin-server           HTTP sync server (axum) - lib + standalone binary\natuin-server-database  Database trait for server\natuin-server-postgres  Postgres implementation (sqlx)\natuin-server-sqlite    SQLite implementation (sqlx)\n```\n\n## Two sync protocols\n\n- **V1 (legacy)**: Syncs history entries directly. Removed.\n- **V2 (current)**: Record store abstraction. All data types (history, KV, aliases, vars, scripts) share the same sync infrastructure using tagged records. Envelope-encrypted with PASETO V4 and per-record CEKs.\n\n## Encryption\n\n- **V1**: XSalsa20Poly1305 (secretbox). Key at `~/.atuin/data/key`.\n- **V2**: PASETO V4 Local (XChaCha20-Poly1305 + Blake2b). Envelope encryption: each record gets a random CEK wrapped with the master key. Record metadata (id, idx, version, tag, host) is authenticated as implicit assertions.\n\n## Databases\n\n- **Client**: SQLite everywhere. Separate DBs for history, record store, KV, scripts. All use sqlx + WAL mode.\n- **Server**: Postgres (primary) or SQLite. Auto-detected from URI prefix.\n- Migrations live alongside each crate. Never modify existing migrations, only add new ones.\n  - Exception: the AI session sidecar's migrations (`atuin-client/src/ai_session/migrations`) were squashed into one `0001_init.sql` before release, and the sidecar file was renamed (`ai_harness_sessions.db` → `ai_session_sidecar.db`) so no old sidecar meets the new history. It is now `threads.db`; the daemon renames `ai_session_sidecar.db` on start. Don't add back compatibility with the old migrations; from here on, add new ones as usual.\n\n## Hot paths\n\n`history start`, `history end`, and `init` skip database initialization for latency. Don't add DB calls to these without good reason.\n\n## Conventions\n\n- Crate placement: anything client-facing -- domain types users' code touches and the stores over\n  them -- lives in `atuin-client`. `atuin-common` is for low-level, cross-crate utilities only; do\n  not put client-facing domain types there.\n- Rust 2024 edition, toolchain 1.98.0.\n- Errors: `eyre::Result` in binaries, `thiserror` for typed errors in libraries.\n- Derive boilerplate: `derive_more` (workspace dep) for `Display`, `From`, `Into`, `AsRef`, `Deref`, `Debug` on newtypes and simple enums. Prefer `derive_more` over manual `impl` when the formatting/conversion is a straight delegation. Use `thiserror` (not `derive_more`) for error types. Use `#[as_ref(str)]` on string newtypes for `AsRef<str>`.\n- Async: tokio. Client uses `current_thread`; server uses `multi_thread`.\n- `#![deny(unsafe_code)]` on client/common, `#![forbid(unsafe_code)]` on server.\n- Clippy: `pedantic` + `nursery` on main crate. CI enforces `-D warnings`, on both the default targets and `--tests`.\n- Rustdoc: CI runs `cargo doc --document-private-items --no-deps --workspace` with `RUSTDOCFLAGS=-D warnings`. Broken intra-doc links fail the build.\n- Format: `cargo +nightly fmt`. `.rustfmt.toml` uses nightly-only options, so formatting requires the nightly toolchain even though the project builds on stable 1.97.0.\n- IDs: UUIDv7 (time-ordered), newtype wrappers (`HistoryId`, `RecordId`, `HostId`).\n- Paths: when the original owned `PathBuf` isn't needed afterwards, use\n  `atuin_common::path::PathBufExt::joined` (import `PathBufExt as _`) instead of `.join()`. This\n  always applies to the second and later joins of a chain (`a.join(x).joined(y)`) and to owned\n  temporaries (`home_dir().joined(x)`). Also prefer it over `let mut p = ..; p.push(..);` when\n  that's simpler. Clippy can't flag this, so watch for it.\n- Serialization: MessagePack for encrypted payloads, JSON for API, TOML for config.\n- Storage traits: `Database` (client), `Store` (record store), `Database` (server) -- all `async_trait`.\n- History builders: `HistoryImported`, `HistoryCaptured`, `HistoryFromDb` with compile-time field validation.\n- Feature flags: `client`, `sync`, `daemon`, `clipboard`, `check-update`.\n\n## Testing\n\n- Unit tests inline with `#[cfg(test)]`. Use `rstest` for every test — `#[rstest]`, never a\n  bare `#[test]` (async: `#[rstest]` + `#[tokio::test]`); migrate plain `#[test]`s in files you touch.\n- Lean on `#[fixture]`s for shared setup and compose them; when a test needs teardown, return an\n  RAII guard from the fixture (e.g. a temp dir removed on `Drop`) rather than cleaning up by hand.\n- Parametrize with `#[case(...)]` (input/expected tables) and `#[values(...)]` (cross-products of\n  independent parameters) instead of near-duplicate tests.\n- Reach for `proptest` when a property holds across many inputs — round-trips (encode/decode, serde,\n  parse/display), invariants, idempotence; keep targeted `#[case]`s for known edge cases and regressions.\n- Server integration tests in `crates/atuin-server/tests/` need Postgres (`ATUIN_DB_URI` env var).\n- E2e tests use temporary homes and private daemon sockets. Add shell setups in\n  `crates/atuin/tests/shells/*.toml`. See `crates/atuin/tests/README.md` for dependencies\n  and how to run them.\n- Use `rstest` for tests, especially when they can be made simpler using `case`s and `fixture`s.\n- Use `\":memory:\"` SQLite for unit tests needing a database.\n- Runner: `cargo nextest`.\n\n## Build and check\n\n```sh\ncargo build\ncargo test\ncargo clippy -- -D warnings\ncargo clippy --tests -- -D warnings\ncargo +nightly fmt --check\nRUSTDOCFLAGS=\"-D warnings\" cargo doc --document-private-items --no-deps --workspace\n```\n",".claude/skills/hunk/SKILL.md":"---\nname: hunk-review\ndescription: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files and hunks, reloads session contents, and adds inline review comments. Use when the user has a Hunk session running or wants to review diffs interactively.\n---\n\n# Hunk Review\n\nHunk is an interactive terminal diff viewer. The TUI is for the user -- do NOT run `hunk diff`, `hunk show`, or other interactive commands directly. Use `hunk session *` CLI commands to inspect and control live sessions through the local daemon.\n\nIf no session exists, ask the user to launch Hunk in their terminal first.\n\n## Workflow\n\n```text\n1. hunk session list                                    # find live sessions\n2. hunk session get --repo .                            # inspect path / repo / source\n3. hunk session review --repo . --json                  # inspect file/hunk structure first\n4. hunk session review --repo . --include-patch --json  # opt into raw diff text only when needed\n5. hunk session context --repo .                        # check current focus when needed\n6. hunk session navigate ...                            # move to the right place\n7. hunk session reload -- <command>                     # swap contents if needed\n8. hunk session comment add ...                         # leave one review note\n9. hunk session comment apply ...                       # apply many agent notes in one stdin batch\n```\n\n## Session selection\n\nMost session commands accept:\n\n- `--repo <path>` -- match the live session by its current loaded repo root (most common)\n- `<session-id>` -- match by exact ID (use when multiple sessions share a repo)\n- If only one session exists, it auto-resolves\n\n`reload` also supports:\n\n- `--session-path <path>` -- match the live Hunk window by its current working directory\n- `--source <path>` -- load the replacement `diff` / `show` command from a different directory\n\nUse `--source` only for advanced reloads where the live session you want to control is not already associated with the checkout you want to load next. For a normal worktree session, prefer selecting it directly with `--repo /path/to/worktree`.\n\n## Commands\n\n### Inspect\n\n```bash\nhunk session list [--json]\nhunk session get (--repo . | <id>) [--json]\nhunk session context (--repo . | <id>) [--json]\nhunk session review (--repo . | <id>) [--json] [--include-patch]\n```\n\n- `get` shows the session `Path`, `Repo`, and `Source`, which helps when choosing between `--repo` and `--session-path`\n- `Repo` is what `--repo` matches; `Path` is what `--session-path` matches\n- `review --json` returns file and hunk structure by default; add `--include-patch` only when a caller truly needs raw unified diff text\n\n### Navigate\n\nAbsolute navigation requires `--file` and exactly one of `--hunk`, `--new-line`, or `--old-line`:\n\n```bash\nhunk session navigate --repo . --file src/App.tsx --hunk 2\nhunk session navigate --repo . --file src/App.tsx --new-line 372\nhunk session navigate --repo . --file src/App.tsx --old-line 355\n```\n\nRelative comment navigation jumps between annotated hunks and does not require `--file`:\n\n```bash\nhunk session navigate --repo . --next-comment\nhunk session navigate --repo . --prev-comment\n```\n\n- `--hunk <n>` is 1-based\n- `--new-line` / `--old-line` are 1-based line numbers on that diff side\n- Use either `--next-comment` or `--prev-comment`, not both\n\n### Reload\n\nSwaps the live session's contents. Pass a Hunk review command after `--`:\n\n```bash\nhunk session reload --repo . -- diff\nhunk session reload --repo . -- diff main...feature -- src/ui\nhunk session reload --repo . -- show HEAD~1\nhunk session reload --repo . -- show HEAD~1 -- README.md\nhunk session reload --repo /path/to/worktree -- diff\nhunk session reload --session-path /path/to/live-window --source /path/to/other-checkout -- diff\n```\n\n- Always include `--` before the nested Hunk command\n- `--repo` or `<session-id>` usually selects the session you want\n- `--source` is advanced: it does not select the session; it only changes where the replacement review command runs\n- If the live session is already showing the target worktree, prefer `hunk session reload --repo /path/to/worktree -- diff`\n- `--session-path` targets the live window when you need to keep session selection separate from reload source\n\n### Comments\n\n```bash\nhunk session comment add --repo . --file README.md --new-line 103 --summary \"Tighten this wording\" [--rationale \"...\"] [--author \"agent\"] [--focus]\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' | hunk session comment apply --repo . --stdin [--focus]\nhunk session comment list --repo . [--file README.md]\nhunk session comment rm --repo . <comment-id>\nhunk session comment clear --repo . --yes [--file README.md]\n```\n\n- `comment add` is best for one note; `comment apply` is best when an agent already has several notes ready\n- `comment add` requires `--file`, `--summary`, and exactly one of `--old-line` or `--new-line`\n- `comment apply` payload items require `filePath`, `summary`, and exactly one target such as `hunk`, `hunkNumber`, `oldLine`, or `newLine`\n- `comment apply` reads a JSON batch from stdin and validates the full batch before mutating the live session\n- Pass `--focus` when you want to jump to the new note or the first note in a batch\n- `comment list` and `comment clear` accept optional `--file`\n- Quote `--summary` and `--rationale` defensively in the shell\n\n## New files in working-tree reviews\n\n`hunk diff` includes untracked files by default. If the user wants tracked changes only, reload with `--exclude-untracked`:\n\n```bash\nhunk session reload --repo . -- diff --exclude-untracked\n```\n\n## Guiding a review\n\nThe user may ask you to walk them through a changeset or review code using Hunk. Start with `hunk session review --json` to understand the file/hunk structure without inflating agent context, then use `--include-patch` only for the files you truly need to read in raw diff form. Use `context` and `navigate` to line up the user's current view before adding comments.\n\nYour role is to narrate: steer the user's view to what matters and leave comments that explain what they're looking at.\n\nTypical flow:\n\n1. Load the right content (`reload` if needed)\n2. Navigate to the first interesting file / hunk\n3. Add a comment explaining what's happening and why\n4. If you already have several notes ready, prefer one `comment apply` batch over many separate shell invocations\n5. Summarize when done\n\nGuidelines:\n\n- Work in the order that tells the clearest story, not necessarily file order\n- Navigate before commenting so the user sees the code you're discussing\n- Use `comment apply` for agent-generated batches and `comment add` for one-off notes\n- Use `--focus` sparingly when the note itself should actively steer the review\n- Keep comments focused: intent, structure, risks, or follow-ups\n- Don't comment on every hunk -- highlight what the user wouldn't spot themselves\n\n## Common errors\n\n- **\"No visible diff file matches ...\"** -- the file is not in the loaded review. Check `context`, then `reload` if needed.\n- **\"No active Hunk sessions\"** -- ask the user to open Hunk in their terminal.\n- **\"Multiple active sessions match\"** -- pass `<session-id>` explicitly.\n- **\"No active Hunk session matches session path ...\"** -- for advanced split-path reloads, verify the live window `Path` via `hunk session get` or `list`, then use `--session-path`.\n- **\"Pass the replacement Hunk command after `--`\"** -- include `--` before the nested `diff` / `show` command.\n- **\"Pass --stdin to read batch comments from stdin JSON.\"** -- `comment apply` only reads its batch payload from stdin.\n- **\"Specify exactly one navigation target\"** -- pick one of `--hunk`, `--old-line`, or `--new-line`.\n- **\"Specify either --next-comment or --prev-comment, not both.\"** -- choose one comment-navigation direction.\n",".atuin/skills/release/SKILL.md":"---\nname: release\ndescription: >\n  Orchestrate a multi-step Atuin CLI release — version bumping, changelog\n  generation, PR creation, tagging, and crates.io publishing. Invoke with\n  /release or /release <version>.\ndisable-model-invocation: true\nargument-hint: \"[version]\"\n---\n\n# Atuin CLI Release\n\nYou are orchestrating a release of the Atuin CLI. Follow the steps below\n**in order**, pausing at each checkpoint for user confirmation. Do not skip\nsteps or combine them.\n\n## Current State\n\n- Workspace version: !`sed -n '/^\\[workspace\\.package\\]/,/^\\[/s/^version = \"\\(.*\\)\"/\\1/p' Cargo.toml`\n- Latest tag: !`git describe --tags --abbrev=0 2>/dev/null || echo \"none\"`\n- Suggested next version: !`git-cliff --bumped-version 2>/dev/null | sed 's/^v//' || echo \"(unknown)\"`\n\n---\n\n## Step 1 — Check Dependencies\n\nVerify these tools are installed: `git`, `cargo`, `gh`, `git-cliff`.\n\nOn macOS, also verify `gsed`. On Linux, `sed` is already GNU sed.\n\nUse `command -v` for each. If any are missing, report which ones and stop.\n\nSet the `SED` variable for later use: if on macOS, `SED=gsed`; on Linux, `SED=sed`.\n\n---\n\n## Step 2 — Determine Version\n\nThe target version may be provided as `$ARGUMENTS`. If it's empty, use\nAskUserQuestion to ask for the new version (show the current state above\nfor reference).\n\nAfter determining the version:\n- If it contains a `-` (e.g. `18.15.0-beta.1`), it is a **prerelease**.\n  Note this — it affects changelog and publish behavior later.\n- Show the user: `current → new` and whether it's a prerelease.\n- **Checkpoint:** Ask the user to confirm before proceeding.\n\n---\n\n## Step 2.5 — Survey Release-Labeled PRs\n\nTwo labels mark PRs whose merge has to be timed around the release. They are\ntypically PRs touching `docs/` or `install.sh` — changes that deploy on merge to\n`main` rather than shipping inside the release artifact.\n\n| Label | Merged at | In the tag & artifact? |\n|-------|-----------|------------------------|\n| `merge-immediately-before-release` | Step 7.5 — after the prep PR merges, before the tag | Yes |\n| `merge-immediately-after-release` | Step 8.5 — after the release workflow uploads assets | No |\n\nCheck both:\n\n```bash\ngh pr list --repo atuinsh/atuin --state open \\\n    --label merge-immediately-before-release \\\n    --json number,title,url\ngh pr list --repo atuinsh/atuin --state open \\\n    --label merge-immediately-after-release \\\n    --json number,title,url\n```\n\nIf both come back empty, say so and continue to Step 3. No checkpoint needed.\n\nThe two labels are mutually exclusive: they name opposite sides of the tag. If a\nPR number comes back in **both** lists, that is a labelling mistake — the two\nqueries are independent, so it would otherwise be approved twice, merged at Step\n7.5, then merged again at Step 8.5 against an already-closed PR. **Stop** and ask\nthe user which single timing applies, then treat it as carrying only that label\nfor the rest of the release. Do not guess, and do not carry it into both steps.\n\nIf either is non-empty:\n\n1. Show the user every PR found, grouped by label, with its number, title, and\n   the merge point from the table above.\n2. **Checkpoint:** Use AskUserQuestion (multiSelect) to ask which of these PRs\n   should in fact be merged at their labeled time.\n3. Record the approved set — it carries to Step 7.5 and Step 8.5. A PR the user\n   declines is left alone for the remainder of the release: don't merge it,\n   don't raise it again.\n\nTake each label at face value. It records the PR author's decision about timing;\nit is not a suggestion for you to re-evaluate:\n\n- An `after-release` PR is **not** in the tag and **not** in the release\n  artifact. That is the intended outcome, not a problem to solve. The label is\n  for changes that deploy the moment they merge, so artifact inclusion is\n  irrelevant to them. An author who needed the change in the artifact would have\n  labeled it `merge-immediately-before-release`.\n- Never merge an `after-release` PR early to get it into the release, and never\n  reclassify a PR from one label to the other.\n\n---\n\n## Step 3 — Set Up Working Directory\n\nClone a fresh copy into a temp directory:\n\n```bash\nWORKDIR=$(mktemp -d)\ngit clone git@github.com:atuinsh/atuin.git \"$WORKDIR\"\n```\n\nPrint the working directory path so the user can find it if needed.\n\nNOTE:\nALL subsequent Bash commands run from `$WORKDIR`.\n\n---\n\n## Step 4 — Create Branch & Update Versions\n\n1. Create a release branch named after the version (no `v` prefix):\n   `git checkout -b <VERSION>`\n\n2. Replace the old version with the new one in all `Cargo.toml` files.\n   **Escape dots** in the old version so sed treats them literally:\n\n   ```bash\n   VERSION_PATTERN=\"${OLD_VERSION//./\\\\.}\"\n   find . -type f -name 'Cargo.toml' -not -path './.git/*' \\\n       -exec $SED -i \"s/$VERSION_PATTERN/$NEW_VERSION/g\" {} \\;\n   ```\n\n3. Run `cargo check` to update `Cargo.lock`.\n\n4. Show `git diff --stat` and the version-related lines from the diff:\n   ```bash\n   git diff --unified=0 -- '*.toml' | grep '^\\+.*version' | grep -vF '+++'\n   ```\n   Remember to use macOS grep arguments on macOS systems.\n\n5. Verify the workspace version was actually updated by re-reading it\n   from `Cargo.toml`.\n\n6. **Checkpoint:** Show the diff summary and ask the user to confirm the\n   version changes look correct.\n\n---\n\n## Step 5 — Update Changelog\n\nThe changelog strategy differs for prereleases vs stable releases:\n\n- **Prerelease:** Maintain a running `## [unreleased]` section containing\n  all changes since the last stable release. Use:\n  `git-cliff --unreleased --strip all`\n  (cliff.toml's `ignore_tags` already ignores beta/alpha tags, so\n  `--unreleased` spans back to the last stable release automatically.)\n\n- **Stable release:** Generate a versioned entry that replaces the\n  `[unreleased]` section. Use:\n  `git-cliff --unreleased --tag \"v<VERSION>\" --strip all`\n\nThen update `CHANGELOG.md`:\n\n1. If an existing `## [unreleased]` or `## [Unreleased]` section exists,\n   **remove it entirely** (the heading and all content up to the next\n   `## ` heading).\n\n2. Insert the new entry before the first existing `## ` version heading.\n\n3. **Checkpoint:** Read and display the new changelog entry to the user.\n   Ask if they want any edits. If so, make the requested changes using\n   the Edit tool. Repeat until they're satisfied.\n\n---\n\n## Step 6 — Commit & Push\n\nStage all changes and commit:\n\n```\nchore(release): prepare for release <VERSION>\n```\n\nPush the branch with `--set-upstream origin`.\n\n---\n\n## Step 7 — Create PR & Wait for Merge\n\n### Create the PR\n\nExtract the changelog entry body (everything between the new `## ` heading\nand the next one) for the PR description.\n\nFor prereleases, the heading to match is `## [unreleased]`.\nFor stable releases, it's `## <VERSION>` (escape dots in the awk pattern).\n\nCreate the PR:\n```bash\ngh pr create \\\n    --title \"chore(release): prepare for release <VERSION>\" \\\n    --body \"<body with changelog>\" \\\n    --repo atuinsh/atuin \\\n    --draft\n```\n\nShow the PR URL to the user. Tell the user to go review the PR, mark it ready,\nand comment `/merge` on it.\n\nWhen the PR shows as merged, proceed to the next step.\n\n---\n\n## Step 7.5 — Merge `merge-immediately-before-release` PRs\n\nSkip to Step 8 unless Step 2.5 recorded approved PRs with this label.\n\nThe prep PR has merged and the tag does not exist yet. This is the only window\nin which these PRs land in `v<VERSION>` and its artifacts.\n\nFor each approved PR:\n\n1. Check it is still mergeable and its checks are green:\n   ```bash\n   gh pr view <N> --repo atuinsh/atuin \\\n       --json mergeable,mergeStateStatus,statusCheckRollup\n   ```\n   If it is not mergeable, or checks are red, report that and ask the user how\n   to proceed. Do not force it through.\n\n2. **Checkpoint:** Show the PR number and title and ask the user to confirm this\n   specific merge. Step 2.5 approved the *timing*; this confirms merging *now*.\n\n3. Merge, then wait until the PR shows as merged:\n   ```bash\n   gh pr comment <N> --repo atuinsh/atuin --body /merge\n   gh pr view <N> --repo atuinsh/atuin --json state --jq .state  # until MERGED\n   ```\n\nThese commits land in the tag but are absent from the changelog generated in\nStep 5, which was cut before they merged. That is expected — do not regenerate\nthe changelog to include them.\n\n---\n\n## Step 8 — Tag Release\n\nBack in the working directory:\n\n```bash\ngit checkout main\ngit pull\ngit tag \"v<VERSION>\"\ngit push --tags\n```\n\nTell the user the tag was pushed and the release CI workflow has been\ntriggered.\n\n---\n\n## Step 8.5 — Merge `merge-immediately-after-release` PRs\n\nSkip to Step 9 unless Step 2.5 recorded approved PRs with this label.\n\n\"After the release\" means **after the release workflow has finished and the\nassets exist** — not merely after the tag was pushed. These PRs deploy the\ninstant they merge, so a docs change announcing the new version must not go\nlive while the release it points at is still building.\n\n1. Wait for the release workflow the tag just triggered:\n   ```bash\n   gh run list --repo atuinsh/atuin --workflow release.yml \\\n       --branch \"v<VERSION>\" --limit 1\n   gh run watch <run-id> --repo atuinsh/atuin --exit-status\n   ```\n   `--exit-status` is what makes a failed run exit non-zero. Without it\n   `gh run watch` prints the failure but still exits `0`, and a broken release\n   sails through into the merges below. If it exits non-zero, **stop** here —\n   do not go on to the release lookup.\n\n2. Confirm the release exists and has assets attached:\n   ```bash\n   gh release view \"v<VERSION>\" --repo atuinsh/atuin --json assets,isDraft\n   ```\n   If the release is missing, is still a draft, or has no assets, **stop**.\n   Report it and merge nothing — these PRs keep until the release is actually\n   out. A release object alone is not evidence: it may predate this run or have\n   been left half-built by a failed workflow. What clears this gate is a\n   non-draft release with assets attached.\n\n3. Re-check that each approved PR is still mergeable and its checks are green:\n   ```bash\n   gh pr view <N> --repo atuinsh/atuin \\\n       --json mergeable,mergeStateStatus,statusCheckRollup\n   ```\n   Step 2.5 surveyed these PRs before the release was even built, so its\n   findings are now many minutes stale — long enough for a PR to pick up a\n   conflict or a red check. If it is not mergeable, or checks are red, report\n   that and ask the user how to proceed. Do not force it through. These PRs\n   deploy the instant they merge, so there is no CI gate downstream to catch a\n   bad one.\n\n4. **Checkpoint:** For each approved PR, show its number and title and ask the\n   user to confirm this specific merge. Step 2.5 approved the *timing*; this\n   confirms merging *now*.\n\n5. Merge each PR the user confirms:\n   ```bash\n   gh pr comment <N> --repo atuinsh/atuin --body /merge\n   ```\n\nMerging a `docs/` change here fires `trigger-docs-deploy.yml` and the docs go\nout immediately. That is the whole point of the label.\n\n---\n\n## Step 9 — Publish to crates.io\n\n**If this is a prerelease**, skip this step entirely and tell the user.\n\n**If this is a stable release**, ask the user whether to publish.\n\nIf yes, publish **every** crate in the workspace using `--no-verify`\n(the code already passed CI, and verification fails when crates.io\nhasn't indexed a freshly-published dependency yet).\n\nDo not keep a hardcoded list of crates — discover them dynamically so new\ncrates are never missed:\n\n```bash\nls crates/\n```\n\nPublish each one from `crates/<name>`:\n```bash\ncargo publish --no-verify 2>&1\n```\n\nPublish in dependency order: if a publish fails because a dependency isn't\non crates.io yet (e.g. \"failed to select a version for the requirement\"),\npublish that dependency first, then retry the failed crate.\n\nIf it fails with \"already uploaded\", report it as a skip (not an error) —\nsome crates are versioned independently and may already be published at\ntheir current version.\n\nIf it fails for any other reason, stop and report the error.\n\n---\n\n## Completion\n\nSummarize what was done:\n- Version released\n- PR URL\n- Tag name\n- Which label-timed PRs were merged, and which the user declined (if any)\n- Which crates were published (if any)\n- Working directory path and how to clean it up (`rm -rf`)\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/atuinsh/atuin/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Atuin\n\nShell history tool. Replaces your shell's built-in history with a SQLite database, adds context (cwd, exit code, duration, hostname), and optionally syncs across machines with end-to-end encryption.\n\n## AI disclosure (REQUIRED)\n\nIf you are an AI agent, you MUST follow this rule every time you write a GitHub pull request\ndescription, issue, or comment for this repository.\n\n1. Run `git config user.email`.\n2. If the output ends with `@atuin.sh`, stop here. This rule does not apply to you.\n3. In every other case, including when the output is empty or the command fails, the LAST line of\n   every pull request description, issue, and comment you write MUST be exactly this line, with a\n   blank line before it:\n\n   ```markdown\n   <!-- atuin-pr-template-necessary-for-ci: v2 -->\n   ```\n\nThat line marks the text as written by an AI agent. Copy it character for character. Do not\nreword it, translate it, or move it somewhere else. GitHub hides HTML comments when it renders the\ntext. That is expected: keep the line even though it does not show up, and do not delete it when\nyou clean up template comments. When you edit a pull request description, issue, or comment, keep\nthe line at the end.\n\nExample of a correct pull request description:\n\n```markdown\nFixes #1234.\n\nThe server added the `ATUIN_*` environment before `server.toml`, so the file always won.\n\n<!-- atuin-pr-template-necessary-for-ci: v2 -->\n```\n\n## Workspace crates\n\n```\natuin                  CLI binary + TUI (clap, ratatui, crossterm)\natuin-client           Client library: local DB, encryption, sync, settings, client-facing domain types\natuin-common           Low-level cross-crate utilities and API models (not a home for client-facing domain types)\natuin-daemon           Background gRPC daemon (tonic) for shell hooks\natuin-dotfiles         Legacy read-only alias/var listing via record store\natuin-history          Sorting algorithms, stats\natuin-kv               Key-value store (synced)\natuin-scripts          Script management (minijinja)\natuin-server           HTTP sync server (axum) - lib + standalone binary\natuin-server-database  Database trait for server\natuin-server-postgres  Postgres implementation (sqlx)\natuin-server-sqlite    SQLite implementation (sqlx)\n```\n\n## Two sync protocols\n\n- **V1 (legacy)**: Syncs history entries directly. Removed.\n- **V2 (current)**: Record store abstraction. All data types (history, KV, aliases, vars, scripts) share the same sync infrastructure using tagged records. Envelope-encrypted with PASETO V4 and per-record CEKs.\n\n## Encryption\n\n- **V1**: XSalsa20Poly1305 (secretbox). Key at `~/.atuin/data/key`.\n- **V2**: PASETO V4 Local (XChaCha20-Poly1305 + Blake2b). Envelope encryption: each record gets a random CEK wrapped with the master key. Record metadata (id, idx, version, tag, host) is authenticated as implicit assertions.\n\n## Databases\n\n- **Client**: SQLite everywhere. Separate DBs for history, record store, KV, scripts. All use sqlx + WAL mode.\n- **Server**: Postgres (primary) or SQLite. Auto-detected from URI prefix.\n- Migrations live alongside each crate. Never modify existing migrations, only add new ones.\n  - Exception: the AI session sidecar's migrations (`atuin-client/src/ai_session/migrations`) were squashed into one `0001_init.sql` before release, and the sidecar file was renamed (`ai_harness_sessions.db` → `ai_session_sidecar.db`) so no old sidecar meets the new history. It is now `threads.db`; the daemon renames `ai_session_sidecar.db` on start. Don't add back compatibility with the old migrations; from here on, add new ones as usual.\n\n## Hot paths\n\n`history start`, `history end`, and `init` skip database initialization for latency. Don't add DB calls to these without good reason.\n\n## Conventions\n\n- Crate placement: anything client-facing -- domain types users' code touches and the stores over\n  them -- lives in `atuin-client`. `atuin-common` is for low-level, cross-crate utilities only; do\n  not put client-facing domain types there.\n- Rust 2024 edition, toolchain 1.98.0.\n- Errors: `eyre::Result` in binaries, `thiserror` for typed errors in libraries.\n- Derive boilerplate: `derive_more` (workspace dep) for `Display`, `From`, `Into`, `AsRef`, `Deref`, `Debug` on newtypes and simple enums. Prefer `derive_more` over manual `impl` when the formatting/conversion is a straight delegation. Use `thiserror` (not `derive_more`) for error types. Use `#[as_ref(str)]` on string newtypes for `AsRef<str>`.\n- Async: tokio. Client uses `current_thread`; server uses `multi_thread`.\n- `#![deny(unsafe_code)]` on client/common, `#![forbid(unsafe_code)]` on server.\n- Clippy: `pedantic` + `nursery` on main crate. CI enforces `-D warnings`, on both the default targets and `--tests`.\n- Rustdoc: CI runs `cargo doc --document-private-items --no-deps --workspace` with `RUSTDOCFLAGS=-D warnings`. Broken intra-doc links fail the build.\n- Format: `cargo +nightly fmt`. `.rustfmt.toml` uses nightly-only options, so formatting requires the nightly toolchain even though the project builds on stable 1.97.0.\n- IDs: UUIDv7 (time-ordered), newtype wrappers (`HistoryId`, `RecordId`, `HostId`).\n- Paths: when the original owned `PathBuf` isn't needed afterwards, use\n  `atuin_common::path::PathBufExt::joined` (import `PathBufExt as _`) instead of `.join()`. This\n  always applies to the second and later joins of a chain (`a.join(x).joined(y)`) and to owned\n  temporaries (`home_dir().joined(x)`). Also prefer it over `let mut p = ..; p.push(..);` when\n  that's simpler. Clippy can't flag this, so watch for it.\n- Serialization: MessagePack for encrypted payloads, JSON for API, TOML for config.\n- Storage traits: `Database` (client), `Store` (record store), `Database` (server) -- all `async_trait`.\n- History builders: `HistoryImported`, `HistoryCaptured`, `HistoryFromDb` with compile-time field validation.\n- Feature flags: `client`, `sync`, `daemon`, `clipboard`, `check-update`.\n\n## Testing\n\n- Unit tests inline with `#[cfg(test)]`. Use `rstest` for every test — `#[rstest]`, never a\n  bare `#[test]` (async: `#[rstest]` + `#[tokio::test]`); migrate plain `#[test]`s in files you touch.\n- Lean on `#[fixture]`s for shared setup and compose them; when a test needs teardown, return an\n  RAII guard from the fixture (e.g. a temp dir removed on `Drop`) rather than cleaning up by hand.\n- Parametrize with `#[case(...)]` (input/expected tables) and `#[values(...)]` (cross-products of\n  independent parameters) instead of near-duplicate tests.\n- Reach for `proptest` when a property holds across many inputs — round-trips (encode/decode, serde,\n  parse/display), invariants, idempotence; keep targeted `#[case]`s for known edge cases and regressions.\n- Server integration tests in `crates/atuin-server/tests/` need Postgres (`ATUIN_DB_URI` env var).\n- E2e tests use temporary homes and private daemon sockets. Add shell setups in\n  `crates/atuin/tests/shells/*.toml`. See `crates/atuin/tests/README.md` for dependencies\n  and how to run them.\n- Use `rstest` for tests, especially when they can be made simpler using `case`s and `fixture`s.\n- Use `\":memory:\"` SQLite for unit tests needing a database.\n- Runner: `cargo nextest`.\n\n## Build and check\n\n```sh\ncargo build\ncargo test\ncargo clippy -- -D warnings\ncargo clippy --tests -- -D warnings\ncargo +nightly fmt --check\nRUSTDOCFLAGS=\"-D warnings\" cargo doc --document-private-items --no-deps --workspace\n```\n","isInternal":false,"tokens":1897,"sizeBytes":7421},{"name":"SKILL.md","path":".atuin/skills/release/SKILL.md","rawUrl":"https://raw.githubusercontent.com/atuinsh/atuin/HEAD/.atuin/skills/release/SKILL.md","title":"release","category":"anthropic-skill","format":"markdown","content":"---\nname: release\ndescription: >\n  Orchestrate a multi-step Atuin CLI release — version bumping, changelog\n  generation, PR creation, tagging, and crates.io publishing. Invoke with\n  /release or /release <version>.\ndisable-model-invocation: true\nargument-hint: \"[version]\"\n---\n\n# Atuin CLI Release\n\nYou are orchestrating a release of the Atuin CLI. Follow the steps below\n**in order**, pausing at each checkpoint for user confirmation. Do not skip\nsteps or combine them.\n\n## Current State\n\n- Workspace version: !`sed -n '/^\\[workspace\\.package\\]/,/^\\[/s/^version = \"\\(.*\\)\"/\\1/p' Cargo.toml`\n- Latest tag: !`git describe --tags --abbrev=0 2>/dev/null || echo \"none\"`\n- Suggested next version: !`git-cliff --bumped-version 2>/dev/null | sed 's/^v//' || echo \"(unknown)\"`\n\n---\n\n## Step 1 — Check Dependencies\n\nVerify these tools are installed: `git`, `cargo`, `gh`, `git-cliff`.\n\nOn macOS, also verify `gsed`. On Linux, `sed` is already GNU sed.\n\nUse `command -v` for each. If any are missing, report which ones and stop.\n\nSet the `SED` variable for later use: if on macOS, `SED=gsed`; on Linux, `SED=sed`.\n\n---\n\n## Step 2 — Determine Version\n\nThe target version may be provided as `$ARGUMENTS`. If it's empty, use\nAskUserQuestion to ask for the new version (show the current state above\nfor reference).\n\nAfter determining the version:\n- If it contains a `-` (e.g. `18.15.0-beta.1`), it is a **prerelease**.\n  Note this — it affects changelog and publish behavior later.\n- Show the user: `current → new` and whether it's a prerelease.\n- **Checkpoint:** Ask the user to confirm before proceeding.\n\n---\n\n## Step 2.5 — Survey Release-Labeled PRs\n\nTwo labels mark PRs whose merge has to be timed around the release. They are\ntypically PRs touching `docs/` or `install.sh` — changes that deploy on merge to\n`main` rather than shipping inside the release artifact.\n\n| Label | Merged at | In the tag & artifact? |\n|-------|-----------|------------------------|\n| `merge-immediately-before-release` | Step 7.5 — after the prep PR merges, before the tag | Yes |\n| `merge-immediately-after-release` | Step 8.5 — after the release workflow uploads assets | No |\n\nCheck both:\n\n```bash\ngh pr list --repo atuinsh/atuin --state open \\\n    --label merge-immediately-before-release \\\n    --json number,title,url\ngh pr list --repo atuinsh/atuin --state open \\\n    --label merge-immediately-after-release \\\n    --json number,title,url\n```\n\nIf both come back empty, say so and continue to Step 3. No checkpoint needed.\n\nThe two labels are mutually exclusive: they name opposite sides of the tag. If a\nPR number comes back in **both** lists, that is a labelling mistake — the two\nqueries are independent, so it would otherwise be approved twice, merged at Step\n7.5, then merged again at Step 8.5 against an already-closed PR. **Stop** and ask\nthe user which single timing applies, then treat it as carrying only that label\nfor the rest of the release. Do not guess, and do not carry it into both steps.\n\nIf either is non-empty:\n\n1. Show the user every PR found, grouped by label, with its number, title, and\n   the merge point from the table above.\n2. **Checkpoint:** Use AskUserQuestion (multiSelect) to ask which of these PRs\n   should in fact be merged at their labeled time.\n3. Record the approved set — it carries to Step 7.5 and Step 8.5. A PR the user\n   declines is left alone for the remainder of the release: don't merge it,\n   don't raise it again.\n\nTake each label at face value. It records the PR author's decision about timing;\nit is not a suggestion for you to re-evaluate:\n\n- An `after-release` PR is **not** in the tag and **not** in the release\n  artifact. That is the intended outcome, not a problem to solve. The label is\n  for changes that deploy the moment they merge, so artifact inclusion is\n  irrelevant to them. An author who needed the change in the artifact would have\n  labeled it `merge-immediately-before-release`.\n- Never merge an `after-release` PR early to get it into the release, and never\n  reclassify a PR from one label to the other.\n\n---\n\n## Step 3 — Set Up Working Directory\n\nClone a fresh copy into a temp directory:\n\n```bash\nWORKDIR=$(mktemp -d)\ngit clone git@github.com:atuinsh/atuin.git \"$WORKDIR\"\n```\n\nPrint the working directory path so the user can find it if needed.\n\nNOTE:\nALL subsequent Bash commands run from `$WORKDIR`.\n\n---\n\n## Step 4 — Create Branch & Update Versions\n\n1. Create a release branch named after the version (no `v` prefix):\n   `git checkout -b <VERSION>`\n\n2. Replace the old version with the new one in all `Cargo.toml` files.\n   **Escape dots** in the old version so sed treats them literally:\n\n   ```bash\n   VERSION_PATTERN=\"${OLD_VERSION//./\\\\.}\"\n   find . -type f -name 'Cargo.toml' -not -path './.git/*' \\\n       -exec $SED -i \"s/$VERSION_PATTERN/$NEW_VERSION/g\" {} \\;\n   ```\n\n3. Run `cargo check` to update `Cargo.lock`.\n\n4. Show `git diff --stat` and the version-related lines from the diff:\n   ```bash\n   git diff --unified=0 -- '*.toml' | grep '^\\+.*version' | grep -vF '+++'\n   ```\n   Remember to use macOS grep arguments on macOS systems.\n\n5. Verify the workspace version was actually updated by re-reading it\n   from `Cargo.toml`.\n\n6. **Checkpoint:** Show the diff summary and ask the user to confirm the\n   version changes look correct.\n\n---\n\n## Step 5 — Update Changelog\n\nThe changelog strategy differs for prereleases vs stable releases:\n\n- **Prerelease:** Maintain a running `## [unreleased]` section containing\n  all changes since the last stable release. Use:\n  `git-cliff --unreleased --strip all`\n  (cliff.toml's `ignore_tags` already ignores beta/alpha tags, so\n  `--unreleased` spans back to the last stable release automatically.)\n\n- **Stable release:** Generate a versioned entry that replaces the\n  `[unreleased]` section. Use:\n  `git-cliff --unreleased --tag \"v<VERSION>\" --strip all`\n\nThen update `CHANGELOG.md`:\n\n1. If an existing `## [unreleased]` or `## [Unreleased]` section exists,\n   **remove it entirely** (the heading and all content up to the next\n   `## ` heading).\n\n2. Insert the new entry before the first existing `## ` version heading.\n\n3. **Checkpoint:** Read and display the new changelog entry to the user.\n   Ask if they want any edits. If so, make the requested changes using\n   the Edit tool. Repeat until they're satisfied.\n\n---\n\n## Step 6 — Commit & Push\n\nStage all changes and commit:\n\n```\nchore(release): prepare for release <VERSION>\n```\n\nPush the branch with `--set-upstream origin`.\n\n---\n\n## Step 7 — Create PR & Wait for Merge\n\n### Create the PR\n\nExtract the changelog entry body (everything between the new `## ` heading\nand the next one) for the PR description.\n\nFor prereleases, the heading to match is `## [unreleased]`.\nFor stable releases, it's `## <VERSION>` (escape dots in the awk pattern).\n\nCreate the PR:\n```bash\ngh pr create \\\n    --title \"chore(release): prepare for release <VERSION>\" \\\n    --body \"<body with changelog>\" \\\n    --repo atuinsh/atuin \\\n    --draft\n```\n\nShow the PR URL to the user. Tell the user to go review the PR, mark it ready,\nand comment `/merge` on it.\n\nWhen the PR shows as merged, proceed to the next step.\n\n---\n\n## Step 7.5 — Merge `merge-immediately-before-release` PRs\n\nSkip to Step 8 unless Step 2.5 recorded approved PRs with this label.\n\nThe prep PR has merged and the tag does not exist yet. This is the only window\nin which these PRs land in `v<VERSION>` and its artifacts.\n\nFor each approved PR:\n\n1. Check it is still mergeable and its checks are green:\n   ```bash\n   gh pr view <N> --repo atuinsh/atuin \\\n       --json mergeable,mergeStateStatus,statusCheckRollup\n   ```\n   If it is not mergeable, or checks are red, report that and ask the user how\n   to proceed. Do not force it through.\n\n2. **Checkpoint:** Show the PR number and title and ask the user to confirm this\n   specific merge. Step 2.5 approved the *timing*; this confirms merging *now*.\n\n3. Merge, then wait until the PR shows as merged:\n   ```bash\n   gh pr comment <N> --repo atuinsh/atuin --body /merge\n   gh pr view <N> --repo atuinsh/atuin --json state --jq .state  # until MERGED\n   ```\n\nThese commits land in the tag but are absent from the changelog generated in\nStep 5, which was cut before they merged. That is expected — do not regenerate\nthe changelog to include them.\n\n---\n\n## Step 8 — Tag Release\n\nBack in the working directory:\n\n```bash\ngit checkout main\ngit pull\ngit tag \"v<VERSION>\"\ngit push --tags\n```\n\nTell the user the tag was pushed and the release CI workflow has been\ntriggered.\n\n---\n\n## Step 8.5 — Merge `merge-immediately-after-release` PRs\n\nSkip to Step 9 unless Step 2.5 recorded approved PRs with this label.\n\n\"After the release\" means **after the release workflow has finished and the\nassets exist** — not merely after the tag was pushed. These PRs deploy the\ninstant they merge, so a docs change announcing the new version must not go\nlive while the release it points at is still building.\n\n1. Wait for the release workflow the tag just triggered:\n   ```bash\n   gh run list --repo atuinsh/atuin --workflow release.yml \\\n       --branch \"v<VERSION>\" --limit 1\n   gh run watch <run-id> --repo atuinsh/atuin --exit-status\n   ```\n   `--exit-status` is what makes a failed run exit non-zero. Without it\n   `gh run watch` prints the failure but still exits `0`, and a broken release\n   sails through into the merges below. If it exits non-zero, **stop** here —\n   do not go on to the release lookup.\n\n2. Confirm the release exists and has assets attached:\n   ```bash\n   gh release view \"v<VERSION>\" --repo atuinsh/atuin --json assets,isDraft\n   ```\n   If the release is missing, is still a draft, or has no assets, **stop**.\n   Report it and merge nothing — these PRs keep until the release is actually\n   out. A release object alone is not evidence: it may predate this run or have\n   been left half-built by a failed workflow. What clears this gate is a\n   non-draft release with assets attached.\n\n3. Re-check that each approved PR is still mergeable and its checks are green:\n   ```bash\n   gh pr view <N> --repo atuinsh/atuin \\\n       --json mergeable,mergeStateStatus,statusCheckRollup\n   ```\n   Step 2.5 surveyed these PRs before the release was even built, so its\n   findings are now many minutes stale — long enough for a PR to pick up a\n   conflict or a red check. If it is not mergeable, or checks are red, report\n   that and ask the user how to proceed. Do not force it through. These PRs\n   deploy the instant they merge, so there is no CI gate downstream to catch a\n   bad one.\n\n4. **Checkpoint:** For each approved PR, show its number and title and ask the\n   user to confirm this specific merge. Step 2.5 approved the *timing*; this\n   confirms merging *now*.\n\n5. Merge each PR the user confirms:\n   ```bash\n   gh pr comment <N> --repo atuinsh/atuin --body /merge\n   ```\n\nMerging a `docs/` change here fires `trigger-docs-deploy.yml` and the docs go\nout immediately. That is the whole point of the label.\n\n---\n\n## Step 9 — Publish to crates.io\n\n**If this is a prerelease**, skip this step entirely and tell the user.\n\n**If this is a stable release**, ask the user whether to publish.\n\nIf yes, publish **every** crate in the workspace using `--no-verify`\n(the code already passed CI, and verification fails when crates.io\nhasn't indexed a freshly-published dependency yet).\n\nDo not keep a hardcoded list of crates — discover them dynamically so new\ncrates are never missed:\n\n```bash\nls crates/\n```\n\nPublish each one from `crates/<name>`:\n```bash\ncargo publish --no-verify 2>&1\n```\n\nPublish in dependency order: if a publish fails because a dependency isn't\non crates.io yet (e.g. \"failed to select a version for the requirement\"),\npublish that dependency first, then retry the failed crate.\n\nIf it fails with \"already uploaded\", report it as a skip (not an error) —\nsome crates are versioned independently and may already be published at\ntheir current version.\n\nIf it fails for any other reason, stop and report the error.\n\n---\n\n## Completion\n\nSummarize what was done:\n- Version released\n- PR URL\n- Tag name\n- Which label-timed PRs were merged, and which the user declined (if any)\n- Which crates were published (if any)\n- Working directory path and how to clean it up (`rm -rf`)\n","frontmatter":{"name":"release","description":"Orchestrate a multi-step Atuin CLI release — version bumping, changelog generation, PR creation, tagging, and crates.io publishing. Invoke with /release or /release <version>.\n","disable-model-invocation":true,"argument-hint":"[version]","disableModelInvocation":true},"isInternal":false,"tokens":3145,"sizeBytes":12283},{"name":"SKILL.md","path":".claude/skills/hunk/SKILL.md","rawUrl":"https://raw.githubusercontent.com/atuinsh/atuin/HEAD/.claude/skills/hunk/SKILL.md","title":"hunk-review","category":"anthropic-skill","format":"markdown","content":"---\nname: hunk-review\ndescription: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files and hunks, reloads session contents, and adds inline review comments. Use when the user has a Hunk session running or wants to review diffs interactively.\n---\n\n# Hunk Review\n\nHunk is an interactive terminal diff viewer. The TUI is for the user -- do NOT run `hunk diff`, `hunk show`, or other interactive commands directly. Use `hunk session *` CLI commands to inspect and control live sessions through the local daemon.\n\nIf no session exists, ask the user to launch Hunk in their terminal first.\n\n## Workflow\n\n```text\n1. hunk session list                                    # find live sessions\n2. hunk session get --repo .                            # inspect path / repo / source\n3. hunk session review --repo . --json                  # inspect file/hunk structure first\n4. hunk session review --repo . --include-patch --json  # opt into raw diff text only when needed\n5. hunk session context --repo .                        # check current focus when needed\n6. hunk session navigate ...                            # move to the right place\n7. hunk session reload -- <command>                     # swap contents if needed\n8. hunk session comment add ...                         # leave one review note\n9. hunk session comment apply ...                       # apply many agent notes in one stdin batch\n```\n\n## Session selection\n\nMost session commands accept:\n\n- `--repo <path>` -- match the live session by its current loaded repo root (most common)\n- `<session-id>` -- match by exact ID (use when multiple sessions share a repo)\n- If only one session exists, it auto-resolves\n\n`reload` also supports:\n\n- `--session-path <path>` -- match the live Hunk window by its current working directory\n- `--source <path>` -- load the replacement `diff` / `show` command from a different directory\n\nUse `--source` only for advanced reloads where the live session you want to control is not already associated with the checkout you want to load next. For a normal worktree session, prefer selecting it directly with `--repo /path/to/worktree`.\n\n## Commands\n\n### Inspect\n\n```bash\nhunk session list [--json]\nhunk session get (--repo . | <id>) [--json]\nhunk session context (--repo . | <id>) [--json]\nhunk session review (--repo . | <id>) [--json] [--include-patch]\n```\n\n- `get` shows the session `Path`, `Repo`, and `Source`, which helps when choosing between `--repo` and `--session-path`\n- `Repo` is what `--repo` matches; `Path` is what `--session-path` matches\n- `review --json` returns file and hunk structure by default; add `--include-patch` only when a caller truly needs raw unified diff text\n\n### Navigate\n\nAbsolute navigation requires `--file` and exactly one of `--hunk`, `--new-line`, or `--old-line`:\n\n```bash\nhunk session navigate --repo . --file src/App.tsx --hunk 2\nhunk session navigate --repo . --file src/App.tsx --new-line 372\nhunk session navigate --repo . --file src/App.tsx --old-line 355\n```\n\nRelative comment navigation jumps between annotated hunks and does not require `--file`:\n\n```bash\nhunk session navigate --repo . --next-comment\nhunk session navigate --repo . --prev-comment\n```\n\n- `--hunk <n>` is 1-based\n- `--new-line` / `--old-line` are 1-based line numbers on that diff side\n- Use either `--next-comment` or `--prev-comment`, not both\n\n### Reload\n\nSwaps the live session's contents. Pass a Hunk review command after `--`:\n\n```bash\nhunk session reload --repo . -- diff\nhunk session reload --repo . -- diff main...feature -- src/ui\nhunk session reload --repo . -- show HEAD~1\nhunk session reload --repo . -- show HEAD~1 -- README.md\nhunk session reload --repo /path/to/worktree -- diff\nhunk session reload --session-path /path/to/live-window --source /path/to/other-checkout -- diff\n```\n\n- Always include `--` before the nested Hunk command\n- `--repo` or `<session-id>` usually selects the session you want\n- `--source` is advanced: it does not select the session; it only changes where the replacement review command runs\n- If the live session is already showing the target worktree, prefer `hunk session reload --repo /path/to/worktree -- diff`\n- `--session-path` targets the live window when you need to keep session selection separate from reload source\n\n### Comments\n\n```bash\nhunk session comment add --repo . --file README.md --new-line 103 --summary \"Tighten this wording\" [--rationale \"...\"] [--author \"agent\"] [--focus]\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' | hunk session comment apply --repo . --stdin [--focus]\nhunk session comment list --repo . [--file README.md]\nhunk session comment rm --repo . <comment-id>\nhunk session comment clear --repo . --yes [--file README.md]\n```\n\n- `comment add` is best for one note; `comment apply` is best when an agent already has several notes ready\n- `comment add` requires `--file`, `--summary`, and exactly one of `--old-line` or `--new-line`\n- `comment apply` payload items require `filePath`, `summary`, and exactly one target such as `hunk`, `hunkNumber`, `oldLine`, or `newLine`\n- `comment apply` reads a JSON batch from stdin and validates the full batch before mutating the live session\n- Pass `--focus` when you want to jump to the new note or the first note in a batch\n- `comment list` and `comment clear` accept optional `--file`\n- Quote `--summary` and `--rationale` defensively in the shell\n\n## New files in working-tree reviews\n\n`hunk diff` includes untracked files by default. If the user wants tracked changes only, reload with `--exclude-untracked`:\n\n```bash\nhunk session reload --repo . -- diff --exclude-untracked\n```\n\n## Guiding a review\n\nThe user may ask you to walk them through a changeset or review code using Hunk. Start with `hunk session review --json` to understand the file/hunk structure without inflating agent context, then use `--include-patch` only for the files you truly need to read in raw diff form. Use `context` and `navigate` to line up the user's current view before adding comments.\n\nYour role is to narrate: steer the user's view to what matters and leave comments that explain what they're looking at.\n\nTypical flow:\n\n1. Load the right content (`reload` if needed)\n2. Navigate to the first interesting file / hunk\n3. Add a comment explaining what's happening and why\n4. If you already have several notes ready, prefer one `comment apply` batch over many separate shell invocations\n5. Summarize when done\n\nGuidelines:\n\n- Work in the order that tells the clearest story, not necessarily file order\n- Navigate before commenting so the user sees the code you're discussing\n- Use `comment apply` for agent-generated batches and `comment add` for one-off notes\n- Use `--focus` sparingly when the note itself should actively steer the review\n- Keep comments focused: intent, structure, risks, or follow-ups\n- Don't comment on every hunk -- highlight what the user wouldn't spot themselves\n\n## Common errors\n\n- **\"No visible diff file matches ...\"** -- the file is not in the loaded review. Check `context`, then `reload` if needed.\n- **\"No active Hunk sessions\"** -- ask the user to open Hunk in their terminal.\n- **\"Multiple active sessions match\"** -- pass `<session-id>` explicitly.\n- **\"No active Hunk session matches session path ...\"** -- for advanced split-path reloads, verify the live window `Path` via `hunk session get` or `list`, then use `--session-path`.\n- **\"Pass the replacement Hunk command after `--`\"** -- include `--` before the nested `diff` / `show` command.\n- **\"Pass --stdin to read batch comments from stdin JSON.\"** -- `comment apply` only reads its batch payload from stdin.\n- **\"Specify exactly one navigation target\"** -- pick one of `--hunk`, `--old-line`, or `--new-line`.\n- **\"Specify either --next-comment or --prev-comment, not both.\"** -- choose one comment-navigation direction.\n","frontmatter":{"name":"hunk-review","description":"Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files and hunks, reloads session contents, and adds inline review comments. Use when the user has a Hunk session running or wants to review diffs interactively."},"isInternal":false,"tokens":1927,"sizeBytes":7921}],"systemPromptSnippet":"<yakaai_skills repo=\"atuinsh/atuin\">\n<!-- File: AGENTS.md (Tokens: ~1897 | Category: root-instruction) -->\n# Atuin\n\nShell history tool. Replaces your shell's built-in history with a SQLite database, adds context (cwd, exit code, duration, hostname), and optionally syncs across machines with end-to-end encryption.\n\n## AI disclosure (REQUIRED)\n\nIf you are an AI agent, you MUST follow this rule every time you write a GitHub pull request\ndescription, issue, or comment for this repository.\n\n1. Run `git config user.email`.\n2. If the output ends with `@atuin.sh`, stop here. This rule does not apply to you.\n3. In every other case, including when the output is empty or the command fails, the LAST line of\n   every pull request description, issue, and comment you write MUST be exactly this line, with a\n   blank line before it:\n\n   ```markdown\n   <!-- atuin-pr-template-necessary-for-ci: v2 -->\n   ```\n\nThat line marks the text as written by an AI agent. Copy it character for character. Do not\nreword it, translate it, or move it somewhere else. GitHub hides HTML comments when it renders the\ntext. That is expected: keep the line even though it does not show up, and do not delete it when\nyou clean up template comments. When you edit a pull request description, issue, or comment, keep\nthe line at the end.\n\nExample of a correct pull request description:\n\n```markdown\nFixes #1234.\n\nThe server added the `ATUIN_*` environment before `server.toml`, so the file always won.\n\n<!-- atuin-pr-template-necessary-for-ci: v2 -->\n```\n\n## Workspace crates\n\n```\natuin                  CLI binary + TUI (clap, ratatui, crossterm)\natuin-client           Client library: local DB, encryption, sync, settings, client-facing domain types\natuin-common           Low-level cross-crate utilities and API models (not a home for client-facing domain types)\natuin-daemon           Background gRPC daemon (tonic) for shell hooks\natuin-dotfiles         Legacy read-only alias/var listing via record store\natuin-history          Sorting algorithms, stats\natuin-kv               Key-value store (synced)\natuin-scripts          Script management (minijinja)\natuin-server           HTTP sync server (axum) - lib + standalone binary\natuin-server-database  Database trait for server\natuin-server-postgres  Postgres implementation (sqlx)\natuin-server-sqlite    SQLite implementation (sqlx)\n```\n\n## Two sync protocols\n\n- **V1 (legacy)**: Syncs history entries directly. Removed.\n- **V2 (current)**: Record store abstraction. All data types (history, KV, aliases, vars, scripts) share the same sync infrastructure using tagged records. Envelope-encrypted with PASETO V4 and per-record CEKs.\n\n## Encryption\n\n- **V1**: XSalsa20Poly1305 (secretbox). Key at `~/.atuin/data/key`.\n- **V2**: PASETO V4 Local (XChaCha20-Poly1305 + Blake2b). Envelope encryption: each record gets a random CEK wrapped with the master key. Record metadata (id, idx, version, tag, host) is authenticated as implicit assertions.\n\n## Databases\n\n- **Client**: SQLite everywhere. Separate DBs for history, record store, KV, scripts. All use sqlx + WAL mode.\n- **Server**: Postgres (primary) or SQLite. Auto-detected from URI prefix.\n- Migrations live alongside each crate. Never modify existing migrations, only add new ones.\n  - Exception: the AI session sidecar's migrations (`atuin-client/src/ai_session/migrations`) were squashed into one `0001_init.sql` before release, and the sidecar file was renamed (`ai_harness_sessions.db` → `ai_session_sidecar.db`) so no old sidecar meets the new history. It is now `threads.db`; the daemon renames `ai_session_sidecar.db` on start. Don't add back compatibility with the old migrations; from here on, add new ones as usual.\n\n## Hot paths\n\n`history start`, `history end`, and `init` skip database initialization for latency. Don't add DB calls to these without good reason.\n\n## Conventions\n\n- Crate placement: anything client-facing -- domain types users' code touches and the stores over\n  them -- lives in `atuin-client`. `atuin-common` is for low-level, cross-crate utilities only; do\n  not put client-facing domain types there.\n- Rust 2024 edition, toolchain 1.98.0.\n- Errors: `eyre::Result` in binaries, `thiserror` for typed errors in libraries.\n- Derive boilerplate: `derive_more` (workspace dep) for `Display`, `From`, `Into`, `AsRef`, `Deref`, `Debug` on newtypes and simple enums. Prefer `derive_more` over manual `impl` when the formatting/conversion is a straight delegation. Use `thiserror` (not `derive_more`) for error types. Use `#[as_ref(str)]` on string newtypes for `AsRef<str>`.\n- Async: tokio. Client uses `current_thread`; server uses `multi_thread`.\n- `#![deny(unsafe_code)]` on client/common, `#![forbid(unsafe_code)]` on server.\n- Clippy: `pedantic` + `nursery` on main crate. CI enforces `-D warnings`, on both the default targets and `--tests`.\n- Rustdoc: CI runs `cargo doc --document-private-items --no-deps --workspace` with `RUSTDOCFLAGS=-D warnings`. Broken intra-doc links fail the build.\n- Format: `cargo +nightly fmt`. `.rustfmt.toml` uses nightly-only options, so formatting requires the nightly toolchain even though the project builds on stable 1.97.0.\n- IDs: UUIDv7 (time-ordered), newtype wrappers (`HistoryId`, `RecordId`, `HostId`).\n- Paths: when the original owned `PathBuf` isn't needed afterwards, use\n  `atuin_common::path::PathBufExt::joined` (import `PathBufExt as _`) instead of `.join()`. This\n  always applies to the second and later joins of a chain (`a.join(x).joined(y)`) and to owned\n  temporaries (`home_dir().joined(x)`). Also prefer it over `let mut p = ..; p.push(..);` when\n  that's simpler. Clippy can't flag this, so watch for it.\n- Serialization: MessagePack for encrypted payloads, JSON for API, TOML for config.\n- Storage traits: `Database` (client), `Store` (record store), `Database` (server) -- all `async_trait`.\n- History builders: `HistoryImported`, `HistoryCaptured`, `HistoryFromDb` with compile-time field validation.\n- Feature flags: `client`, `sync`, `daemon`, `clipboard`, `check-update`.\n\n## Testing\n\n- Unit tests inline with `#[cfg(test)]`. Use `rstest` for every test — `#[rstest]`, never a\n  bare `#[test]` (async: `#[rstest]` + `#[tokio::test]`); migrate plain `#[test]`s in files you touch.\n- Lean on `#[fixture]`s for shared setup and compose them; when a test needs teardown, return an\n  RAII guard from the fixture (e.g. a temp dir removed on `Drop`) rather than cleaning up by hand.\n- Parametrize with `#[case(...)]` (input/expected tables) and `#[values(...)]` (cross-products of\n  independent parameters) instead of near-duplicate tests.\n- Reach for `proptest` when a property holds across many inputs — round-trips (encode/decode, serde,\n  parse/display), invariants, idempotence; keep targeted `#[case]`s for known edge cases and regressions.\n- Server integration tests in `crates/atuin-server/tests/` need Postgres (`ATUIN_DB_URI` env var).\n- E2e tests use temporary homes and private daemon sockets. Add shell setups in\n  `crates/atuin/tests/shells/*.toml`. See `crates/atuin/tests/README.md` for dependencies\n  and how to run them.\n- Use `rstest` for tests, especially when they can be made simpler using `case`s and `fixture`s.\n- Use `\":memory:\"` SQLite for unit tests needing a database.\n- Runner: `cargo nextest`.\n\n## Build and check\n\n```sh\ncargo build\ncargo test\ncargo clippy -- -D warnings\ncargo clippy --tests -- -D warnings\ncargo +nightly fmt --check\nRUSTDOCFLAGS=\"-D warnings\" cargo doc --document-private-items --no-deps --workspace\n```\n\n<!-- File: .atuin/skills/release/SKILL.md (Tokens: ~3145 | Category: anthropic-skill) -->\n---\nname: release\ndescription: >\n  Orchestrate a multi-step Atuin CLI release — version bumping, changelog\n  generation, PR creation, tagging, and crates.io publishing. Invoke with\n  /release or /release <version>.\ndisable-model-invocation: true\nargument-hint: \"[version]\"\n---\n\n# Atuin CLI Release\n\nYou are orchestrating a release of the Atuin CLI. Follow the steps below\n**in order**, pausing at each checkpoint for user confirmation. Do not skip\nsteps or combine them.\n\n## Current State\n\n- Workspace version: !`sed -n '/^\\[workspace\\.package\\]/,/^\\[/s/^version = \"\\(.*\\)\"/\\1/p' Cargo.toml`\n- Latest tag: !`git describe --tags --abbrev=0 2>/dev/null || echo \"none\"`\n- Suggested next version: !`git-cliff --bumped-version 2>/dev/null | sed 's/^v//' || echo \"(unknown)\"`\n\n---\n\n## Step 1 — Check Dependencies\n\nVerify these tools are installed: `git`, `cargo`, `gh`, `git-cliff`.\n\nOn macOS, also verify `gsed`. On Linux, `sed` is already GNU sed.\n\nUse `command -v` for each. If any are missing, report which ones and stop.\n\nSet the `SED` variable for later use: if on macOS, `SED=gsed`; on Linux, `SED=sed`.\n\n---\n\n## Step 2 — Determine Version\n\nThe target version may be provided as `$ARGUMENTS`. If it's empty, use\nAskUserQuestion to ask for the new version (show the current state above\nfor reference).\n\nAfter determining the version:\n- If it contains a `-` (e.g. `18.15.0-beta.1`), it is a **prerelease**.\n  Note this — it affects changelog and publish behavior later.\n- Show the user: `current → new` and whether it's a prerelease.\n- **Checkpoint:** Ask the user to confirm before proceeding.\n\n---\n\n## Step 2.5 — Survey Release-Labeled PRs\n\nTwo labels mark PRs whose merge has to be timed around the release. They are\ntypically PRs touching `docs/` or `install.sh` — changes that deploy on merge to\n`main` rather than shipping inside the release artifact.\n\n| Label | Merged at | In the tag & artifact? |\n|-------|-----------|------------------------|\n| `merge-immediately-before-release` | Step 7.5 — after the prep PR merges, before the tag | Yes |\n| `merge-immediately-after-release` | Step 8.5 — after the release workflow uploads assets | No |\n\nCheck both:\n\n```bash\ngh pr list --repo atuinsh/atuin --state open \\\n    --label merge-immediately-before-release \\\n    --json number,title,url\ngh pr list --repo atuinsh/atuin --state open \\\n    --label merge-immediately-after-release \\\n    --json number,title,url\n```\n\nIf both come back empty, say so and continue to Step 3. No checkpoint needed.\n\nThe two labels are mutually exclusive: they name opposite sides of the tag. If a\nPR number comes back in **both** lists, that is a labelling mistake — the two\nqueries are independent, so it would otherwise be approved twice, merged at Step\n7.5, then merged again at Step 8.5 against an already-closed PR. **Stop** and ask\nthe user which single timing applies, then treat it as carrying only that label\nfor the rest of the release. Do not guess, and do not carry it into both steps.\n\nIf either is non-empty:\n\n1. Show the user every PR found, grouped by label, with its number, title, and\n   the merge point from the table above.\n2. **Checkpoint:** Use AskUserQuestion (multiSelect) to ask which of these PRs\n   should in fact be merged at their labeled time.\n3. Record the approved set — it carries to Step 7.5 and Step 8.5. A PR the user\n   declines is left alone for the remainder of the release: don't merge it,\n   don't raise it again.\n\nTake each label at face value. It records the PR author's decision about timing;\nit is not a suggestion for you to re-evaluate:\n\n- An `after-release` PR is **not** in the tag and **not** in the release\n  artifact. That is the intended outcome, not a problem to solve. The label is\n  for changes that deploy the moment they merge, so artifact inclusion is\n  irrelevant to them. An author who needed the change in the artifact would have\n  labeled it `merge-immediately-before-release`.\n- Never merge an `after-release` PR early to get it into the release, and never\n  reclassify a PR from one label to the other.\n\n---\n\n## Step 3 — Set Up Working Directory\n\nClone a fresh copy into a temp directory:\n\n```bash\nWORKDIR=$(mktemp -d)\ngit clone git@github.com:atuinsh/atuin.git \"$WORKDIR\"\n```\n\nPrint the working directory path so the user can find it if needed.\n\nNOTE:\nALL subsequent Bash commands run from `$WORKDIR`.\n\n---\n\n## Step 4 — Create Branch & Update Versions\n\n1. Create a release branch named after the version (no `v` prefix):\n   `git checkout -b <VERSION>`\n\n2. Replace the old version with the new one in all `Cargo.toml` files.\n   **Escape dots** in the old version so sed treats them literally:\n\n   ```bash\n   VERSION_PATTERN=\"${OLD_VERSION//./\\\\.}\"\n   find . -type f -name 'Cargo.toml' -not -path './.git/*' \\\n       -exec $SED -i \"s/$VERSION_PATTERN/$NEW_VERSION/g\" {} \\;\n   ```\n\n3. Run `cargo check` to update `Cargo.lock`.\n\n4. Show `git diff --stat` and the version-related lines from the diff:\n   ```bash\n   git diff --unified=0 -- '*.toml' | grep '^\\+.*version' | grep -vF '+++'\n   ```\n   Remember to use macOS grep arguments on macOS systems.\n\n5. Verify the workspace version was actually updated by re-reading it\n   from `Cargo.toml`.\n\n6. **Checkpoint:** Show the diff summary and ask the user to confirm the\n   version changes look correct.\n\n---\n\n## Step 5 — Update Changelog\n\nThe changelog strategy differs for prereleases vs stable releases:\n\n- **Prerelease:** Maintain a running `## [unreleased]` section containing\n  all changes since the last stable release. Use:\n  `git-cliff --unreleased --strip all`\n  (cliff.toml's `ignore_tags` already ignores beta/alpha tags, so\n  `--unreleased` spans back to the last stable release automatically.)\n\n- **Stable release:** Generate a versioned entry that replaces the\n  `[unreleased]` section. Use:\n  `git-cliff --unreleased --tag \"v<VERSION>\" --strip all`\n\nThen update `CHANGELOG.md`:\n\n1. If an existing `## [unreleased]` or `## [Unreleased]` section exists,\n   **remove it entirely** (the heading and all content up to the next\n   `## ` heading).\n\n2. Insert the new entry before the first existing `## ` version heading.\n\n3. **Checkpoint:** Read and display the new changelog entry to the user.\n   Ask if they want any edits. If so, make the requested changes using\n   the Edit tool. Repeat until they're satisfied.\n\n---\n\n## Step 6 — Commit & Push\n\nStage all changes and commit:\n\n```\nchore(release): prepare for release <VERSION>\n```\n\nPush the branch with `--set-upstream origin`.\n\n---\n\n## Step 7 — Create PR & Wait for Merge\n\n### Create the PR\n\nExtract the changelog entry body (everything between the new `## ` heading\nand the next one) for the PR description.\n\nFor prereleases, the heading to match is `## [unreleased]`.\nFor stable releases, it's `## <VERSION>` (escape dots in the awk pattern).\n\nCreate the PR:\n```bash\ngh pr create \\\n    --title \"chore(release): prepare for release <VERSION>\" \\\n    --body \"<body with changelog>\" \\\n    --repo atuinsh/atuin \\\n    --draft\n```\n\nShow the PR URL to the user. Tell the user to go review the PR, mark it ready,\nand comment `/merge` on it.\n\nWhen the PR shows as merged, proceed to the next step.\n\n---\n\n## Step 7.5 — Merge `merge-immediately-before-release` PRs\n\nSkip to Step 8 unless Step 2.5 recorded approved PRs with this label.\n\nThe prep PR has merged and the tag does not exist yet. This is the only window\nin which these PRs land in `v<VERSION>` and its artifacts.\n\nFor each approved PR:\n\n1. Check it is still mergeable and its checks are green:\n   ```bash\n   gh pr view <N> --repo atuinsh/atuin \\\n       --json mergeable,mergeStateStatus,statusCheckRollup\n   ```\n   If it is not mergeable, or checks are red, report that and ask the user how\n   to proceed. Do not force it through.\n\n2. **Checkpoint:** Show the PR number and title and ask the user to confirm this\n   specific merge. Step 2.5 approved the *timing*; this confirms merging *now*.\n\n3. Merge, then wait until the PR shows as merged:\n   ```bash\n   gh pr comment <N> --repo atuinsh/atuin --body /merge\n   gh pr view <N> --repo atuinsh/atuin --json state --jq .state  # until MERGED\n   ```\n\nThese commits land in the tag but are absent from the changelog generated in\nStep 5, which was cut before they merged. That is expected — do not regenerate\nthe changelog to include them.\n\n---\n\n## Step 8 — Tag Release\n\nBack in the working directory:\n\n```bash\ngit checkout main\ngit pull\ngit tag \"v<VERSION>\"\ngit push --tags\n```\n\nTell the user the tag was pushed and the release CI workflow has been\ntriggered.\n\n---\n\n## Step 8.5 — Merge `merge-immediately-after-release` PRs\n\nSkip to Step 9 unless Step 2.5 recorded approved PRs with this label.\n\n\"After the release\" means **after the release workflow has finished and the\nassets exist** — not merely after the tag was pushed. These PRs deploy the\ninstant they merge, so a docs change announcing the new version must not go\nlive while the release it points at is still building.\n\n1. Wait for the release workflow the tag just triggered:\n   ```bash\n   gh run list --repo atuinsh/atuin --workflow release.yml \\\n       --branch \"v<VERSION>\" --limit 1\n   gh run watch <run-id> --repo atuinsh/atuin --exit-status\n   ```\n   `--exit-status` is what makes a failed run exit non-zero. Without it\n   `gh run watch` prints the failure but still exits `0`, and a broken release\n   sails through into the merges below. If it exits non-zero, **stop** here —\n   do not go on to the release lookup.\n\n2. Confirm the release exists and has assets attached:\n   ```bash\n   gh release view \"v<VERSION>\" --repo atuinsh/atuin --json assets,isDraft\n   ```\n   If the release is missing, is still a draft, or has no assets, **stop**.\n   Report it and merge nothing — these PRs keep until the release is actually\n   out. A release object alone is not evidence: it may predate this run or have\n   been left half-built by a failed workflow. What clears this gate is a\n   non-draft release with assets attached.\n\n3. Re-check that each approved PR is still mergeable and its checks are green:\n   ```bash\n   gh pr view <N> --repo atuinsh/atuin \\\n       --json mergeable,mergeStateStatus,statusCheckRollup\n   ```\n   Step 2.5 surveyed these PRs before the release was even built, so its\n   findings are now many minutes stale — long enough for a PR to pick up a\n   conflict or a red check. If it is not mergeable, or checks are red, report\n   that and ask the user how to proceed. Do not force it through. These PRs\n   deploy the instant they merge, so there is no CI gate downstream to catch a\n   bad one.\n\n4. **Checkpoint:** For each approved PR, show its number and title and ask the\n   user to confirm this specific merge. Step 2.5 approved the *timing*; this\n   confirms merging *now*.\n\n5. Merge each PR the user confirms:\n   ```bash\n   gh pr comment <N> --repo atuinsh/atuin --body /merge\n   ```\n\nMerging a `docs/` change here fires `trigger-docs-deploy.yml` and the docs go\nout immediately. That is the whole point of the label.\n\n---\n\n## Step 9 — Publish to crates.io\n\n**If this is a prerelease**, skip this step entirely and tell the user.\n\n**If this is a stable release**, ask the user whether to publish.\n\nIf yes, publish **every** crate in the workspace using `--no-verify`\n(the code already passed CI, and verification fails when crates.io\nhasn't indexed a freshly-published dependency yet).\n\nDo not keep a hardcoded list of crates — discover them dynamically so new\ncrates are never missed:\n\n```bash\nls crates/\n```\n\nPublish each one from `crates/<name>`:\n```bash\ncargo publish --no-verify 2>&1\n```\n\nPublish in dependency order: if a publish fails because a dependency isn't\non crates.io yet (e.g. \"failed to select a version for the requirement\"),\npublish that dependency first, then retry the failed crate.\n\nIf it fails with \"already uploaded\", report it as a skip (not an error) —\nsome crates are versioned independently and may already be published at\ntheir current version.\n\nIf it fails for any other reason, stop and report the error.\n\n---\n\n## Completion\n\nSummarize what was done:\n- Version released\n- PR URL\n- Tag name\n- Which label-timed PRs were merged, and which the user declined (if any)\n- Which crates were published (if any)\n- Working directory path and how to clean it up (`rm -rf`)\n\n<!-- File: .claude/skills/hunk/SKILL.md (Tokens: ~1927 | Category: anthropic-skill) -->\n---\nname: hunk-review\ndescription: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files and hunks, reloads session contents, and adds inline review comments. Use when the user has a Hunk session running or wants to review diffs interactively.\n---\n\n# Hunk Review\n\nHunk is an interactive terminal diff viewer. The TUI is for the user -- do NOT run `hunk diff`, `hunk show`, or other interactive commands directly. Use `hunk session *` CLI commands to inspect and control live sessions through the local daemon.\n\nIf no session exists, ask the user to launch Hunk in their terminal first.\n\n## Workflow\n\n```text\n1. hunk session list                                    # find live sessions\n2. hunk session get --repo .                            # inspect path / repo / source\n3. hunk session review --repo . --json                  # inspect file/hunk structure first\n4. hunk session review --repo . --include-patch --json  # opt into raw diff text only when needed\n5. hunk session context --repo .                        # check current focus when needed\n6. hunk session navigate ...                            # move to the right place\n7. hunk session reload -- <command>                     # swap contents if needed\n8. hunk session comment add ...                         # leave one review note\n9. hunk session comment apply ...                       # apply many agent notes in one stdin batch\n```\n\n## Session selection\n\nMost session commands accept:\n\n- `--repo <path>` -- match the live session by its current loaded repo root (most common)\n- `<session-id>` -- match by exact ID (use when multiple sessions share a repo)\n- If only one session exists, it auto-resolves\n\n`reload` also supports:\n\n- `--session-path <path>` -- match the live Hunk window by its current working directory\n- `--source <path>` -- load the replacement `diff` / `show` command from a different directory\n\nUse `--source` only for advanced reloads where the live session you want to control is not already associated with the checkout you want to load next. For a normal worktree session, prefer selecting it directly with `--repo /path/to/worktree`.\n\n## Commands\n\n### Inspect\n\n```bash\nhunk session list [--json]\nhunk session get (--repo . | <id>) [--json]\nhunk session context (--repo . | <id>) [--json]\nhunk session review (--repo . | <id>) [--json] [--include-patch]\n```\n\n- `get` shows the session `Path`, `Repo`, and `Source`, which helps when choosing between `--repo` and `--session-path`\n- `Repo` is what `--repo` matches; `Path` is what `--session-path` matches\n- `review --json` returns file and hunk structure by default; add `--include-patch` only when a caller truly needs raw unified diff text\n\n### Navigate\n\nAbsolute navigation requires `--file` and exactly one of `--hunk`, `--new-line`, or `--old-line`:\n\n```bash\nhunk session navigate --repo . --file src/App.tsx --hunk 2\nhunk session navigate --repo . --file src/App.tsx --new-line 372\nhunk session navigate --repo . --file src/App.tsx --old-line 355\n```\n\nRelative comment navigation jumps between annotated hunks and does not require `--file`:\n\n```bash\nhunk session navigate --repo . --next-comment\nhunk session navigate --repo . --prev-comment\n```\n\n- `--hunk <n>` is 1-based\n- `--new-line` / `--old-line` are 1-based line numbers on that diff side\n- Use either `--next-comment` or `--prev-comment`, not both\n\n### Reload\n\nSwaps the live session's contents. Pass a Hunk review command after `--`:\n\n```bash\nhunk session reload --repo . -- diff\nhunk session reload --repo . -- diff main...feature -- src/ui\nhunk session reload --repo . -- show HEAD~1\nhunk session reload --repo . -- show HEAD~1 -- README.md\nhunk session reload --repo /path/to/worktree -- diff\nhunk session reload --session-path /path/to/live-window --source /path/to/other-checkout -- diff\n```\n\n- Always include `--` before the nested Hunk command\n- `--repo` or `<session-id>` usually selects the session you want\n- `--source` is advanced: it does not select the session; it only changes where the replacement review command runs\n- If the live session is already showing the target worktree, prefer `hunk session reload --repo /path/to/worktree -- diff`\n- `--session-path` targets the live window when you need to keep session selection separate from reload source\n\n### Comments\n\n```bash\nhunk session comment add --repo . --file README.md --new-line 103 --summary \"Tighten this wording\" [--rationale \"...\"] [--author \"agent\"] [--focus]\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' | hunk session comment apply --repo . --stdin [--focus]\nhunk session comment list --repo . [--file README.md]\nhunk session comment rm --repo . <comment-id>\nhunk session comment clear --repo . --yes [--file README.md]\n```\n\n- `comment add` is best for one note; `comment apply` is best when an agent already has several notes ready\n- `comment add` requires `--file`, `--summary`, and exactly one of `--old-line` or `--new-line`\n- `comment apply` payload items require `filePath`, `summary`, and exactly one target such as `hunk`, `hunkNumber`, `oldLine`, or `newLine`\n- `comment apply` reads a JSON batch from stdin and validates the full batch before mutating the live session\n- Pass `--focus` when you want to jump to the new note or the first note in a batch\n- `comment list` and `comment clear` accept optional `--file`\n- Quote `--summary` and `--rationale` defensively in the shell\n\n## New files in working-tree reviews\n\n`hunk diff` includes untracked files by default. If the user wants tracked changes only, reload with `--exclude-untracked`:\n\n```bash\nhunk session reload --repo . -- diff --exclude-untracked\n```\n\n## Guiding a review\n\nThe user may ask you to walk them through a changeset or review code using Hunk. Start with `hunk session review --json` to understand the file/hunk structure without inflating agent context, then use `--include-patch` only for the files you truly need to read in raw diff form. Use `context` and `navigate` to line up the user's current view before adding comments.\n\nYour role is to narrate: steer the user's view to what matters and leave comments that explain what they're looking at.\n\nTypical flow:\n\n1. Load the right content (`reload` if needed)\n2. Navigate to the first interesting file / hunk\n3. Add a comment explaining what's happening and why\n4. If you already have several notes ready, prefer one `comment apply` batch over many separate shell invocations\n5. Summarize when done\n\nGuidelines:\n\n- Work in the order that tells the clearest story, not necessarily file order\n- Navigate before commenting so the user sees the code you're discussing\n- Use `comment apply` for agent-generated batches and `comment add` for one-off notes\n- Use `--focus` sparingly when the note itself should actively steer the review\n- Keep comments focused: intent, structure, risks, or follow-ups\n- Don't comment on every hunk -- highlight what the user wouldn't spot themselves\n\n## Common errors\n\n- **\"No visible diff file matches ...\"** -- the file is not in the loaded review. Check `context`, then `reload` if needed.\n- **\"No active Hunk sessions\"** -- ask the user to open Hunk in their terminal.\n- **\"Multiple active sessions match\"** -- pass `<session-id>` explicitly.\n- **\"No active Hunk session matches session path ...\"** -- for advanced split-path reloads, verify the live window `Path` via `hunk session get` or `list`, then use `--session-path`.\n- **\"Pass the replacement Hunk command after `--`\"** -- include `--` before the nested `diff` / `show` command.\n- **\"Pass --stdin to read batch comments from stdin JSON.\"** -- `comment apply` only reads its batch payload from stdin.\n- **\"Specify exactly one navigation target\"** -- pick one of `--hunk`, `--old-line`, or `--new-line`.\n- **\"Specify either --next-comment or --prev-comment, not both.\"** -- choose one comment-navigation direction.\n</yakaai_skills>"}