{"owner":"modem-dev","repo":"hunk","hasSkills":true,"totalSkillsCount":35,"totalTokensCount":41646,"categories":["anthropic-skill","root-instruction","claude-rule","plugin-manifest","subagent-persona"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/hunk-extensions/SKILL.md",".agents/skills/hunk-review/SKILL.md","AGENTS.md","CLAUDE.md","examples/extensions/cli-tools/README.md","examples/extensions/code-document-file-view/README.md","examples/extensions/inline-edit/README.md","examples/extensions/jsx-file-view-gallery/README.md","examples/extensions/jsx-file-view-gallery/mixed-review/README.md","examples/extensions/jsx-file-view/README.md","examples/extensions/pane-layout/README.md","examples/extensions/rendered-markdown/README.md","examples/extensions/review-note-navigator/README.md","examples/extensions/review-snapshot-export/README.md","examples/extensions/review-triage/README.md","examples/extensions/vim-navigation/README.md","packages/hunk-git/README.md","packages/hunk-jj/README.md","packages/hunk-sapling/README.md","packages/hunk-vcs/README.md","packages/hunk/README.md","packages/hunk/skills/hunk-extensions/SKILL.md","packages/hunk/skills/hunk-review/SKILL.md","packages/session-broker-bun/README.md","packages/session-broker-core/README.md","packages/session-broker-node/README.md","packages/session-broker/README.md","packages/term-video/README.md","skills/hunk-launch-video/SKILL.md","skills/hunk-release/SKILL.md","website/src/content/docs/docs/agents/agent-context-and-stml.md","website/src/content/docs/docs/agents/comments-and-annotations.md","website/src/content/docs/docs/agents/live-session-control.md","website/src/content/docs/docs/agents/review-skill.md","website/src/content/docs/docs/agents/review-with-an-agent.md"],"skills":{".agents/skills/hunk-extensions/SKILL.md":"---\nname: hunk-extensions\ndescription: Maps the `hunkdiff/extension` authoring surface for Hunk, the terminal diff viewer — hiding or reordering reviewed files, docked panes, alternate file views, commands and key bindings, dialogs, workspace writes, themes, syntax languages, VCS backends, lifecycle events. Use when writing, debugging, or installing a Hunk extension, or when a request asks Hunk itself to behave differently. Not for reviewing a diff in a live session — that is hunk-review.\n---\n\n<!-- generated by `bun run generate:skill` -->\n\n# hunk-extensions\n\nThis file is a pointer. The instructions live in the installed `hunk` CLI so they always match its version.\n\nBefore anything else, load them and follow them:\n\n```bash\nhunk skill show hunk-extensions\n```\n\nIf `hunk` is not on PATH, ask the user to install Hunk first: https://hunk.dev/docs/start/install/\n",".agents/skills/hunk-review/SKILL.md":"---\nname: hunk-review\ndescription: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files, hunks, and exact lines, reloads session contents, adds inline review comments, and paints attention marks on character ranges. Use when the user has a Hunk session running or wants to review diffs interactively.\n---\n\n<!-- generated by `bun run generate:skill` -->\n\n# hunk-review\n\nThis file is a pointer. The instructions live in the installed `hunk` CLI so they always match its version.\n\nBefore anything else, load them and follow them:\n\n```bash\nhunk skill show hunk-review\n```\n\nIf `hunk` is not on PATH, ask the user to install Hunk first: https://hunk.dev/docs/start/install/\n","AGENTS.md":"# hunk agent notes\n\n## purpose\n\n- Terminal-first diff viewer for understanding coding-agent changesets.\n- Product target is \"modern desktop diff tool in a terminal\", not a pager-style TUI.\n\n## architecture\n\n### workspace map\n\n```text\npackages/hunk/                 published CLI, application, and public extension/OpenTUI facades\npackages/hunk-vcs/             private dependency-bottom VCS helpers\npackages/hunk-{git,jj,sapling}/ private bundled VCS providers\npackages/hunk-gh/              private bundled GitHub review extension\npackages/session-broker-core/  low-level broker protocol and state\npackages/session-broker/       runtime-neutral broker, daemon, auth, and connection lifecycle\npackages/session-broker-{bun,node}/ runtime listener adapters\npackages/term-video/           private terminal capture tooling\nwebsite/                       product/documentation site, not the browser review client\n```\n\n### application flow\n\n```text\nmain.tsx -> app/startup.ts -> app/cli.ts\n  -> headless command/history plan returned to main.tsx, or\n  -> app plan: extensionBootstrap.ts -> sessionBootstrap.ts -> normalized Changeset / DiffFile[]\n     -> main.tsx lazy-loads runInteractiveApp.tsx -> HunkSessionHost -> AppHost -> App\n     -> pane/diff planning -> Pierre-backed terminal rows\n```\n\n### shared review seam\n\nTerminal, agent/session, broker/protocol, extension, and HTTP/SSE consumers share review semantics.\nThe browser client and UI remain planned. Do not recreate semantic review behavior in a surface:\n\n```text\nDiffFile[] -> projectReviewDocument -> ReviewDocumentV1 -> ReviewStore\nReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer -> surface projection\n```\n\n- **Model:** `packages/hunk/src/core/review/{types,document,identity}.ts` owns the ordered, JSON-safe document.\n  File order is review/sidebar order; use `key` (referenced as `fileKey` elsewhere),\n  `contentIdentity`, and `sourceIdentity` (cached source text additionally requires\n  `sourceAttested`) — not runtime IDs or indexes — across reloads/surfaces.\n- **Shared derivations:** `geometry.ts`, `expansion.ts`, `anchors.ts`, `stml.ts`, and\n  `contentManifest.ts` own ranges, gaps, source splitting, note targets/ownership, tag roles, and\n  parity manifests. Consume them; never re-derive those facts in a renderer.\n- **State:** `state.ts` is semantic state; `actions.ts` transitions; `reducer.ts` pure/no-I/O;\n  `selectors.ts` shared policies; `store.ts` synchronous observable storage. New cross-surface\n  operations start as intents. Callers supply mutable-note IDs/timestamps; core derives identities.\n- **Surfaces/publishers:** `useTerminalReview.ts` is the TUI adapter and\n  `reviewNoteMapping.ts` is terminal-only. Rows, measurement, scrolling, layout, themes, DOM\n  mechanics, and source I/O stay local. `useHunkSessionBridge.ts` publishes the current terminal\n  session export; `registration.ts` builds its metadata/initial snapshot and `bridge.ts` receives\n  agent commands. This broker export is not a full `ReviewState` mirror.\n- **Other consumers:** Web/API consumers reuse the model, derivations, state, intents, and the\n  producer/protocol tier. Never build a parallel protocol. Keep presentation/client-local state\n  local; host/extension commands need explicit remote capabilities. See\n  `docs/browser-review-rebuild.md` for the rollout and current boundaries.\n- **Conformance:** `test/review-conformance/` has hand-authored semantic fixtures covering every\n  registered core, terminal, producer, broker, protocol, and extension projection. Every new\n  semantic consumer registers its real projection and runs the whole corpus.\n  `scripts/quality/source-boundaries.test.ts` keeps the seam\n  renderer/platform-free; its Node-debt list is shrink-only and tombstone lists append-only. A\n  repaid seam finding deletes copies, adds a file or banned-symbol tombstone and adversarial\n  fixture, registers consumers, and updates `docs/browser-review-seam-audit.md`.\n\n- Bundled VCS implementations live in the private `packages/hunk-{git,jj,sapling}` workspaces and consume\n  the public `hunkdiff/extension` contract plus explicit `@hunk/vcs/*` implementation leaves;\n  `packages/hunk/src/app` composes their registrations into the\n  provider-neutral core VCS catalog. Do not add provider commands, spawning, or source readers under `packages/hunk/src/core`.\n- `hunk daemon serve` is the one loopback daemon for all live sessions; sessions auto-start and\n  register with it rather than opening per-TUI ports. Reuse `classifyReviewPublication` and\n  `ReviewChunkAssembler` for publication ordering and bounded, digest-verified resources. Browser\n  review stays same-origin with no CORS; each session mints its capability and gives the daemon only\n  its digest. Transport semantics come from the browser-safe review protocol modules and the\n  existing intent path. See `docs/browser-review-rebuild.md` and the relevant module headers.\n- User extensions, bundled VCS providers, bundled core commands, and bundled UI share one public\n  registration API and registry model. VCS and UI use dedicated registries; bundled core commands\n  load first into each session-owned extension registry so delegated resources share its lifecycle. `ExtensionSession` owns the\n  user registry across routed surfaces. Keep `packages/hunk/src/extension-api/types.ts` import-free,\n  bundled VCS renderer-free, repo-local extensions trust-gated, and bundled extensions active under\n  `--no-extensions`. See `docs/extension-architecture.md`, `docs/extensions.md`, and\n  `packages/hunk/skills/hunk-extensions/SKILL.md`.\n- Sidecar file order is intentional sidebar and review-stream order.\n- **Session wire:** the daemon and every window exchange `HUNK_SESSION_DAEMON_VERSION`\n  (`packages/hunk/src/session/protocol.ts`) in the signed hello and require an exact match. Any\n  change to what a session registers or snapshots — under `packages/hunk/src/session/**`,\n  `packages/hunk/src/core/reviewDescriptor.ts`, `packages/hunk/src/app/session/registration.ts`, or\n  anything else that appears in `packages/hunk/src/session/broker/fixtures/session-wire.v<N>.json`\n  — requires a bump. `wire.snapshot.test.ts` enforces it; bump, run\n  `bun run generate:session-wire`, and delete the previous fixture in the same change. The\n  cross-revision admin scope (`status`/`stop`) is frozen separately and never grows in place.\n- Derive shared rendering, navigation, scrolling, and note behavior from one planning layer. Make\n  shared geometry explicit, and remove obsolete paths instead of retaining parallel implementations.\n\n## architectural rules\n\n- `bun run deps:check` enforces both the `packages/*` workspace graph and\n  `packages/hunk/src/*` tiers; `.dependency-cruiser.cjs` is authoritative and\n  `docs/module-boundaries.md` explains the model. Standalone packages do not import Hunk internals.\n  Bundled providers use only provider-local modules, platform built-ins, `hunkdiff/extension`, and\n  explicit `@hunk/vcs/*` leaves. The known-violations baseline is shrink-only: fix an edge, rerun\n  `bun run deps:baseline`, and never add to it.\n- Keep the app review-first: the main pane is a single top-to-bottom stream of all visible file diffs.\n- The sidebar is for navigation. Selecting a file jumps to that file in the main review stream; it should not collapse the main pane to one file.\n- Keep Pierre as the diff engine and renderer foundation. Do not switch the main renderer back to OpenTUI's built-in `<diff>` widget.\n- Keep split and unified views terminal-native and driven from the same normalized diff model.\n- Preserve mouse + keyboard parity for primary actions.\n- Keep the chrome restrained: top menu bar, minimal borders, no redundant metadata headers.\n\n## component guidance\n\n- `HunkSessionHost` owns history/review routing in one React root. `AppHost` owns reload\n  serialization, extension adoption, and broker/content/React commit ordering. `App` owns review\n  interaction, navigation, layout, theme, filtering, and pane coordination. Pane and diff modules\n  own rendering and geometry.\n- `InteractiveSessionInitialization` in `packages/hunk/src/core/session/initialization.ts` carries\n  finalized launch inputs whose lifetime spans routed surfaces. Add a concern there only when\n  `HunkSessionHost` owns that cross-surface lifetime; keep surface-local bootstrap data out.\n- Confirmation prompts with a small set of choices should reuse `ConfirmDialog` (body rows plus a clickable key-legend action row) instead of composing `ModalFrame` with a hand-rolled footer; keyboard handling for its actions stays in `useAppKeyboardShortcuts`.\n- Extend existing components or add focused components rather than growing `App` into a monolith.\n- Shared formatting, ids, and small derivations belong in helpers, not repeated inline.\n- When refactoring logic that spans helpers and UI components, add tests at the level where the user-visible behavior actually lives, not only at the lowest helper layer.\n\n## theme guidance\n\n- Built-in theme ids and source metadata live in `packages/hunk/src/core/theme/catalog.ts`; `packages/hunk/src/ui/themes.ts`\n  derives Hunk's semantic `AppTheme` values.\n- When adding or renaming a built-in theme, update validation, public exports, docs/examples, the\n  appropriate Changeset, and tests. Keep source palette tokens separate from semantic mappings and\n  cover non-trivial derived colors.\n- `BUNDLED_SHIKI_THEME_DIFF_COLORS` in `packages/hunk/src/core/theme/catalog.ts` is generated. Edit the sourcing policy in `scripts/generate/generate-theme-diff-colors.ts`, then run `bun run generate:theme-colors`.\n\n## testing\n\n- Colocate unit tests with the code they cover (`packages/hunk/src/core/foo.ts` + `packages/hunk/src/core/foo.test.ts`, `packages/hunk/src/ui/AppHost.*.test.tsx`, `packages/hunk/src/ui/lib/*.test.ts`).\n- Put shared unit-test helpers in `test/helpers/`.\n- Name test helpers so they explicitly include `Test` and are clearly test-only (`createTestDiffFile`).\n- Use repo-level `test/` directories by intent:\n  - `test/cli/` for black-box CLI contract coverage.\n  - `test/session/` for daemon/session integration and end-to-end flows.\n  - `test/pty/` for PTY-backed live UI integration tests.\n  - `test/review-conformance/` for the shared review model's golden fixtures and per-consumer conformance suites.\n  - `test/session-broker-node/` for real Node listener/adapter conformance.\n  - `test/session-broker-runtime/` for shared Bun/Node broker fixtures.\n  - `test/smoke/` for opt-in terminal transcript smoke coverage.\n- `bun run test` does not include review conformance, PTY, TTY smoke, or real-Node adapter\n  conformance under `test/session-broker-node/`. Run the dedicated command documented in\n  `test/README.md` when changing those areas.\n\n## source formatting\n\n- Separate functions, class methods, nested helpers, and test declarations with a blank line; keep JSDoc and test-specific comments attached to their declaration.\n- Use blank lines between logical phases inside functions. Keep related statements grouped rather than packing an entire workflow into one dense block.\n\n## code comments\n\n- Add short JSDoc-style comments to functions and helpers.\n- Write header comments in active voice: the first sentence says what the module or function\n  does (\"Applies one action to the review state and returns the next state.\"), followed by its\n  invariants. Avoid passive or self-important framing (\"The one place where…\", \"the single\n  source of truth for…\") — name the behavior, not the architecture's opinion of itself.\n- For orchestration and controller modules, explain the product workflow before the mechanics:\n  name the user-visible triggers that converge there, state what the module owns, identify the\n  neighboring authority it deliberately leaves elsewhere, and call out preservation or\n  non-reloadable invariants. Prefer concrete flows (\"watch changes and manual refresh both rebuild\n  the mounted review\") over abstract labels (\"handles refresh\").\n- Add inline comments for intent, invariants, ownership, or non-obvious tradeoffs. Do not narrate\n  syntax, praise the implementation, address the reader conversationally, or record temporary\n  implementation history that belongs in Git or maintainer docs.\n\n## naming\n\n- Prefer names that match the role the code plays in the product and architecture.\n- Use `layout` for structural placement or arrangement data.\n- Use `geometry` for aggregate spatial data used by rendering, scrolling, or interaction.\n- Use `bounds` for one concrete visible extent within a larger structure.\n\n## review behavior\n\n- Default behavior is a multi-file review stream in sidebar order.\n- Layout modes are `auto`, `split`, and `unified`. `auto` chooses split on wide terminals and unified\n  on narrow ones; explicit modes override it.\n- `[` and `]` navigate hunks across the full review stream. Do not reintroduce `j`/`k` hunk navigation unless the user asks.\n- Agent context belongs beside the code, not hidden in a separate mode or workflow.\n- Agent notes are hunk-specific: show notes for the selected hunk, render them in the diff flow near the annotated row, and keep a clear spatial relationship to the code they explain.\n- Keep note behavior explicit. If the UI intentionally prioritizes one note, one selection, or one active target, encode that as a named policy rather than scattering array-index assumptions through the codebase.\n- STML markup notes (experimental) live in `packages/hunk/src/ui/lib/stml/`. The layout engine is deliberately a deterministic line layout, not OpenTUI flexbox: the row-windowed review stream needs exact note heights before mount, so `(markup, width)` must always produce the same lines. Colors stay symbolic until render time so measurement never needs a theme. Do not \"simplify\" this into flexbox renderables, and keep note-card geometry in `agentNoteGeometry` as the single source for rendering, measurement, and agent-facing width reporting.\n- Keep temporary sidecars concise and review-oriented. Their file order is intentional, while the\n  visible note UI remains hunk-note driven rather than showing generic explainer cards.\n- Agents review via `packages/hunk/skills/hunk-review/SKILL.md` using `hunk session *` commands; do not run interactive TUI commands directly.\n- `packages/hunk/skills/hunk-review/SKILL.md` is generated. Edit `packages/hunk/src/hunk-review/skillDocument.ts`, `packages/hunk/src/session/agent/surface.ts`, or `packages/hunk/src/session/agent/errors.ts`, then run `bun run generate:skill`; never hand-edit the skill file.\n\n## binary notes\n\n- Installed `hunk` is a compiled snapshot, not linked to source.\n- After source changes, rebuild/reinstall with `bun run install:bin`.\n- For rendering verification, prefer a real TTY smoke run over redirected stdout capture.\n\n## verification\n\n- For rendering changes: run `bun run typecheck`, `bun run test`, `bun run test:integration`,\n  `bun run test:tty-smoke`, and do one real TTY smoke run on an actual diff.\n- For interaction, layout, scrolling, navigation, windowing, or other terminal-native behavior: add or update PTY integration coverage in `test/pty/*-integration.test.ts` and run it with `bun run test:integration`.\n- For broker/runtime changes, run `bun run test:session-broker-node` in addition to the relevant\n  Bun tests.\n- For CLI, config, or pager work: make sure the relevant source invocation still works (`diff`, `show`, `patch`, or `pager`).\n- Preserve current interaction model unless the user asks to change it explicitly.\n\n## cross-platform support\n\n- Hunk should work on macOS, Linux, and Windows. Keep tests and CI portable unless a case is explicitly Unix-only (PTY/TTY smoke coverage is Unix-only).\n- In tests, avoid hard-coded POSIX paths, separators, shell syntax, and filenames invalid on Windows; use Node path helpers for real filesystem paths while preserving user-provided/protocol paths when pass-through is intentional.\n- If Windows-only Bun behavior appears around timers, sockets, or line endings, prefer a small compatibility fix or a narrowly scoped skip with a comment over broadening Unix assumptions.\n\n## releases\n\n- User-visible changes require a Changeset; maintenance-only changes require an empty Changeset.\n  Follow `.changeset/README.md` and do not edit `CHANGELOG.md` directly.\n- For release preparation, publishing, backports, and post-release verification, read `skills/hunk-release/SKILL.md`.\n- Never push a release tag or trigger publishing without explicit user confirmation.\n- `hunk.dev/changelog` is generated from `CHANGELOG.md` by `bun run generate:changelog`; hand-author only `website/releases/notes.json`, and never edit its output. `docs/changelog-on-hunk-dev.md` explains how release dates and the pre-tag window work.\n\n## repo notes\n\n- Local review artifacts are ignored on purpose. Leave them alone unless the user explicitly wants them updated, and do not commit them.\n- Before committing or preparing a PR, follow `CONTRIBUTING.md`.\n","CLAUDE.md":"AGENTS.md","examples/extensions/cli-tools/README.md":"# CLI tools extension\n\nDemonstrates a generic top-level command tree. The handler owns every token below `cli-tools`, can stream headless output, receives cooperative cancellation, and can hand terminal ownership to one built-in Hunk command.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx --extension ./examples/extensions/cli-tools cli-tools status\nbun run packages/hunk/src/main.tsx --extension ./examples/extensions/cli-tools cli-tools review\n```\n\n`status` writes to stdout and exits. `review` performs signal-aware asynchronous preprocessing, writes progress to stderr, then delegates to `hunk diff`. A delegating handler must not write stdout or read stdin because the built-in command or TUI takes ownership of both.\n\nThe example parses its raw argument tokens directly. Extensions may use their own parser, make network requests, spawn processes, or access the filesystem under Hunk's ordinary extension trust model.\n","examples/extensions/code-document-file-view/README.md":"# Code-document file view\n\nThis API-v28 example presents one old/new code pair per diff hunk. It declares the complete source documents so Hunk can tokenize with multiline lexical context, while each displayed span references either a complete old-side line or a partial new-side UTF-16 slice. Gutters and the unhighlighted prefix stay ordinary symbolic spans.\n\nHunk owns the language, active syntax theme, workers, caching, terminal safety, and plain fallback. The extension never supplies token colors.\n\nRun it from this checkout against two JavaScript or TypeScript files:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/code-document-file-view \\\n  path/to/before.ts path/to/after.ts\n```\n\nChoose **View → File presentation → Code documents: old / new**. The example intentionally omits `sourceRanges`: syntax references paint code but do not claim note/navigation ownership. A visible note therefore makes Hunk use the raw diff, as required by the all-or-raw note policy.\n","examples/extensions/inline-edit/README.md":"# Inline edit extension\n\nA miniature line editor for the file under review. Press `Ctrl-E`, type into the diff, press `Ctrl-S`, and Hunk writes the file back to your working tree after asking you first.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\nIt exists to demonstrate that Hunk's interactive extension surfaces compose, so it uses them all at once:\n\n| Capability                              | Where this extension uses it                                                                                                                                                                                                                                           |\n| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `ctx.workspace`                         | `canWriteDocument` gates the affordance, `readDocument(\"new\")` fills the buffer, `writeDocument` performs the consented write and reloads the review.                                                                                                                  |\n| `mode` on a registered file view        | `onKey` claims arrows, editable characters, Backspace, Enter, and `Ctrl-S`, while `]`, `?`, and `q` pass through so navigation, help, and quit keep working. `Ctrl-S` is recognized with `matchesKey`, so the bare control byte terminals send for it matches too.     |\n| `fileViews.enterMode(viewId)`           | One call makes the view the file's presentation _and_ gives its mode the keyboard, so `Ctrl-E` opens the editor in a single press.                                                                                                                                     |\n| `fileViews.refresh(viewId, { fileId })` | Every buffer or caret change re-derives the layout. A view's layout is a pure function of `(file, width)`, so this is the only way a stateful presentation redraws — and the buffer belongs to one file, so the refresh is scoped to it and no other file re-lays out. |\n\n## Try it from this checkout\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/inline-edit\n```\n\n## Install it globally\n\nCopy the whole folder — it has no dependencies:\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/inline-edit ~/.config/hunk/extensions/\n```\n\nHunk discovers the folder automatically on later launches. Open **View** and choose **File presentation: Inline edit**, or press `Ctrl-E`. The command is named `inline-edit.edit` for `[keybindings]` customization.\n\n## Keys\n\n| Key                           | Does                                                                          |\n| ----------------------------- | ----------------------------------------------------------------------------- |\n| `Ctrl-E`                      | Starts editing the selected file, showing the view if it was not already.     |\n| `↑` `↓` `←` `→`               | Move the caret. `←`/`→` wrap across line ends.                                |\n| any other printable character | Types at the caret — including characters bound to Hunk commands, like `z`.   |\n| `Backspace`                   | Deletes back one character, joining with the previous line at column 0.       |\n| `Enter`                       | Splits the line at the caret.                                                 |\n| `Ctrl-S`                      | Asks Hunk to write the buffer. Hunk confirms, writes, and reloads the review. |\n| `Esc`                         | Leaves the editor and **discards** everything typed since the last write.     |\n| everything else               | Still Hunk's: `]` moves to the next hunk, `?` opens help, `q` quits.          |\n\nThe header row reads `EDITING — Esc exits · ctrl+s writes`, plus a `MODIFIED` marker whenever the buffer differs from the text on disk.\n\n## How the pieces fit\n\n`Ctrl-E` is one press. The command gates on `canWriteDocument`, starts reading the document, and calls `fileViews.enterMode` before awaiting that read. Entry therefore uses the same selected file the command captured; a selection change cannot attach a late buffer to another file. `enterMode` makes the view that file's presentation and gives its mode the keyboard together, then the completed read builds the buffer and refreshes the view. When entry refuses (no `mode`, a file Hunk keeps on raw diff, a view that does not match), it warns by name and returns `false` without installing a buffer.\n\nThe editor slot is claimed synchronously, before that command's first `await`. Reading the document suspends the handler, so a guard that only checked \"is a session live?\" would let a second `Ctrl-E` through the window in between, and one of the two handlers would then be parked forever on a session nothing could end. The claim is released on every way out — an unwritable review, an unreadable document, a refused `enterMode` — and once a session is live it is the live session that answers the next press.\n\n`onKey` has to answer synchronously — its return value _is_ the routing decision — and the mode context carries only `file` and `fileViews`. So a keystroke can never write anything itself. What `Ctrl-S` does instead is post a request into the edit session, and the command handler that entered the mode is still awaiting that session: `ctx.workspace` is valid for the whole life of a command handler's promise, so the handler is the mode's async runtime.\n\nThat loop ends the same way from every direction, because `onExit` runs on every exit path:\n\n- **Saved.** A successful `writeDocument` reloads the session, the reload exits the mode, `onExit` ends the loop.\n- **Escaped.** Escape is host-owned, so it exits the mode without ever reaching `onKey`, and the loop ends the same way.\n- **Cancelled write.** `{ ok: false, reason: \"cancelled\" }` is the user answering, not a failure. The editor keeps running.\n- **Failed write.** The `detail` sentence is shown as a warning and the editor keeps running, so nothing typed is lost to a full disk.\n\n## Limitations\n\nThis is a demonstration, not an editor:\n\n- **No wrapping.** Lines are truncated at the pane width with `…`. A file-view layout must be deterministic for `(file, width)`, and wrapping would be a second layout problem on top of the one this example is about.\n- **Whole-line truncation.** The caret can move past the visible edge; the row does not scroll horizontally to follow it.\n- **One buffer, no undo.** Escape discards everything since the last write, with no confirmation beyond the notice it leaves behind.\n- **Writes are working-tree only**, which is a property of `ctx.workspace`: `hunk show`, `hunk patch`, a staged diff, and a file-pair diff have no working-tree document to replace, so the command refuses instead of opening an editor that could not save.\n- **Agent notes describe the document as it was loaded.** They stay on the lines they were written about, but nothing re-reads the changeset while you type.\n\n## Source bindings and provenance\n\nRows carry `sourceRanges` so Hunk can place its own inline notes inside this presentation, and a row may only bind a line it honestly still _is_. Row position cannot answer that once you split or join a line, so the edit session keeps **provenance**: for each buffer line, the document line it came from, or nothing at all.\n\n| Edit               | Provenance                                                               |\n| ------------------ | ------------------------------------------------------------------------ |\n| typing in a line   | kept — an edited line is still the line it came from                     |\n| `Enter` (split)    | the first line keeps it; the new tail gets none                          |\n| `Backspace` (join) | the merged line presents both source lines so either note stays attached |\n\nSo a line you inserted binds nothing, the lines below a split keep the numbers they had, and a joined row keeps every source line it now presents. The hunk extents in `hunkRows` are derived from the same provenance, so the hunk highlight follows the rows still holding a hunk's lines instead of drifting down by however many lines you added above them. A join across two distinct hunks is refused because one bound row cannot belong to two hunk extents.\n\nOne thing this shows that is easy to get wrong: Hunk accepts a binding only on a row exactly one `hunkRows` extent owns, and rejects the whole layout otherwise — so a last pass drops the bindings no extent owns, and context lines outside every hunk are presented without one. The [rendered Markdown example](../rendered-markdown/) ends its layout with the same pass, for the same reason.\n\n## Where this is documented\n\n- [`docs/extensions.md` → Interactive file views](../../../docs/extensions.md#interactive-file-views)\n- [`docs/extensions.md` → Reading and writing a reviewed file](../../../docs/extensions.md#reading-and-writing-a-reviewed-file)\n- [`docs/extensions.md` → `hunk.registerFileView(view)`](../../../docs/extensions.md#hunkregisterfileviewview-experimental)\n","examples/extensions/jsx-file-view-gallery/README.md":"# JSX file-view gallery\n\nThree opt-in presentations exercise the constrained React/OpenTUI row contract against checked-in, realistic file pairs. Run one command from the repository root, then press **F8** or choose **Extensions → Toggle JSX demo for current file**. Press F8 again to restore Pierre's raw diff.\n\n## 1. Change atlas\n\nNested boxes, responsive meters, semantic color, and selected-hunk styling summarize a multi-hunk TypeScript refactor. It uses no source parser and works from public hunk/change metadata alone.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/before.ts \\\n  examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/after.ts\n```\n\n## 2. CSS palette delta\n\nThe extension lazily reads both exact documents, associates changed opaque three- or six-digit hexadecimal custom properties with each real diff hunk, and paints old/new terminal color swatches inside deterministic two-row rectangles.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/css-palette/before.css \\\n  examples/extensions/jsx-file-view-gallery/fixtures/css-palette/after.css\n```\n\n## 3. Dependency delta\n\nA conservative package-file parser highlights only the changed semantic-version segment: patch-only changes emphasize the patch number, minor upgrades emphasize the minor number, and major upgrades emphasize the full old/new strings. It retains positional bounds for every parsed hunk; invalid JSON or unavailable source falls back to raw diff.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/before/package.json \\\n  examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/after/package.json\n```\n\n## Mixed five-file review\n\nTo see all three preview types retained together between ordinary raw diffs—and enough content to exercise stream scrolling—run:\n\n```bash\nbun run ./examples/extensions/jsx-file-view-gallery/mixed-review/run.ts\n```\n\nFollow the short activation sequence in [`mixed-review/README.md`](./mixed-review/README.md).\n\n## Contract illustrated\n\n- Every painter stays inside a declared fixed-height row; geometry, scrolling, windowing, and hunk navigation remain host-owned.\n- Every row keeps useful symbolic spans for row-local error fallback, clipped to the same fixed rectangle.\n- Semantic data is captured in closures during `layout`; painters receive only bounded paint props and use Hunk's live paint-only semantic theme palette.\n- The demos intentionally have no pointer handlers. Registered commands are the supported interaction path.\n- Layouts return `null` when exact source is unavailable or no supported semantic row can be attributed. Mixed diffs may retain neutral summary rows for non-semantic hunks so navigation stays positional.\n- Rows bind conservatively attributed exact-source ranges. Hunk renders a note inside the alternate view only when its preferred-side anchor resolves uniquely; otherwise the complete file falls back to raw diff.\n\nThis gallery is experimental and is not loaded unless you explicitly pass or install its folder. See [`docs/file-view-jsx-poc.md`](../../../docs/file-view-jsx-poc.md) for the full contract.\n","examples/extensions/jsx-file-view-gallery/mixed-review/README.md":"# Mixed preview review\n\nThis launches one real five-file Git working-tree review that is intentionally taller than a terminal viewport:\n\n- `README.md` — raw Markdown diff;\n- `package.json` — dependency version-segment highlights;\n- `scripts/deploy.py` — raw Python diff;\n- `src/invoice.ts` — responsive change-atlas cards;\n- `styles/theme.css` — exact-source color swatches.\n\nThe launcher creates a temporary repository, commits the checked-in `before` fixtures, copies the `after` fixtures into its working tree, and starts Hunk from this checkout. The repository is removed when Hunk exits.\n\nFrom the Hunk repository root:\n\n```bash\nbun run ./examples/extensions/jsx-file-view-gallery/mixed-review/run.ts\n```\n\nRaw diff is deliberately the default. To build the mixed stream:\n\n1. Click `package.json` in the sidebar and press **F8**.\n2. Click `src/invoice.ts` and press **F8**.\n3. Click `styles/theme.css` and press **F8**.\n4. Click `README.md` to return to the top, then scroll through the main pane.\n\nSelecting files only jumps the main review stream; it does not collapse other files. The three preview selections therefore remain active together, interleaved with the two raw Pierre diffs. Use `[` and `]` while scrolling to verify that hunk navigation crosses raw and custom sections using the same host-owned geometry.\n","examples/extensions/jsx-file-view/README.md":"# JSX file-view POC extension\n\nAn opt-in proof of concept for fixed-height React/OpenTUI rows in alternate file presentations. It appears only for files with at least two parsed hunks and creates two custom rows per hunk, with stable row IDs and explicit hunk bounds.\n\nRun it from this checkout against a multi-hunk working-tree change:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/jsx-file-view\n```\n\nChoose **Extensions → Toggle JSX hunk cards (POC)**. The row component uses a React state hook and OpenTUI `box`/`text` elements. Its registered F8 command/menu item is the supported keyboard path. A cooperatively delivered, un-dragged left-button mouse-up toggles local detail and stops propagation; wheel, drag, and unhandled input remain host-owned. The row is a non-focusable paint surface, with no portal, renderer, focus, or input-delivery guarantee. Each component is a closure over the hunk summary; Hunk passes it only bounded paint props, including a live semantic theme palette that does not participate in layout. The `spans` on every row are the host-rendered fallback, clipped to the same declared fixed height if the component fails. Hook state survives selected-hunk updates while mounted, but is intentionally lost when windowing unmounts the row or a new layout generation replaces it.\n\nThis example is deliberately opt-in and experimental. See [`docs/file-view-jsx-poc.md`](../../../docs/file-view-jsx-poc.md) for constraints.\n","examples/extensions/pane-layout/README.md":"# Pane layout extension\n\nRegisters a resizable right pane and fixed two-row top and bottom panes.\n\nRun it from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/pane-layout\n```\n\nPress `ctrl+p` or use the **Extensions** menu. Drag the right divider to resize.\n\nCopy this directory to your Hunk extensions directory to install it.\n","examples/extensions/rendered-markdown/README.md":"# Rendered Markdown extension\n\nAn optional Markdown preview for Hunk's experimental file-view API. It parses Markdown with [Marked](https://marked.js.org/) and presents headings, inline formatting, links, lists, quotes, tables, and fenced code as host-owned symbolic rows.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\n## Try it from this checkout\n\nThe repository's root install supplies the example's development dependency:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/rendered-markdown \\\n  before.md after.md\n```\n\n## Install it globally\n\nCopy the whole folder, then install its local dependency:\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/rendered-markdown ~/.config/hunk/extensions/\ncd ~/.config/hunk/extensions/rendered-markdown\nbun install\n```\n\nHunk discovers the folder automatically on later launches. Open **View** and choose **File presentation: Rendered Markdown**, or press `F8`. The command is named `rendered-markdown.toggle-rendered-markdown` for `[keybindings]` customization.\n\nRaw Pierre diff remains the default and fallback. The preview reads exact text through `input.readDocument`, returns hunk row bounds in source-hunk order, binds rendered blocks to exact new-side ranges, and retains hunk navigation and selection highlighting. Hunk inserts inline note cards at uniquely bound rows; if any visible note cannot be resolved, the complete file temporarily returns to raw rendering so review data is never hidden.\n\n## Why it returns rows instead of an OpenTUI Markdown component\n\nOpenTUI includes a capable `MarkdownRenderable`, but mounting it would make the extension own opaque layout geometry. Hunk needs exact rows before mount for review-stream measurement, windowing, scrolling, hunk navigation, and fallback. This example therefore uses the same Marked parser OpenTUI uses, then translates its tokens into the file-view contract's generic tones and text attributes.\n","examples/extensions/review-note-navigator/README.md":"# Review note navigator extension\n\nLists every note currently saved in Hunk's shared ReviewStore, then navigates to a selected visible note's authoritative source anchor. The example shows how `ctx.review.snapshot()` complements dialogs and live navigation: selection and note events alone cannot recover notes that already existed before the extension began listening.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-note-navigator\n```\n\nSave one or more review notes, then run **Extensions → Navigate saved review note…** (`F8`). Each choice includes its reconciliation status, file, preferred line, side, and summary.\n\n- Active notes reveal their current source line.\n- Stale notes reveal the last authoritative anchor Hunk retained.\n- Orphaned notes remain visible in the inventory but produce a warning because they have no current review location.\n- A note whose file is hidden by Hunk's current file filter remains in the inventory, but guarded navigation refuses the hidden target with a warning; clear the filter and retry.\n- Drafts and static sidecar annotations that never entered ReviewStore are intentionally absent.\n\nThe command captures the complete note inventory before opening its selector. After the user chooses, it reads the authoritative snapshot again and resolves the selected note by stable note id, so an edit made by another review surface cannot make it navigate using an obsolete anchor. A reload cancels the dialog and retires the command's review controls.\n","examples/extensions/review-snapshot-export/README.md":"# Review snapshot export extension\n\nExports Hunk's authoritative saved review state as JSON. The example shows why `ctx.review.snapshot()` is more useful than accumulating `note_created` events: one command receives every note currently retained by the shared ReviewStore, including stale and orphaned notes an exporter must handle explicitly.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-snapshot-export\n```\n\nAdd one or more review notes, then run **Extensions → Export review snapshot…** (`F9`) and choose a new output path. Relative paths resolve from the review's working directory; the example refuses to overwrite an existing file.\n\nThe JSON includes:\n\n- the opaque producer generation and ReviewStore revision\n- every reviewed file's stable `fileKey`, runtime navigation id, content identity, status, and path\n- all saved live and reviewer notes with their resolved old/new anchors and reconciliation status\n\nDraft notes and static sidecar annotations that never entered ReviewStore are intentionally absent. Saved notes appear in live-arrival order followed by reviewer-creation order.\n\nThe command captures a snapshot before opening its path dialog, then reads the controls again before writing. If either the generation or revision changed, it refuses the stale export and asks the user to rerun the command. Publishers can use the same check before an irreversible network request.\n\n## Trust\n\nThis example uses `node:fs` directly to write the user-selected export path. Hunk extensions run with the user's full permissions; this is distinct from `ctx.workspace`, which mediates writes to reviewed files and asks for consent. Install and run only extensions you trust.\n","examples/extensions/review-triage/README.md":"# Review triage extension\n\nA session-local hunk review board for Hunk. It records which hunks you have visited and lets you mark the selected hunk **approved**, **investigate**, or **blocked** with an optional rationale.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-triage\n```\n\nOr copy the directory to your Hunk extensions directory and keep its `package.json`; its manifest makes the folder a single `review-triage` extension.\n\n## Use\n\nOpen **Extensions → Toggle review triage** (`Y`). The right pane lists each visible file's hunks; click a hunk to navigate the review stream. Use **Extensions → Mark selected hunk…** (`x`) to choose a status and enter an optional rationale. **Center current review line**, **Set review focus…**, and **Clear triage decisions** are menu-only commands.\n\nThe board intentionally keeps state only for the running Hunk session. Reloading reconciles decisions against the newly parsed hunks and drops entries that no longer match, rather than silently transferring a decision to changed code.\n\n## API surface exercised\n\n- `registerPane` renders public file/hunk summaries and navigates with pane actions.\n- `registerCommand` supplies the Extensions-menu items and user-remappable defaults; `ctx.commands.execute` delegates the dedicated centering action to Hunk's public semantic command.\n- `dialogs.select`, `dialogs.input`, and `dialogs.confirm` implement the review decision and clear flows.\n- Lifecycle handlers track changeset loads, reloads, selection, viewed hunks, Hunk notes, filters, and pending watch reloads through a `useSyncExternalStore` bridge.\n- `hunk.events` publishes decisions and listens for `review-triage:open`, so another extension can reveal the board without importing its state.\n","examples/extensions/vim-navigation/README.md":"# Vim navigation extension\n\nA small Vim-style normal mode for Hunk's whole review stream. It demonstrates session keyboard modes and public semantic command execution without accessing scroll boxes, renderer objects, or viewport coordinates.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\n## Try it from this checkout\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/vim-navigation\n```\n\nPress `F6` or choose **Extensions → Toggle Vim navigation**. The persistent status badge shows when the mode owns review-level keys; click the badge, choose the host-owned exit menu item, or press `Esc` to leave.\n\n## Install it globally\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/vim-navigation ~/.config/hunk/extensions/\n```\n\n## Keys\n\n| Key                 | Action                                                      |\n| ------------------- | ----------------------------------------------------------- |\n| `j` / `k`           | Move the current review line down/up                        |\n| `[` / `]`           | Move to the previous/next hunk                              |\n| `gg` / `G`          | Jump to the start/end of the review                         |\n| `zt` / `zz` / `zb`  | Align the current line at the top/center/bottom             |\n| `Ctrl-D` / `Ctrl-U` | Move down/up by half pages                                  |\n| positive digits     | Prefix the next relative motion, for example `5j` or `3]`   |\n| `:`                 | Open the host-rendered Vim command line                     |\n| `Esc`               | Exit the mode (host-owned; the extension never receives it) |\n| everything else     | Pass through to normal Hunk routing                         |\n\nCounts are parsed by the extension and capped at 10,000. Once a normal-mode sequence resolves, the extension calls `ctx.commands.execute(id, { count })` exactly once, so Hunk applies movement atomically. A bare `0` passes to Hunk's normal layout shortcut; `0` can extend a count that already began with `1`–`9`.\n\nPressing `:` passes the key to the example's registered command, which opens `ctx.prompts.line()` on the status row with a `:` prefix. That focused host input captures typed keys ahead of the still-active session mode until Enter submits, or Escape clears the buffer and a second Escape cancels. The deliberately small Ex-style command set is:\n\n| Command   | Action                          |\n| --------- | ------------------------------- |\n| `:top`    | Jump to the start of the review |\n| `:bottom` | Jump to the end of the review   |\n\nUnsupported commands produce an attributed warning. Absolute source-line commands such as Vim's `:100` are intentionally absent because Hunk does not expose source-line targeting as a public semantic command; relative counted movement such as `100j` remains available in normal mode.\n\nThe example enables Hunk's host-owned current-line marker on entry so the `z*` alignment commands have a target. It resets all pending prefix/count state on entry and exit. Invalid continuations clear pending state and pass the current key back to Hunk.\n","packages/hunk-git/README.md":"# @hunk/git\n\nPrivate, statically bundled Git provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-tree, revision, and stash\nreviews; history; exact old/new source reads; staged changes; untracked and large-file handling;\nand filesystem-assisted watch plans.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-git` and `bun run deps:check` after changes.\n","packages/hunk-jj/README.md":"# @hunk/jj\n\nPrivate, statically bundled Jujutsu provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-copy and revision reviews,\nhistory, exact source reads where the revision shape permits them, and signature-based refresh.\nStaged and stash reviews are not supported.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-jj` and `bun run deps:check` after changes.\n","packages/hunk-sapling/README.md":"# @hunk/sapling\n\nPrivate, statically bundled Sapling provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-copy and revision reviews,\nuntracked-file discovery, and signature-based refresh. History, staged and stash reviews, exact\nsource reads, and filesystem watch plans are not supported.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-sapling` and `bun run deps:check` after changes.\n","packages/hunk-vcs/README.md":"# @hunk/vcs\n\nPrivate implementation helpers shared by Hunk's bundled VCS providers. This `0.0.0` workspace is\nnot a public or versioned SDK.\n\nThe package has no root export. Providers import only the leaf they need:\n\n- `@hunk/vcs/async-process`\n- `@hunk/vcs/diff-target`\n- `@hunk/vcs/large-file`\n- `@hunk/vcs/path`\n- `@hunk/vcs/source`\n\n`@hunk/vcs` sits at the bottom of the workspace graph and does not import other workspace packages.\nProvider implementations combine these helpers with the public `hunkdiff/extension` contract; Hunk\nregisters them in `packages/hunk/src/extensions/default/vcs/index.ts` and composes the catalog in\n`packages/hunk/src/app/vcsCatalog.ts`.\n\nKeep unit tests beside the source. Run `bun test packages/hunk-vcs` and `bun run deps:check` after\nchanging this package.\n","packages/hunk/README.md":"# hunk\n\nHunk is an interactive terminal diff viewer for reviewing code changes, built on [OpenTUI](https://github.com/anomalyco/opentui) and [Pierre diffs](https://www.npmjs.com/package/@pierre/diffs).\n\n**[hunk.dev](https://hunk.dev)** · [Documentation](https://hunk.dev/docs/)\n\n[![CI status](https://img.shields.io/github/actions/workflow/status/modem-dev/hunk/ci.yml?branch=main&style=for-the-badge&label=CI)](https://github.com/modem-dev/hunk/actions/workflows/ci.yml?branch=main)\n[![Latest release](https://img.shields.io/github/v/release/modem-dev/hunk?style=for-the-badge)](https://github.com/modem-dev/hunk/releases)\n[![MIT License](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE)\n[![Join the Discord community](https://img.shields.io/badge/Discord-Join%20community-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/WZFjaP6Gt8)\n\n- multi-file review stream with sidebar navigation\n- inline AI and agent annotations beside the code\n- split, unified, and responsive auto layouts\n- watch mode for auto-reloading file and Git-backed reviews\n- keyboard, mouse, pager, and Git difftool support\n\n<table>\n <tr>\n   <td width=\"60%\" align=\"center\">\n     <img width=\"845\" alt=\"image\" src=\"https://raw.githubusercontent.com/modem-dev/hunk/refs/heads/main/website/public/docs/images/review-stream.webp\" />\n     <br />\n     <sub>Split view with sidebar and inline AI notes</sub>\n   </td>\n   <td width=\"40%\" align=\"center\">\n     <img width=\"507\"alt=\"image\" src=\"https://raw.githubusercontent.com/modem-dev/hunk/refs/heads/main/website/public/docs/images/agent-comments.webp\" />\n     <br />\n     <sub>Unified view and mouse-selectable menus</sub>\n   </td>\n </tr>\n</table>\n\n## Install\n\nOn macOS and Linux, install the standalone binary:\n\n```bash\ncurl -fsSL https://hunk.dev/install.sh | sh\n```\n\nWindows users can install with npm or mise. Other installation methods are also available:\n\n```bash\nnpm i -g hunkdiff                    # macOS, Linux, or Windows; requires Node.js 22+\nbrew install hunk                    # macOS or Linux\nmise use -g hunk                     # macOS, Linux, or Windows\n```\n\n> [!NOTE]\n> If you previously installed hunk via `modem-dev/tap`, be sure to uninstall it first with `brew uninstall modem-dev/tap/hunk`.\n\nWindows requires mise 2026.8.6 or newer. Nix users can use the `default` package exported in `flake.nix`; see [the Nix guide](https://github.com/modem-dev/hunk/blob/main/nix/README.md) for details. Hunk also ships as a default tool in [Omarchy](https://omarchy.org), installed through mise.\n\nRequirements:\n\n- macOS (arm64, x64), Linux (arm64, x64; glibc), or Windows (x64); prebuilt binaries are not published for Windows on arm64 or musl-based Linux such as Alpine\n- On x86-64, a CPU with SSE4.2 (Intel Nehalem 2008+, AMD Bulldozer 2011+); arm64 has no CPU feature floor\n- Node.js 22+ for the npm install; the install script, Homebrew, mise, and Nix ship a standalone binary that does not require Node.js\n- Git recommended for most workflows\n\n### Update Hunk\n\nFor npm, Homebrew, and default install-script installs (Hunk 0.20+):\n\n```bash\nhunk update          # install the newest release\nhunk update --check  # check without installing\nhunk update 0.20.0   # select an exact npm or default install-script release\n```\n\nOn an older release, update once with the installer or package manager that installed Hunk, then use `hunk update` going forward. Custom `HUNK_INSTALL_DIR` installs must re-run the installer with the same directory; mise, Nix, and source installs use their owning tools instead.\n\n## Quick start\n\n```bash\nhunk           # show help\nhunk --version # print the installed version\n```\n\n### Working with Git\n\nUse familiar Git commands to open changes in Hunk:\n\n```bash\nhunk diff                      # review current repo changes, including untracked files\nhunk diff --watch              # auto-reload as the working tree changes\nhunk show                      # review the latest commit\nhunk show HEAD~1               # review an earlier commit\nhunk log                       # browse history on a terminal; print when redirected\nhunk log --static              # force static output, paging when needed\nhunk status                    # view workspace status and sibling worktrees (Git)\nhunk status --static           # print one read-only workspace snapshot\nhunk status --json             # emit one versioned workspace snapshot\n```\n\n`hunk status` is read-only: it does not fetch or change the repository. See [workspace status](docs/status.md) for details.\n\n`hunk log` opens an interactive history browser in a terminal and prints text when piped or redirected. Use `--oneline` for compact output. In the browser, enable **Graph view** from the View menu to see commit branches, or press `v` to select a commit range. Quit a commit review to return to history.\n\n### Working with GitHub\n\nHunk bundles `hunk gh`, so pull requests, commits, and comparisons open directly without the\nGitHub CLI or a separate extension:\n\n```bash\nhunk gh pr                         # infer the open PR for the current branch\nhunk gh pr 123                     # infer the repository from origin\nhunk gh pr 'modem-dev/hunk#123'    # quote this form for shells that treat # as a comment\nhunk gh commit a1b2c3d --repo modem-dev/hunk\nhunk gh compare main...feature --repo modem-dev/hunk\n```\n\nPublic repositories work anonymously. Set `GH_TOKEN`, or `GITHUB_TOKEN` when it is absent, for\nprivate repositories and higher API limits. GitHub Enterprise is not currently supported.\n\n### Working with Jujutsu and Sapling\n\nHunk auto-detects Jujutsu and Sapling checkouts, so `hunk diff [revset]` and `hunk show [revset]` use native revsets inside jj or Sapling workspaces. `hunk log --vcs jj` also reads JJ history directly, including in a non-colocated workspace. To override VCS detection, set `vcs = \"git\"` or `vcs = \"jj\"` or `vcs = \"sl\"` in [config](#config).\n\n### Viewing complete files and browsing directories\n\n```bash\nhunk open                 # browse the current directory\nhunk open README.md       # view a complete, syntax-highlighted document\nhunk open path/to/project # browse another directory without requiring a repository\n```\n\nUse arrows and Enter to navigate, Tab to switch focus, `i` to show hidden/Git-ignored entries, and `e` to edit the displayed file in `$EDITOR`. See [the open workflow](docs/open.md) for details and file limits.\n\n### Working with raw files and patches\n\n```bash\nhunk diff --files before.ts after.ts        # compare two files directly\nhunk diff --files before.ts after.ts --watch # auto-reload when either file changes\ngit diff --no-color | hunk patch -          # review a patch from stdin\n```\n\n### Working with agents\n\n1. Install the review skill once: `npx skills add modem-dev/hunk -g --skill hunk-review`.\n2. Open Hunk in another terminal with `hunk diff` or `hunk show`.\n3. Ask your agent to use the hunk-review skill against the live Hunk session.\n4. Press `a` in Hunk to show agent notes; they start hidden.\n\nIf your agent has no skill system, ask it to run `hunk skill show` and follow the output.\n\nSee [Review with an agent](https://hunk.dev/docs/agents/review-with-an-agent/) for live sessions, `--agent-context`, and experimental rich notes.\n\n## Feature comparison\n\n| Capability                           | [hunk](https://github.com/modem-dev/hunk) | [lumen](https://github.com/jnsahaj/lumen) | [difftastic](https://github.com/Wilfred/difftastic) | [delta](https://github.com/dandavison/delta) | [diff-so-fancy](https://github.com/so-fancy/diff-so-fancy) | [diff](https://www.gnu.org/software/diffutils/) |\n| ------------------------------------ | ----------------------------------------- | ----------------------------------------- | --------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |\n| Review-first interactive UI          | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Multi-file review stream + sidebar   | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Inline agent / AI annotations        | ✅                                        | ❌                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Responsive auto split/unified layout | ✅                                        | ❌                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Mouse support inside the viewer      | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Runtime view toggles                 | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Syntax highlighting                  | ✅                                        | ✅                                        | ✅                                                  | ✅                                           | ❌                                                         | ❌                                              |\n| Structural diffing                   | ❌                                        | ❌                                        | ✅                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Pager-compatible mode                | ✅                                        | ❌                                        | ✅                                                  | ✅                                           | ✅                                                         | ✅                                              |\n\n## Advanced\n\n### Config\n\nYou can persist preferences to a config file:\n\n- `$XDG_CONFIG_HOME/hunk/config.toml` (default `~/.config/hunk/config.toml`)\n- `.hunk/config.toml` in a repository\n\nPersistent state uses `$XDG_STATE_HOME/hunk/state.json` when the variable is\nabsolute, or `~/.local/state/hunk/state.json` otherwise. Hunk preserves existing\nstate by copying the old config-directory file on first use, keeping the original\nand falling back to it if migration fails. Global extension paths are unchanged.\n\nFor example:\n\n```toml\ntheme = \"github-dark-default\"\nmode = \"auto\"               # auto, split, unified\nauto_split_min_width = 160  # minimum available diff columns for split in auto mode\nline_numbers = true\nwrap_lines = false\n```\n\nSee the [configuration guide](https://hunk.dev/docs/configure/configuration/) for command overrides and examples, the [full reference](https://hunk.dev/docs/reference/config/) for every option, or the [theme guide](https://hunk.dev/docs/configure/themes/) to customize colors.\n\n### Keybindings\n\nRemap shortcuts with a `[keybindings]` table in your user config. See [the keybinding guide](https://hunk.dev/docs/configure/keybindings/) for available commands, default keys, and examples.\n\n### Git integration\n\nSet Hunk as your Git pager so `git diff` and `git show` open in Hunk automatically:\n\n> [!NOTE]\n> Untracked files are auto-included only for Hunk's own `hunk diff` working-tree loader. If you open `git diff` through `hunk pager`, Git still decides the patch contents, so untracked files will not appear there.\n\n```bash\ngit config --global core.pager \"hunk pager\"\n```\n\nOr in your Git config:\n\n```ini\n[core]\n    pager = hunk pager\n```\n\nIf you want to keep Git's default pager and add opt-in aliases instead:\n\n```bash\ngit config --global alias.hdiff \"-c core.pager=\\\"hunk pager\\\" diff\"\ngit config --global alias.hshow \"-c core.pager=\\\"hunk pager\\\" show\"\n```\n\n### Jujutsu pager integration\n\nTo use Hunk as jj's pager, run `jj config edit --user` and update:\n\n```toml\n[ui]\npager = [\"hunk\", \"pager\"]\ndiff-formatter = \":git\"\n```\n\n### Sapling pager integration\n\nTo use Hunk as Sapling's pager, run `sl config -u` and update:\n\n```ini\n[pager]\npager = hunk pager\n```\n\n### Extensions (experimental)\n\nThe extension API is experimental and may change in breaking ways between\nminor releases while it stabilizes; breaking changes are called out in\nrelease notes.\n\nHunk loads plain TypeScript extensions from `~/.config/hunk/extensions/`, from a\nrepository's `.hunk/extensions/` (after you explicitly trust that repository),\nand from `--extension <path>` for development. `--no-extensions` turns those off\nfor one run; Hunk's own bundled backends (Git, Jujutsu, and Sapling) stay loaded.\n\nExtensions can add commands, themes, VCS backends, custom sidebars, and more. For example, filter lockfiles out of a review:\n\n```ts\n// ~/.config/hunk/extensions/collapse-lockfiles.ts\nimport type { HunkExtensionAPI } from \"hunkdiff/extension\";\n\nexport default function (hunk: HunkExtensionAPI) {\n  hunk.transformChangeset((changeset, ctx) => {\n    const files = changeset.files.filter((file) => !file.path.endsWith(\".lock\"));\n    ctx.notify(`Collapsed ${changeset.files.length - files.length} lockfiles`);\n    return { ...changeset, files };\n  });\n}\n```\n\nExtensions shared as git repositories install straight from their host, and a\n`hunk-extension` GitHub topic marks community ones:\n\n```bash\nhunk extension install acme/hunk-word-diff@v1.2.0   # or git:host/path, a URL, a local path\nhunk extension list                                 # then update [name] / remove <name>\n```\n\nBrowse community extensions at\n[github.com/topics/hunk-extension](https://github.com/topics/hunk-extension);\npublish yours by pushing the extension to a repository root and adding that\ntopic.\n\nSee [the extension guide](https://hunk.dev/docs/extend/extensions/) for the API, configuration, and publishing instructions.\nInstallable examples include\n[review triage](https://github.com/modem-dev/hunk/tree/main/examples/extensions/review-triage),\n[review snapshot export](https://github.com/modem-dev/hunk/tree/main/examples/extensions/review-snapshot-export), an optional\n[rendered Markdown file view](https://github.com/modem-dev/hunk/tree/main/examples/extensions/rendered-markdown), and a\n[Vim navigation mode](https://github.com/modem-dev/hunk/tree/main/examples/extensions/vim-navigation) built from public semantic commands.\n\n### OpenTUI component\n\nHunk also publishes `HunkDiffView` and lower-level primitives from `hunkdiff/opentui` for embedding the same diff renderer in your own OpenTUI app.\n\nSee [the OpenTUI component guide](https://hunk.dev/docs/reference/opentui-components/) for install, API, and runnable examples.\n\n## Examples\n\nReady-to-run demo diffs live in [the examples directory](https://github.com/modem-dev/hunk/tree/main/examples).\n\nEach example includes the exact command to run from the repository root.\n\n## Contributing\n\n💬 _Chat with users/contributors on the [Modem Discord server](https://discord.gg/WZFjaP6Gt8)_\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution guidelines.\n\n## Sponsor\n\nSponsored by [Modem](https://modem.dev?utm_source=github&utm_medium=oss&utm_campaign=oss_hunk&utm_content=readme_footer).\n\n<a href=\"https://modem.dev?utm_source=github&utm_medium=oss&utm_campaign=oss_hunk&utm_content=readme_footer\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://modem.dev/images/logo/svg/modem-combined-white.svg\">\n    <source media=\"(prefers-color-scheme: light)\" srcset=\"https://modem.dev/images/logo/svg/modem-combined-black.svg\">\n    <img src=\"https://modem.dev/images/logo/svg/modem-combined-black.svg\" alt=\"Modem\" width=\"220\">\n  </picture>\n</a>\n\n## License\n\n[MIT](https://github.com/modem-dev/hunk/blob/main/LICENSE)\n","packages/hunk/skills/hunk-extensions/SKILL.md":"---\nname: hunk-extensions\ndescription: Maps the `hunkdiff/extension` authoring surface for Hunk, the terminal diff viewer — hiding or reordering reviewed files, docked panes, alternate file views, commands and key bindings, dialogs, workspace writes, themes, syntax languages, VCS backends, lifecycle events. Use when writing, debugging, or installing a Hunk extension, or when a request asks Hunk itself to behave differently. Not for reviewing a diff in a live session — that is hunk-review.\n---\n\n# Building Hunk extensions\n\nA Hunk extension is **one TypeScript (or JSX/JS) file that default-exports a\nfactory**. Hunk imports it at startup and hands it an API object. No build step,\nno manifest required.\n\n```ts\n// ~/.config/hunk/extensions/hello.ts\nimport type { HunkExtensionAPI } from \"hunkdiff/extension\";\n\nexport default function (hunk: HunkExtensionAPI) {\n  hunk.on(\"startup\", (_event, ctx) => ctx.notify(\"Hello\"));\n}\n```\n\nThis skill is a map of the touchpoints, not a recipe. Decide what to build from\nthe user's request; use the table below to find the call, then read the linked\nmaterial before writing code.\n\n## Sources of truth — read before writing\n\n| Source                                     | What it answers                                              |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `docs/extensions.md`                       | The authoring guide. Every call, every rule. Start here.     |\n| `packages/hunk/src/extension-api/types.ts` | The contract — exact field names, optionality, doc comments. |\n| `examples/extensions/*`                    | Working extensions. Copy these patterns rather than invent.  |\n| `docs/extension-architecture.md`           | Hunk's internals. Needed only when changing the host.        |\n| `docs/keybindings.md`, `docs/themes.md`    | Chord grammar and theme token rules that extensions inherit. |\n\nOutside a Hunk checkout the guide is split across\n<https://hunk.dev/docs/extend/extensions/> (discovery, trust, config) and its\ncompanion pages — extension-api, file-previews, vcs-adapters, custom-panes —\nand the contract ships as `node_modules/hunkdiff/dist/npm/extension/index.d.ts`.\n\nThe examples, by what they demonstrate:\n\n- `review-triage/` — pane + commands + all three dialog shapes + lifecycle\n  events + the extension event bus + a `useSyncExternalStore` bridge.\n- `inline-edit/` — an interactive file-view `mode` driving `ctx.workspace` writes;\n  its README explains the async lifetime rules better than anything else in tree.\n- `rendered-markdown/` — a file view producing host-rendered rows from parsed\n  Markdown, and a folder extension with an npm dependency.\n- `code-document-file-view/` — API-v28 host-owned syntax paint over complete old/new\n  code documents, including semantic gutters and partial UTF-16 ranges.\n- `jsx-file-view/`, `jsx-file-view-gallery/` — the experimental fixed-height JSX\n  row component contract.\n\n## Where extensions live\n\n| Source                                     | Trust            |\n| ------------------------------------------ | ---------------- |\n| `--extension <path>` (repeatable)          | runs immediately |\n| `[extensions] paths` in user config        | runs immediately |\n| `~/.config/hunk/extensions/` (XDG-aware)   | runs immediately |\n| `.hunk/extensions/` or repo-config `paths` | **trust prompt** |\n\nOnly the repo-local group is gated. Everything else — including `--extension`,\neven when its path points inside the repository under review — is read as\nexplicit user intent and executes with full user permissions, no prompt. Never\npass or suggest a path you have not read, including one copied from a\nrepository's own README.\n\nA directory matches `*.ts`/`*.tsx`/`*.js`/`*.jsx`/`*.mjs` at its top level, plus\none level of folder extensions. A folder is an extension if it has a\n`package.json` with `{\"hunk\": {\"extensions\": [\"./index.ts\"]}}`, or an\n`index.{ts,tsx,js,jsx,mjs}`. Reach for a folder only when you need npm\ndependencies, helper modules, or a README; a single file keeps the install to one\n`cp`. A `.hunk/extensions/` folder extension's `node_modules` has to exist on\nevery machine that loads it — keep a repo-shared extension dependency-free.\n\nShared extensions install from git with `hunk extension install <source>`\n(`owner/repo[@ref]`, `git:host/path[@ref]`, a git URL, or a local path) into\n`~/.config/hunk/extensions/installed/<repo-name>/`, where they load with global\norigin; `list`, `update`, and `remove` manage them. Declared `dependencies` are\n`bun install`ed at install time. The manifest may state\n`{\"hunk\": {\"apiVersion\": N}}` — the minimum extension API version — and an older\nHunk refuses the extension with a startup notice instead of failing mid-factory.\nTo publish, push the folder-extension layout to a git repository's root with\nreal `name`/`version`/`description`, tag releases for `@ref` pins, and add the\n`hunk-extension` GitHub topic so it appears at\n<https://github.com/topics/hunk-extension>.\n\nThe **id** is the file stem, or the folder name for a folder extension — unless\nits manifest declares several entries, in which case each entry is its own\nextension named by its own stem (numeric suffix on collision). The id is the\nnamespace it owns: commands are `<id>.<commandId>`, panes and keyboard modes\nare `<id>:<localId>`, config `[extension.<id>]`. Ids match\n`/^[A-Za-z0-9][A-Za-z0-9_-]*$/`; `hunk`, `git`, `jj`, and `sl` are reserved. A\nbad or duplicate id is skipped with a startup notice. `[extensions] disabled = [\"<id>\"]`\nfilters a user extension before import or trust prompting. `--disable-extension <id>` and\n`--enable-extension <id>` are ordered one-run overrides; `--no-extensions` remains the hard switch\nfor all user extensions. Bundled capabilities use separate host-owned selection ids such as\n`hunk.gh`, even when their public registrations remain under the `hunk` namespace.\n\n## Pick the touchpoint\n\n| To do this                                               | Call                                                          |\n| -------------------------------------------------------- | ------------------------------------------------------------- |\n| Keep demo/training view settings temporary               | `hunk.configureSession(options)`                              |\n| Add a selectable color theme                             | `hunk.registerTheme(theme)`                                   |\n| Highlight an extension, exact filename, or filename glob | `hunk.registerFileLanguage(matcher, lang)`                    |\n| Support another VCS (`git`/`jj`/`sl` are reserved)       | `hunk.registerVcsAdapter(adapter)`                            |\n| Report read-only workspace and sibling status            | Optional `registerVcsAdapter({ status })` capability (API 31) |\n| Add a navigation/list/status pane beside the review      | `hunk.registerPane(pane)`                                     |\n| Present a file as something other than a raw diff        | `hunk.registerFileView(view)` (experimental)                  |\n| Mark character ranges inside diff lines                  | `hunk.registerLineHighlighter(highlighter)`                   |\n| Interpret review keys as a temporary global mode         | `hunk.registerKeyboardMode(mode)`                             |\n| Add a generic top-level CLI command tree                 | `hunk.registerCliCommand(command, handler)`                   |\n| Bind a key / add an Extensions-menu entry                | `hunk.registerCommand(command, handler)`                      |\n| Show persistent text on the bottom status row            | `ctx.statusLine.set(item)` in a handler                       |\n| Ask for one line of text inline, `less`-style            | `ctx.prompts.line(options)` in a command                      |\n| Hide, reorder, retitle files before review               | `hunk.transformChangeset(fn)`                                 |\n| React to loads, selection, view movement, notes, reloads | `hunk.on(event, handler)`                                     |\n| Coordinate with another loaded extension                 | `hunk.events.emit` / `hunk.events.on`                         |\n| Reload after an external agent changes reviewed inputs   | `ctx.review.requestReload()` in an event                      |\n| Read user-supplied settings                              | `hunk.config` (`[extension.<id>]` table)                      |\n| Snapshot stable files and every saved review note        | `ctx.review.snapshot()` in a command                          |\n| Branch on the API generation (currently `32`)            | `hunk.apiVersion`                                             |\n\nRegistration is only valid while the factory runs — Hunk seals the API object\nafterwards.\n\nPromise-returning VCS `watchSignature` hooks and watch cancellation require API\nversion 25. Declare `{\"hunk\": {\"apiVersion\": 25}}` in the manifest, or branch on\n`hunk.apiVersion` and return signatures synchronously on older hosts. Use async\nI/O and honor `ctx.signal` on API 25; existing synchronous hooks remain supported.\n\n### Workspace status providers (API 31)\n\nThe optional `registerVcsAdapter({ status })` capability offers cancellable, read-only\n`read`, `readSiblings`, and `planReview` operations, plus optional async `watchPlan`.\nRead [Workspace status capability](../../../../docs/extensions.md#workspace-status-capability)\nand the `ExtensionVcsStatusCapability` contract before implementing it.\n\n- Action ids are supported semantic ids, not opaque strings: `staged` reviews the index;\n  `unstaged` reviews the working copy. Offer each at most once with provider-owned labels.\n  A no-index provider offers only `unstaged` and omits `paths[].index`.\n- Return `{ cwd, input: ExtensionVcsStatusReviewInput }` from `planReview`. Preserve the\n  inspected target and opaque token validation. This exported type excludes pathspecs while\n  preserving exclusive `range` / `rangeEndpoints` comparisons. Any `pathspecs` field, even\n  `[]` or `undefined`, rejects; `input.staged` must match the offered action id.\n- Sibling ids and paths must each be unique and share the current `repositoryId`. If a row\n  matches the current worktree's id or path, both must match. Embedded ready sibling facts\n  and `readSiblings` results follow the same checks; unavailable/error rows never mean clean.\n- Bound collections before returning: 20,000 changed paths, 100 sibling rows, five operations,\n  256 watch targets, four sources per target, 20,000 watch roots/entries per target, and\n  40,000 aggregate collection entries per result (including nested and outer arrays).\n  Defensive action-array validation caps at 32, but only two unique ids are supported.\n  Oversized results reject before per-element schema traversal. Mark bounded sibling scans\n  `truncated`; honor `context.signal`, bound I/O, and reap subprocesses.\n\n### Generic CLI handlers\n\nRegister one lowercase-kebab top-level token; the handler owns every raw token\nbelow it. Built-ins and aliases cannot be shadowed, and discovery order makes\nthe first extension claim win. During development, place the explicit path\nbefore the extension command:\n\n```bash\nhunk --extension ./my-ext.ts my-command sync --help\n```\n\nThe handler receives frozen args plus `ctx.cwd`, `ctx.signal`, streaming\n`ctx.stdin`, and leased `ctx.stdout`/`ctx.stderr` writers. `summary` and\n`usage` are listed when a token reaches discovery unclaimed, so write them as\none short line each. Return `{ kind:\n\"exit\", code? }` or `{ kind: \"delegate\", argv: [\"diff\", ...] }`. Delegation is\nbuilt-in-only and one-time: do not write stdout or read stdin before delegating;\nuse stderr for progress. Reading stdin is an exit-only workflow. Respect cancellation promptly.\nRepo-local providers remain trust-gated; `--no-extensions` performs no discovery\nor import, while a leading explicit `--extension` path is immediate consent.\n\nUse `packages/hunk-gh/src/` as the reference for a complete bundled CLI preprocessor: direct\nauthenticated HTTP with cancellation, temporary artifacts with platform-accurate permission claims\nretained through delegated startup, cleanup on `shutdown`, and a one-time handoff to built-in\n`patch` without touching stdin or stdout.\n\n## What handlers receive\n\nEvery event, bus, command, and file-view mode handler — plus every changeset\ntransform — gets `ctx.cwd` and `ctx.notify(message, type?)`. A file view's\n`matches` and `layout` get no context at all. Beyond that:\n\n- **Event and bus handlers** also get `ctx.panes` (open/close/toggle/isOpen on\n  any pane), live `ctx.navigation`, attributed `ctx.dialogs`, `ctx.statusLine`\n  (set/clear this extension's status-row items), review reloads through\n  `ctx.review.requestReload()`, and\n  `ctx.events.emit`. `ctx.sidebars` is a deprecated alias for `ctx.panes`.\n- **Command handlers** get `ctx.panes`, `ctx.fileViews` (select/toggle/isActive/\n  refresh/enterMode/exitMode), `ctx.highlights` (refresh prepared line marks,\n  whole or `{ fileId }`-scoped), `ctx.selection` (a snapshot of file, hunk index,\n  nullable current `{ side, line }` source address, and `files`, the visible files\n  in review order), `ctx.navigation` (live,\n  guarded `selectFile`/`selectHunk`/`revealLine`, the\n  last landing one exact `(side, line)` near the viewport top), `ctx.commands`\n  (`isEnabled`/`execute` for public semantic `hunk.*` commands),\n  `ctx.keyboardModes` (enter/exit/probe this extension's session modes), `ctx.review`\n  (deeply immutable snapshots of stable files and complete saved store notes),\n  `ctx.dialogs` (`confirm`/`select`/`input`, queued and attributed),\n  `ctx.statusLine` (set/clear persistent status-row items), `ctx.prompts`\n  (`line`: an inline status-row input resolving the text or `null`, queued and\n  attributed like dialogs), and\n  `ctx.workspace` (`readDocument`, `canWriteDocument`, `writeDocument` with consent).\n- **Pane components** get frozen `files`, selection, placement, exact dimensions,\n  nullable immutable delegated-source `review` metadata, optional `currentLine` paint\n  (with `{ side, line }` when opted in), semantic `theme`, resolved `keybindings`, and\n  guarded navigation/notification `actions`. Availability callbacks receive the same\n  `review` value, so a pane can consume no geometry for ordinary reviews.\n- **File-view `layout`** gets `file`, `width`, `signal`, `changes`, and a lazy\n  `readDocument(side)`.\n- **File-view `mode` handlers** get `ctx.file` and `ctx.fileViews`. `onKey`,\n  `onEnter`, and `onExit` must answer **synchronously** — `onKey`'s return value\n  (`\"handled\"`/`\"pass\"`/`\"exit\"`) is the routing decision, so kick off async work\n  and report it later through `notify` or `refresh`. A passed key reaches any\n  active session keyboard mode before ordinary Hunk routing. Escape is host-owned\n  and never reaches `onKey`.\n- **Session keyboard-mode handlers** get only `ctx.commands`, `ctx.highlights`,\n  `ctx.statusLine`, and activation-scoped `ctx.keyboardModes` beyond the standard\n  context. A prompt-shaped interaction is a command plus `ctx.prompts.line()`,\n  not a mode. Those controls become inert on\n  exit, and lifecycle callbacks cannot change keyboard ownership. Keys are frozen\n  snapshots; dialogs, focused inputs, and file-view modes outrank them. When the\n  session mode owns input, Escape exits it; the status badge and Extensions menu\n  are unconditional host-owned exits.\n\nEvent payloads, pane props, and a command's selection all hand you frozen\n`ExtensionDiffFile` / `ExtensionDiffHunk` views. A changeset transform is the\nexception: it receives the live changeset and is expected to return a new one.\n`metadata` is unfrozen either way — it is the renderer's parsed diff, so pass it\nthrough untouched.\n\n## Rules that bite\n\nMost extension bugs are one of these:\n\n- **Registering a surface does not show it.** Panes need `defaultOpen`,\n  `replaces: \"hunk:files\"`, or a command that opens them. File views remain raw\n  until selected from the **View** menu.\n- **A rejected file-view layout silently becomes raw diff.** `hunkRows` needs one\n  in-bounds, inclusive entry per parsed hunk at the same array index, and\n  `sourceRanges` may not overlap on a side; invalid, oversized, cancelled, and\n  throwing layouts warn once and fall back.\n- **Syntax paint stays declarative.** File views declare complete `codeDocuments` and\n  map exact symbolic spans with `syntax`; Hunk owns tokenization, theme colors,\n  visible-window demand, resource limits, and plain fallback. Keep gutters and\n  separators in non-syntax spans, and use `sourceRanges` separately for notes/navigation.\n- **Never bundle or vendor React.** Hunk serves its own `react` and `@opentui/*`\n  to extension files; a second copy means a second hooks dispatcher and the\n  component fails to render. Import them normally. OpenTUI intrinsics (`box`,\n  `text`, `scrollbox`) need no import.\n- **`layout` is a pure derivation of `(file, width)`.** A stateful view keeps\n  painting its first answer until `ctx.fileViews.refresh(viewId)` — scope it with\n  `{ fileId }` when the state belongs to one file.\n- **Handler state must live outside the component.** Panes unmount when closed;\n  bridge module-level state into React with `useSyncExternalStore` and immutable\n  snapshots (`review-triage/index.tsx` is the working version).\n- **Use `ctx.review.snapshot()` for complete saved-note state.** `note_created` and\n  `note_edited` are incremental UI events, not an authoritative collection. Snapshots\n  include stale and orphaned saved notes, exclude drafts and static sidecar annotations,\n  and should be re-read before irreversible async work; compare both generation and revision.\n  `review-note-navigator` shows how to join stable note ids and file keys back to guarded\n  navigation after awaiting a selector; file filters can still refuse hidden targets.\n- **Retained review controls expire on reload.** An old handler cannot control\n  replacement content: pane/navigation calls become inert, dialogs cancel, and\n  workspace reads or not-yet-started writes return `null`/`unavailable`. A\n  consented write already in progress reports its real outcome, holds graceful\n  exit until it settles, and reconciles the active review on success. `shutdown`\n  runs after revocation, so use it only\n  to release extension-owned resources.\n- **A reload keeps your factory and renames the files.** Factories re-run only\n  after a trust grant or a cwd change, so module state survives — but a file's\n  `id` encodes its position in the changeset, so a reload that adds or drops a\n  file renumbers the rest. Key durable per-file state by `path`, or reconcile it\n  on `changeset_loaded`. Pick one deliberately.\n- **Transforms must preserve `metadata`** (spreading a file does), keep ids\n  unique, and return a real changeset — otherwise the transform is skipped with a\n  warning and the previous changeset carries forward.\n- **Chords are defaults.** Users remap by command id in `[keybindings]`; built-ins\n  win conflicts, refused one chord at a time. Bind the character shift produces\n  (`\"!\"`, not `\"shift+1\"`).\n- **Keyboard modes are grammar, not behavior.** Keep pending sequences and\n  numeric prefixes in the extension, then call one public `ctx.commands.execute`\n  after resolving an action. `vim-navigation` demonstrates counts, Ctrl chords,\n  and a `:` key passed to a registered command whose host input dialog temporarily\n  outranks the still-active mode.\n- **`ctx.commands` invokes Hunk, not other extensions.** Probe with\n  `isEnabled(\"hunk.review.nextHunk\")`, then call `execute(id, { count })` for an\n  explicitly public built-in. Counts are positive whole numbers up to 10,000,\n  applied atomically to movement; one-shot actions run once. Unknown, disabled,\n  private, extension-owned, or stale commands return `false`.\n- **Repo config can set `[extension.<id>]` for a globally installed extension.**\n  Treat `hunk.config` as untrusted for anything exec-adjacent (binary paths,\n  shell commands, module loading).\n- **`ctx.workspace` writes only apply to reloadable, unstaged working-tree\n  reviews**, by reviewed file id, inside the review root, with consent. Everything\n  else returns `{ ok: false, reason }` — check `canWriteDocument` first.\n- **File-view note placement is all-or-raw per file**: an unbound or range-less\n  visible note makes Hunk render the complete raw diff instead of guessing.\n- **Failures are contained, not sandboxed.** A throwing factory is rolled back to\n  zero registrations and a throwing handler is a warning naming the extension —\n  containment against bugs, not against code that should not have been loaded.\n- **The API touches nothing outside the review.** No clipboard, no filesystem, no\n  process surface beyond `ctx.workspace` — an extension is ordinary code, so shell\n  out for the rest. Never write to stdout: the renderer owns it. For the same\n  reason `hunk.log` is collected as diagnostics and printed nowhere; `ctx.notify`\n  is how a user hears from you.\n- **`HunkExtensionUserError`** (detected structurally by `name`) buys the full\n  treatment — message plus `suggestions`, no stack trace — only from a VCS adapter\n  operation, which is where Hunk formats it for the CLI. From a command or event\n  handler only the message survives, as a warning toast.\n\n## Verifying\n\nHunk's TUI needs a real terminal, and the review UI is the user's — **do not\nlaunch `hunk diff`/`hunk show` to test, and do not reach for a pipe.** No\ninvocation applies extensions headlessly: `hunk diff … | cat` still starts the\napp and still takes the keyboard, so it hangs holding the user's terminal.\nPractical checks, in order of cost:\n\n1. **Typecheck.** In a checkout, `bun run typecheck` covers\n   `examples/extensions/**` via the `hunkdiff/extension` path mapping. Standalone,\n   add `hunkdiff` as a dev dependency and run `tsc --noEmit`; for a `.tsx`\n   extension also add `react`, `@types/react` (React ships no declarations of its\n   own), `@opentui/core`, and `@opentui/react` as **dev** dependencies and set\n   `\"jsx\": \"react-jsx\"` with\n   `\"jsxImportSource\": \"@opentui/react\"`, or every `<box>` and `<text>` is an\n   untyped intrinsic. Types only — shipping those packages is the second-React bug.\n2. **Unit-test the logic.** When parsing, matching, or formatting is worth\n   testing, put it in helper modules with plain `bun test` coverage.\n3. **PTY integration.** In a checkout, `test/pty/extensions-integration.test.ts`\n   launches Hunk over a PTY with `--extension <path>` and asserts on rendered\n   snapshots; extend it via `test/pty/harness.ts` and run `bun run test:integration`.\n4. **Hand it to the user** to run: `hunk diff --extension ./my-ext`. `--extension`\n   loads immediately with no trust prompt, so it is the iteration path. Ask them\n   what the footer notices and toasts said.\n5. **Triage with `--no-extensions`** to confirm a symptom belongs to an extension\n   (bundled VCS backends, the built-in files pane, and the `/` content search stay loaded\n   either way).\n\n## If it does not load\n\n- No startup notice at all → a successful load is silent, so either it loaded and\n  nothing opened it, or discovery never saw the file. Check the directory, the\n  entry suffix, or the folder's `package.json` `hunk.extensions` paths.\n- Notice naming the extension → id rejected (reserved, malformed, or already\n  claimed), import failure, missing default export, or a throwing factory.\n- Repo-local extension silently absent → the trust prompt was dismissed or denied;\n  decisions are stored per repo root in `~/.config/hunk/state.json`.\n- Pane closes with a toast → the component threw; a second React copy is\n  the usual cause.\n- Pane or file view never appears → nothing opened it (no `defaultOpen`, no\n  command), `matches` returned false, or the layout was rejected.\n- Command never fires → its chord lost to a built-in or an earlier extension (a\n  warning says so); it is still reachable from the **Extensions** menu and\n  bindable by `<id>.<commandId>`.\n\n## Changing Hunk itself\n\nOnly when the work is in the `hunk` repo rather than in a user extension:\n\n- Shipped VCS backends, `hunk gh`, the built-in files pane, and the `/` content search are\n  **bundled extensions**, registering through the same public API. That dogfooding is deliberate —\n  if the public contract cannot express something, that is a real gap, not a reason for a private\n  path. `default/vcs/` loads from VCS adapter resolution and must stay renderer-free;\n  `default/core/` loads session-owned CLI capabilities from private workspaces such as\n  `packages/hunk-gh`; and `default/ui/` holds process-cached rendering registrations.\n- `packages/hunk/src/extension-api/types.ts` must stay **import-free**; declaration emission\n  publishes whatever it reaches, and `scripts/packaging/check-pack.ts` fails the pack\n  otherwise. Shapes shared with internal code are declared there and re-exported\n  inward.\n- New API surface means updating `docs/extensions.md` (its examples are\n  typechecked as consumer code), the matching hand-written page under\n  `website/src/content/docs/docs/extend/` (only `cli.md` and `config.md` are\n  generated), `docs/extension-architecture.md` if ownership moves, and a changeset.\n- `AGENTS.md` and `docs/extension-architecture.md` own the rest of these rules.\n","packages/hunk/skills/hunk-review/SKILL.md":"---\nname: hunk-review\ndescription: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files, hunks, and exact lines, reloads session contents, adds inline review comments, and paints attention marks on character ranges. 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\n10. hunk session highlight add ...                      # light up the exact range you are explaining\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 (<session-id> | --repo <path>) [--json]\nhunk session context (<session-id> | --repo <path>) [--json]\nhunk session review (<session-id> | --repo <path>) [--include-patch] [--include-notes] [--json]\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- `review --include-notes` also returns the live review notes alongside the file and hunk structure\n\n### Navigate\n\n```bash\nhunk session navigate (<session-id> | --repo <path>) --file <path> (--hunk <n> | --old-line <n> | --new-line <n>) [--json]\nhunk session navigate (<session-id> | --repo <path>) --comment <id> [--json]\nhunk session navigate (<session-id> | --repo <path>) (--next-comment | --prev-comment) [--json]\n```\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\nExact comment navigation uses the `commentId` returned by `hunk session comment list --json` and does not require `--file`:\n\n```bash\nhunk session navigate --repo . --comment comment-1\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- A line target lands the user's viewport on that exact line (falling back to its hunk when the line is inside a collapsed region); `--hunk` lands on the hunk\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 (<session-id> | --repo <path> | --session-path <path>) [--source <path>] [--json] -- diff [ref] [-- <pathspec...>]\nhunk session reload (<session-id> | --repo <path> | --session-path <path>) [--source <path>] [--json] -- show [ref] [-- <pathspec...>]\n```\n\nExamples:\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 (<session-id> | --repo <path>) (--reply-to <note-id> | --file <path> (--old-line <n> | --new-line <n>)) --summary <text> [--rationale <text>] [--author <name>] [--markup <stml>] [--focus] [--json]\nhunk session comment apply (<session-id> | --repo <path>) --stdin [--focus] [--json]\nhunk session comment list (<session-id> | --repo <path>) [--file <path>] [--type <live|all|ai|agent|user>] [--json]\nhunk session comment rm (<session-id> | --repo <path>) <comment-id> [--json]\nhunk session comment clear (<session-id> | --repo <path>) [--file <path>] [--include-user|--all] --yes [--json]\n```\n\nExamples:\n\n```bash\nhunk session comment add --repo . --file README.md --new-line 103 --summary \"Tighten this wording\"\nhunk session comment add --repo . --reply-to user:123 --summary \"Addressed in the latest revision\"\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' | hunk session comment apply --repo . --stdin\n```\n\n- Agent notes start hidden: the user sees them only after pressing `a` or setting `agent_notes = true`. `--focus` jumps to the new note (or the first note in a batch) and turns agent notes on, so pass it on the first note you leave in a session, or tell the user to press `a`\n- `comment list` shows the comments added through `hunk session comment` by default; `--type user` lists notes the user wrote with `c`, `--type ai` lists notes loaded from an `--agent-context` sidecar, `--type agent` lists agent-authored notes, and `--type all` lists every note\n- `comment add` is best for one note; `comment apply` is best when an agent already has several notes ready\n- Root `comment add` notes require `--file`, `--summary`, and exactly one of `--old-line` or `--new-line`; replies use `--reply-to <note-id>` with `--summary` and inherit the parent's anchor\n- `comment apply` items require `summary` plus either `replyTo` by itself or `filePath` with 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- `comment list` and `comment clear` accept optional `--file`\n- Quote `--summary` and `--rationale` defensively in the shell\n\n### Attention marks\n\nHighlights paint character ranges inside the diff lines the user is looking at — use them to light up the exact expression you are explaining while you narrate.\n\n```bash\nhunk session highlight add (<session-id> | --repo <path>) --file <path> (--old-line <n> | --new-line <n>) --start <n> --end <n> [--tone <tone>] [--focus] [--json]\nhunk session highlight clear (<session-id> | --repo <path>) [--file <path>] [--json]\n```\n\nExamples:\n\n```bash\nhunk session highlight add --repo . --file src/App.tsx --new-line 42 --start 6 --end 19\nhunk session highlight add --repo . --file src/App.tsx --new-line 42 --start 6 --end 19 --tone warning --focus\nhunk session highlight clear --repo .\n```\n\n- `highlight add` requires `--file`, exactly one of `--old-line` or `--new-line`, and the `--start` / `--end` offsets\n- `--start` is a 0-based inclusive offset into the line's text and `--end` is exclusive, counted in UTF-16 code units — the same `[start, end)` range extensions use\n- Tones: `match` (default), `info`, `warning`, `error`, `dim`; `current` renders as reverse video and is best reserved for the one range under discussion\n- Pass `--focus` to also land the viewport on the marked line\n- Marks survive scrolling, navigation, and reloads that leave the marked file's content unchanged; a reload that changes that file drops its marks, and `highlight clear` removes them explicitly (optionally per `--file`)\n- Marks are visual only — pair them with a `comment add` when the explanation should persist as a note\n\n### Experimental rich markup notes (STML)\n\nOnly use STML when `hunk session context --json` lists `stml` in `experimentalFeatures`. The user opts into that experience by launching the review with `--experimental`; do not ask a normal session to render markup.\n\nFor an opted-in session, `--markup` (or a `markup` field on apply items) renders the note body as STML — a small HTML-like markup for terminal UI (boxes, rows, gauges, badges, lists, code). Keep `--summary` a real sentence: it is the fallback and the `comment list` text.\n\nBefore writing markup, run `hunk markup guide` once — it has copy-paste patterns and the width rules. The session context also reports `noteMarkupWidth` (the live render width); preview with `hunk markup render - --width <that>`. Comment responses echo `markupWidth` and return `markupNotes` when markup degraded — fix what they flag.\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 `highlight add --focus` to steer the user's eyes to the exact expression while you explain it, and `highlight clear` before moving to the next topic\n- Use `comment apply` for agent-generated batches and `comment add` for one-off notes\n- Use `--focus` on your first note so agent notes become visible, then sparingly when a note 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 diff file matches ...\"** -- the file is not in the loaded review. Check `context`, then `reload` if needed.\n- **\"No active Hunk sessions\"** -- if Hunk is visibly running, localhost may be blocked by the agent sandbox; retry with network/sandbox escalation. Otherwise ask the user to open Hunk.\n- **\"Multiple active sessions match\"** -- pass `<session-id>` explicitly.\n- **\"No active 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 exactly one comment target\"** -- pass `comment add` one of `--old-line` or `--new-line`.\n- **\"Specify exactly one highlight target\"** -- pass `highlight add` one of `--old-line` or `--new-line`.\n- **\"Highlight --end must be greater than --start\"** -- offsets are `[start, end)` UTF-16 code units into the line text; end is exclusive.\n- **\"Specify either --next-comment or --prev-comment, not both.\"** -- choose one comment-navigation direction.\n- **\"The session daemon is ...\"** -- a `daemon-build-mismatch` (the `--json` error carries `daemon`, `cli`, `attachedSessions`, and `recommendedAction`). Tell the user which build is newer and how many windows are attached, then **ask** before running `hunk daemon restart --yes`; never restart unprompted. After the restart, windows that failed to register attach on their own, so re-run `hunk session list` instead of relaunching anything. When `recommendedAction` is `use-newer-hunk`, the daemon is the newer build: use that Hunk instead.\n- **\"Could not read the raw diff for ...\"** -- the session reloaded or closed while `--include-patch` was reading it. Re-run `review`; drop `--include-patch` if you only need file and hunk structure.\n","packages/session-broker-bun/README.md":"# @hunk/session-broker-bun\n\nPrivate Bun HTTP and websocket adapter for `@hunk/session-broker`.\n\nUse this workspace to serve a runtime-neutral `SessionBrokerDaemon` through `Bun.serve(...)`.\nIt is not a published, versioned SDK package. See the main broker README for current internal API\nconstruction and the [session broker SDK contract](../../docs/session-broker-sdk.md) for the future\npublic package.\n\n## What it does\n\n- binds a broker daemon to a Bun HTTP server\n- upgrades websocket requests on the daemon socket path\n- forwards websocket messages and close events into the daemon\n- exposes a `stopped` promise compatible with Hunk's daemon lifecycle\n- lets callers override or add custom HTTP routes before the daemon's built-in routes\n\n## Usage\n\nCreate the daemon with steps 1–2 in\n[`@hunk/session-broker`](../session-broker/README.md), then bind it to Bun:\n\n```ts\nimport type { SessionBrokerDaemon } from \"@hunk/session-broker\";\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-bun\";\n\ndeclare const daemon: SessionBrokerDaemon;\n\nconst server = serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n## Custom routes\n\nYou can override or extend request handling with `handleRequest`.\n\n```ts\nconst server = serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n  handleRequest: async (request) => {\n    const url = new URL(request.url);\n    if (url.pathname === \"/health\") {\n      return Response.json({ ok: true, overridden: true });\n    }\n\n    return undefined;\n  },\n});\n```\n\nReturn `undefined` to fall through to the daemon's built-in routes. The raw `/broker` HTTP API is available only when the daemon was created with `exposeHttpApi: true`.\n\n## License\n\nMIT\n","packages/session-broker-core/README.md":"# @hunk/session-broker-core\n\nLow-level shared primitives for the session broker packages.\n\nThis package is an **internal foundation layer**, not the main entrypoint you should build against in most cases.\n\n## Use this package when\n\n- you are working on broker internals\n- you need the shared envelope types and parsers directly\n- you are implementing a higher-level broker package on top of it\n\n## Prefer these packages for normal use\n\n- `@hunk/session-broker` — main runtime-neutral broker API\n- `@hunk/session-broker-bun` — Bun runtime adapter\n- `@hunk/session-broker-node` — Node runtime adapter\n\n## What this package includes\n\n- shared session envelope types\n- registration and snapshot wire parsing helpers\n- low-level in-memory `SessionBrokerState`\n- selector helpers for `sessionId`, `sessionPath`, and `repoRoot`\n- generic terminal metadata capture\n\n## What this package does not include\n\n- daemon behavior\n- session-side websocket lifecycle helpers\n- Bun or Node listener setup\n- app-specific command semantics or projections\n\nThose higher-level concerns live in the packages above.\n\n## Package boundary\n\nThe intended split is:\n\n- **`@hunk/session-broker-core`** — low-level primitives\n- **`@hunk/session-broker`** — main broker API\n- **runtime adapters** — Bun and Node listener bindings\n\n## Quick example\n\n```ts\nimport {\n  brokerWireParsers,\n  parseSessionRegistrationEnvelope,\n  parseSessionSnapshotEnvelope,\n  SessionBrokerState,\n} from \"@hunk/session-broker-core\";\n```\n\nIf you find yourself reaching for this package directly in app code, double-check whether `@hunk/session-broker` would be the better fit.\n\n## License\n\nMIT\n","packages/session-broker-node/README.md":"# @hunk/session-broker-node\n\nPrivate Node HTTP and websocket adapter for `@hunk/session-broker`.\n\nUse this workspace to serve the broker daemon under Node instead of Bun. It is not a published,\nversioned SDK package. See the main broker README for current internal API construction and the\n[session broker SDK contract](../../docs/session-broker-sdk.md) for the future public package.\n\n## What it does\n\n- serves a runtime-neutral `SessionBrokerDaemon` through Node HTTP\n- upgrades websocket requests with `ws`\n- forwards websocket messages and close events into the daemon\n- exposes async startup and shutdown helpers\n- keeps the runtime-specific listener code out of `@hunk/session-broker`\n\n## Usage\n\nCreate the daemon with steps 1–2 in\n[`@hunk/session-broker`](../session-broker/README.md), then bind it to Node:\n\n```ts\nimport type { SessionBrokerDaemon } from \"@hunk/session-broker\";\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-node\";\n\ndeclare const daemon: SessionBrokerDaemon;\n\nconst server = await serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n## Why this package exists\n\nThis package runs the shared broker API against Node listener and websocket primitives.\n\nIf the Node adapter needs an abstraction the shared package does not provide, the fix should happen in `@hunk/session-broker`, not as Node-only glue.\n\n## License\n\nMIT\n","packages/session-broker/README.md":"# @hunk/session-broker\n\nRuntime-neutral session broker daemon and connection helpers.\n\nThe [session broker SDK contract](https://github.com/modem-dev/hunk/blob/main/docs/session-broker-sdk.md)\ndescribes the future supported public package. These private `0.0.0` source workspaces predate that\ncontract and do not yet satisfy all of its security, compatibility, supervision, packaging, or\nrelease gates.\n\nThe current package provides signed producer/caller hello, short-lived caller sessions, replay\nadmission, and default-deny raw HTTP authorization primitives. Hunk discovers app-scoped\ncredentials, starts session producers, and authenticates callers in its own runtime adapters. The\nlegacy Hunk WebSocket and custom session routes remain internal-only; no bearer fallback is\navailable. Browser-review capabilities are separate from broker caller credentials.\n\nThis is the **main broker package** in the workspace. It owns the reusable broker behavior without committing to Bun or Node server APIs.\n\nUse this package when you want to:\n\n- track live sessions\n- register and update session snapshots\n- route commands to one live session\n- expose broker health and optional raw list/get/dispatch APIs\n- manage session-side websocket connection state\n\n## Current workspace roles\n\nThe source tree is currently split into four private workspaces:\n\n- `@hunk/session-broker-core` — low-level shared primitives and envelope parsing\n- `@hunk/session-broker` — **main runtime-neutral broker API**\n- `@hunk/session-broker-bun` — Bun HTTP/websocket adapter\n- `@hunk/session-broker-node` — Node HTTP/websocket adapter\n\nThe public SDK contract consolidates these into one `@hunk/session-broker` package whose root\nselects Node or Bun automatically. Until that migration lands, these names and examples describe\ninternal workspace usage only.\n\n## What this package owns\n\n- `SessionBroker` raw session registry\n- `SessionBrokerDaemon` runtime-neutral daemon engine\n- `SessionBrokerConnection` runtime-neutral session-side websocket helper\n- raw broker HTTP request types\n- health handling and optional capabilities API handling\n- stale-session pruning and idle shutdown\n\n## What this package does not own\n\n- Bun `Bun.serve(...)`\n- Node `http` / `ws` listener setup\n- app-specific command semantics\n- app-specific projections like Hunk review exports, comments, or selected hunks\n- daemon process launch policy\n\n## Current internal quick start\n\n### 1. Create a broker\n\n```ts\nimport {\n  SessionBroker,\n  brokerWireParsers,\n  createSessionBrokerProtocolParsers,\n  parseSessionRegistrationEnvelope,\n  parseSessionSnapshotEnvelope,\n} from \"@hunk/session-broker\";\n\ninterface SessionInfo {\n  title: string;\n}\n\ninterface SessionState {\n  selectedIndex: number;\n}\n\nfunction parseInfo(value: unknown): SessionInfo | null {\n  const record = brokerWireParsers.asRecord(value);\n  if (!record) {\n    return null;\n  }\n\n  const title = brokerWireParsers.parseRequiredString(record.title);\n  return title === null ? null : { title };\n}\n\nfunction parseState(value: unknown): SessionState | null {\n  const record = brokerWireParsers.asRecord(value);\n  if (!record) {\n    return null;\n  }\n\n  const selectedIndex = brokerWireParsers.parseNonNegativeInt(record.selectedIndex);\n  return selectedIndex === null ? null : { selectedIndex };\n}\n\nconst protocolParsers = createSessionBrokerProtocolParsers({\n  appRevision: 1,\n  features: [],\n  parseRegistration: (value) => parseSessionRegistrationEnvelope(value, parseInfo),\n  parseSnapshot: (value) => parseSessionSnapshotEnvelope(value, parseState),\n  commands: [\n    {\n      command: \"select\",\n      version: 1,\n      parseInput: (value) => (brokerWireParsers.parseNonNegativeInt(value) === null ? null : value),\n      parseResult: (value) => (value === true ? true : null),\n    },\n  ],\n});\n\nconst broker = new SessionBroker({ protocolParsers });\n```\n\n### 2. Create a daemon engine\n\n```ts\nimport { createSessionBrokerDaemon } from \"@hunk/session-broker\";\n\nconst daemon = createSessionBrokerDaemon({\n  broker,\n  capabilities: {\n    version: 1,\n    name: \"example-broker\",\n  },\n});\n```\n\nAt this point the daemon can:\n\n- handle health requests\n- process websocket register/snapshot/heartbeat/result messages\n- prune stale sessions and request idle shutdown\n\nThe raw HTTP broker API is opt-in and fails closed: `exposeHttpApi: true` exposes no control route\nunless an explicit immutable `appId`, a singleton `appRevision`, a `callerAuthenticator`, and an app\n`authorizer` are supplied. The included\n`SessionBrokerAuthenticator` implements Ed25519 challenge/proof and signed requests; applications\ninject app-scoped grants, public verifiers, daemon signing identity, revocation policy, and their\nown default-deny authorization hook. It performs no filesystem, environment, coordinator, or Hunk\ncredential discovery.\n\n### 3. Serve it through a runtime adapter\n\n#### Bun\n\n```ts\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-bun\";\n\nconst server = serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n#### Node\n\n```ts\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-node\";\n\nconst server = await serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n## Session-side connection helper\n\nUse `SessionBrokerConnection` when an app window or live process needs to stay registered with the broker.\n\n```ts\nimport {\n  createNativeSessionBrokerLifecycleClock,\n  createSessionBrokerConnection,\n  type SessionBrokerSocketLike,\n} from \"@hunk/session-broker\";\n\nconst lifecycleClock = createNativeSessionBrokerLifecycleClock();\nconst connection = createSessionBrokerConnection({\n  url: \"ws://127.0.0.1:47657/session\",\n  createSocket: (url) => new WebSocket(url) as unknown as SessionBrokerSocketLike,\n  registration,\n  snapshot,\n  protocolParsers,\n  lifecycleClock,\n  bridge: {\n    dispatchCommand: async (message) => {\n      if (message.command !== \"select\") throw new Error(\"Unsupported command.\");\n      selectFile(message.input);\n      return true;\n    },\n  },\n});\n\nconnection.start();\n```\n\n`lifecycleClock` may be supplied in the connection options when an application needs to share\nlifecycle timing with its launcher or make producer scheduling deterministic. The ordinary\n`SessionBrokerLifecycleClock` contract provides current time, one-shot scheduling, delayed-first\nfixed-rate interval scheduling, and awaitable delay. Scheduled callbacks return idempotent\ndisposers. `createNativeSessionBrokerLifecycleClock()` uses unref'd native timers so pending\nhandshake, heartbeat, reconnect, and polling work does not retain the process.\n\n`createSocket` must return a fresh socket object for every generation because the helper installs\nproperty callbacks that cannot distinguish queued events after object reuse. Reconnect and close\ncallbacks receive a frozen opaque generation token; after foreign work settles, use\n`connection.isGenerationCurrent(token)` before committing app-owned state. These checks fence\ncommits only: they do not cancel foreign promises, cryptography, probes, or other work already in\nprogress.\n\nUnexpected defects in connection-owned native callbacks, scheduled work, or fire-and-forget work\nterminally retire that connection. Applications may supply `onDefect` to observe the event. The\ncallback receives only `SESSION_BROKER_LIFECYCLE_DEFECT_MESSAGE`; thrown values and lifecycle data\nare never forwarded, the callback runs at most once, and callback failures are contained. This\nruntime-neutral package does not write defect reports to process output.\n\nThe helper owns:\n\n- initial `register`\n- later `snapshot` updates\n- heartbeats\n- `command-result` replies\n- queued broker commands until the bridge is ready\n- reconnect scheduling\n\n## Raw broker API\n\nThe daemon always serves `GET /health`. Its raw capability/control API is intentionally small and\ndisabled by default. When `exposeHttpApi: true` is set together with an explicit `appId`, singleton\n`appRevision`, caller authenticator, and authorizer, it additionally serves:\n\n- `GET /broker/capabilities`\n- `POST /broker`\n\nRequest body shapes:\n\n```ts\n{ action: \"list\" }\n{ action: \"get\", selector: { sessionId: \"...\" } }\n{ action: \"dispatch\", selector: { sessionId: \"...\" }, command: \"...\", commandVersion: 1, input: {...} }\n```\n\nAn omitted `commandVersion` is validated and deliberately defaults to revision `1` for current\ninternal callers. Authentication covers the exact bounded HTTP body bytes before strict UTF-8 and\nJSON decoding. Authenticated responses use `{ body, authentication }`; the signed authentication\nrecord binds daemon generation, broker revision, target application contract when applicable,\nrequest ID, HTTP status, and the canonical structured-body digest.\n\n## Hunk-specific layering\n\nHunk uses this package for generic broker lifecycle and keeps product behavior in `packages/hunk`:\n\n- credential discovery: `packages/hunk/src/session/broker/credentials.ts`\n- daemon and Hunk-specific routes: `packages/hunk/src/session/broker/brokerServer.ts`\n- authenticated agent caller: `packages/hunk/src/session/agent/cliClient.ts`\n- session commands and review projections: `packages/hunk/src/session/`\n\nThe broker owns transport and lifecycle; Hunk owns session meaning and policy.\n\n## License\n\nMIT\n","packages/term-video/README.md":"# @hunk/term-video\n\nKeyframe-based terminal product videos. Instead of screen-recording in real\ntime, a script drives the real TUI over a PTY and snaps discrete styled\nkeyframes; a storyboard then declares how long each state holds and which\ncaption accompanies it; Chromium composites each frame onto a stage (window\nchrome, captions, title cards); ffmpeg encodes the result. Pacing is\ndeterministic, a minute of video is ~300 unique frames, and every frame is an\ninspectable PNG before anything is encoded.\n\n## Entry points\n\n- `@hunk/term-video/capture` (Bun) — PTY driving and keyframe snapping:\n  `createKeyframer`, `launchApp`, `launchShell`, `createCommandWrapper`,\n  `typeCommand`, `ensureKeyboardIsLive`, `makeSceneFilter`.\n- `@hunk/term-video/plan` (Bun or Node) — pure storyboard→frame-plan\n  expansion: `planFrames`, `requiredKeyframes`. Unit-tested; owns caption\n  carry-forward plus camera-pan, zoom, and source-aligned highlight animation.\n- `@hunk/term-video/compose` (Node ≥ 22) — `composeStoryboard` renders the\n  plan in headless Chromium against `src/stage.html` (or a custom stage) and\n  writes PNG frames plus an ffmpeg concat list. `playwright-core` is resolved\n  from the caller's work directory so its version can match the driven\n  Chromium build.\n\n- `@hunk/term-video/timeline` (Node ≥ 22) — `renderTimeline` steps a free-form\n  motion page frame by frame instead of planning stage states. The page owns\n  all of its motion and exposes `timelineReady`, `TIMELINE_DURATION`, and a\n  pure `renderAt(t)`, so any frame renders on its own for review stills.\n  `frameTimes` and `encodeArgs` are the unit-tested sampling and encoding\n  pieces.\n\n## Consumers\n\n`scripts/launch-video/` holds Hunk's current storyboard (scenes, captions,\ncards) on top of this package, and `scripts/launch-video/promo/` holds Hunk's\nrelease-promo style kit rendered through `timeline`; `skills/hunk-launch-video/SKILL.md` documents the\nend-to-end workflow and recipes for single-feature, custom, and full-release\nvideos, environment gotchas included.\n","skills/hunk-launch-video/SKILL.md":"---\nname: hunk-launch-video\ndescription: Produces Hunk videos by driving the real TUI headlessly in a PTY, compositing captioned 1080p frames in Chromium, and encoding with ffmpeg. Use for feature demos, workflow explainers, announcements, launch videos, and full-release roundups.\nmetadata:\n  internal: true\n---\n\n# Hunk video pipeline\n\nSource-checkout only: the pipeline lives in `scripts/launch-video/`, which never ships to npm.\nIt can be used for pull-request demos as well as maintainer release videos. Unix-only — the\ncapture scripts exec `/bin/bash`.\n\nGenerates product videos where every terminal frame is the real Hunk TUI —\nno screen recording, no mockups. Three stages:\n\n```text\ncapture.ts   (bun)               drive Hunk over a PTY, snap styled keyframes to PNG\ncompose.mjs  (node + Playwright) render each PNG on a 1920x1080 HTML stage in Chromium\nffmpeg                           encode the composited PNGs at 30fps to mp4/webm\n```\n\nPlaywright controls headless Chromium: for each planned frame it loads the\nterminal PNG onto the HTML stage, applies the window chrome, cards, captions,\nand transition state, then screenshots the completed stage back to PNG. ffmpeg\nsequences those composited screenshots into the final videos.\n\nThe generic machinery (PTY driving, keyframe rendering, storyboard planning,\nChromium compositing, the stage template) is the `@hunk/term-video` workspace\npackage in `packages/term-video/`; `scripts/launch-video/` holds only Hunk's\nscenes, captions, and cards on top of it.\n\n## Choosing a recipe\n\nThe pipeline is not release-specific. Choose the editorial scope, then use the\nsame capture → composite → encode stages:\n\n- **Single feature:** a short demonstration of one capability or workflow. Use\n  the single-feature recipe below and capture only the required scene.\n- **Full release:** the social promo for a release, in Hunk's house style\n  (`scripts/launch-video/promo/`). Use the full-release recipe: write one\n  storyboard file for the release; the style kit stays unchanged.\n- **Custom video:** author any set of scenes and `SHOTS` for tutorials,\n  comparisons, announcements, or workflow explainers; follow the scene and\n  storyboard rules below.\n\n## Creating a video\n\nExpect ~3–6 min for capture and ~2–4 min for compose — run both with a long\ntimeout (or in the background); each logs per-snap / per-shot progress.\n`compose.mjs` needs node ≥ 18 on PATH (bun alone is not enough).\n\n```sh\n# 0. dependencies (tuistory is a devDependency; ghostty-opentui arrives transitively)\nbun install    # if a postinstall hook fails in a sandbox, retry with --ignore-scripts\n\n# 1. capture the current storyboard's default scenes. Output defaults to\n#    <repo>/.video-work/ regardless of cwd (pass a path argument to override),\n#    but the PROCESS must run from the repo root — see gotchas.\nbun run scripts/launch-video/capture.ts\n\n# 2. one-time portable compositor setup. Playwright installs a Chromium build\n#    that exactly matches its browser driver (see gotchas to reuse a system or\n#    sandbox browser instead).\nprintf '{\"name\":\"hunk-video-work\",\"private\":true}\\n' > .video-work/package.json\ncd .video-work\nbun add playwright playwright-core\nbunx playwright install chromium\ncd ..\n\n# 3. composite the storyboard\nnode scripts/launch-video/compose.mjs .video-work\n\n# 4. encode\ncd .video-work\nffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n  -c:v libx264 -preset slow -crf 18 -movflags +faststart launch.mp4\nffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n  -c:v libvpx-vp9 -b:v 0 -crf 32 -row-mt 1 launch.webm\n```\n\nOverride the default scene set to iterate on one or more scenes:\n\n```sh\nSCENES=review bun run scripts/launch-video/capture.ts   # comma-separated scene names\n```\n\nScene names are the `wants(\"...\")` guards in `capture.ts`'s `main()`. `SCENES=`\nreplaces the canonical default scene set; `compose.mjs` preflights that every\nframe its `SHOTS` table references exists in `frames/` and fails fast listing\nany missing ones, so a full composite still needs every referenced scene\ncaptured at least once.\n\n## Single-feature recipe\n\nFor a short test, demo, or one-feature announcement, capture and composite only\nthe scene for that feature:\n\n1. Pick one user-visible capability and find its scene name in the\n   `wants(\"...\")` guards. If it does not have a scene, author one using the\n   guidance below. Capture only that scene:\n\n   ```sh\n   SCENES=review bun run scripts/launch-video/capture.ts\n   ```\n\n2. Make a scratch compositor beside the canonical one so its imports and\n   repo-relative paths continue to work:\n\n   ```sh\n   cp scripts/launch-video/compose.mjs scripts/launch-video/compose-one-feature.mjs\n   ```\n\n3. In the scratch copy, trim `SHOTS` to an opening card, only the selected\n   feature's frames, and an outro card. Rewrite those cards and captions for\n   the scoped cut. Sequence lengths must still match the captured frame names.\n4. Composite into the same work directory, then run the normal ffmpeg commands\n   with descriptive output names:\n\n   ```sh\n   node scripts/launch-video/compose-one-feature.mjs .video-work\n   cd .video-work\n   ffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n     -c:v libx264 -preset slow -crf 18 -movflags +faststart hunk-feature-demo.mp4\n   ffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n     -c:v libvpx-vp9 -b:v 0 -crf 32 -row-mt 1 hunk-feature-demo.webm\n   cd ..\n   rm scripts/launch-video/compose-one-feature.mjs\n   ```\n\nKeep the scratch compositor uncommitted. The canonical `compose.mjs` remains\nthe checked-in reference storyboard. If you added a capture scene only to make\nPR evidence, revert that scene after encoding; retain it only when it is useful\nchecked-in demo coverage and belongs to the submitted change.\n\n## Full-release recipe\n\nRelease videos use the promo style kit in `scripts/launch-video/promo/`, so\nevery release looks like part of one series. The kit renders through\n`@hunk/term-video/timeline`:\n\n```text\nstoryboards/<version>.mjs   release content: chapters, captions, montage, credits range\nengine.js + stage.html      the style: sting, countdown hook, chapter cards, terminal\n                            scenes, montage, contributors h, outro, transitions\ncontributors.mjs            git range -> contributors.json + avatars/\nrender.mjs                  storyboard + frames + contributors -> promo-<version>.mp4\n```\n\n1. **Pick the headlines.** Read the release section in `CHANGELOG.md` (or its\n   hand-written **Highlights**) and choose 4–6 chapters ordered by impact: new\n   capabilities first, then visible UI/UX changes, then smaller conveniences.\n   Per-PR entries that are too small for a chapter go in the montage. Check\n   that each claim is accurate: a Patch-level fix can earn a chapter, but name\n   what it does rather than calling it new. Confirm the shortlist with the\n   user before capturing.\n2. **Start the storyboard** by copying the newest one, for example\n   `cp storyboards/0.24.0.mjs storyboards/0.25.0.mjs`. Set `version`,\n   rewrite `chapters` and `montage`, set `montage.fixes.count` from the\n   changelog's patch entries, and point `contributors.from`/`to` at the\n   previous release tag and this release's tag. Leave `aliases` from older\n   releases out unless they still apply.\n3. **Capture** each chapter's keyframes, reusing or adding scenes in\n   `capture.ts` (see \"Authoring scenes\"); every `frames[].img` must name a\n   captured keyframe. `SCENES=a,b bun run scripts/launch-video/capture.ts`.\n4. **Credits.** Fetch tags and history (`git fetch --tags --unshallow` in a\n   shallow clone), then run\n   `node scripts/launch-video/promo/contributors.mjs .video-work`. Review the\n   printed table: everyone who authored or co-authored a commit in the range\n   appears once, bots and AI co-author trailers excluded. Map unresolved\n   people to logins with `contributors.aliases` (email or name to a login, or\n   `{ login, id }` when the GitHub users API is unavailable) and rerun; anyone\n   still unresolved is credited by name with an initials tile. Never guess an\n   account.\n5. **Work dir.** Besides `playwright-core` (see gotchas), the kit needs the\n   brand font: `cd .video-work && bun add @fontsource-variable/jetbrains-mono`.\n6. **Stills first.** `node scripts/launch-video/promo/render.mjs .video-work\n--stills 2.6,8,14,...` renders review frames in seconds; check every\n   chapter's callouts and captions before the full render. `render.mjs`\n   validates the storyboard and lists any missing keyframes up front.\n7. **Render** with `node scripts/launch-video/promo/render.mjs .video-work`\n   (about 3 minutes for a minute of video) and verify the cut (see\n   \"Verification\"). When `hunk-release` invoked this workflow, return the\n   approved MP4 to that skill for versioned naming and embedding; do not upload\n   or edit the public release without its confirmation gate.\n\n### Chapter reference\n\nTimes are seconds from the start of the chapter's terminal scene; camera,\nhighlight, and label coordinates are fractions of the captured frame.\n\n- `label`, `title` (lines; prefix a word with `*` to color it), `sub`, `cmd`\n  (typed into the title card's command chip), `winTitle`.\n- `frames: [{ img, dur, key?, fade? }]` plays keyframes in order. `key` shows\n  that keycap pressing as the frame appears, so one frame per keypress reads\n  as typing; `fade` crossfades from the previous frame (use it for layout\n  swaps). The `steps()` helper in the 0.24.0 storyboard builds key-walk runs.\n- `cameras: [[t, { x, y, scale }, motionSeconds?]]` must start at time 0.\n- `highlights: [{ t0, t1, x, y, w, h }]` and `labels: [{ t0, t1, x, y, text }]`\n  outline and annotate source regions; keep edges in blank rows and labels off\n  terminal text (see \"Camera and callout composition\").\n- `captions: [[t, html]]` with `<b>` for the accent color.\n- `bodyH` gives short shell output a compact window instead of a full screen.\n\n### Extending the style\n\nKeep release copy out of `engine.js`: it reads everything release-specific\nfrom the storyboard, and its defaults hold only brand constants (tagline,\ninstall command, site). When a release needs something the kit cannot show,\nadd a reusable scene type or storyboard field to the engine and document it\nhere rather than special-casing one release. The brand geometry in the engine\nfollows `website/public/brand/`; update both together.\n\nThe caption storyboard below (`compose.mjs` on the shared stage) remains the\ntool for single-feature demos, PR evidence, and custom explainers.\n\n## Per-video editorial surface\n\nThis section covers the caption storyboard used for single-feature, PR, and\ncustom videos; release promos keep their content in `promo/storyboards/`. The\ncapture machinery is reusable, but the storyboard is editorial content for one\nvideo. Rewrite it to match the video's scope. The checked-in reference is the\n0.24 release roundup, not a frozen release artifact:\n\n- `compose.mjs`: the whole `SHOTS` table; opening, feature, and outro cards;\n  every caption; camera targets; and callout rectangles. A `NEW` badge is a\n  claim about the video at hand, so drop or move it as features age.\n- `capture.ts`: `SCENES` selects from a reusable scene library. With no\n  override it captures only the current storyboard's default scene; update\n  that default whenever the canonical storyboard changes. Hunk-side glue\n  (`launchHunk`, `launchHunkShell`, demo repositories, and the keyboard probe)\n  remains reusable.\n\nReusable machinery lives in `@hunk/term-video` (`packages/term-video/`) —\nextend it there, don't fork it into the scripts: `createKeyframer`,\n`launchApp`/`launchShell`, `createCommandWrapper`, `typeCommand`,\n`ensureKeyboardIsLive`, `makeSceneFilter` (`src/capture.ts`); the unit-tested\nstoryboard planner with the caption/timing semantics (`src/plan.mjs`);\n`composeStoryboard` with font/Chromium resolution and the missing-keyframe\npreflight (`src/compose.mjs`); the stage template (`src/stage.html`); and\n`renderTimeline` for free-form motion pages such as the promo kit\n(`src/timeline.mjs`).\n\n## Environment gotchas\n\nSandbox-specific bullets are marked; each cost real debugging time.\n\n- **Run `capture.ts` with bun from the repo root.** tuistory uses subpath\n  self-imports (`tuistory/pty`) that only resolve inside this repo's\n  `node_modules`; running the script from elsewhere resolves tuistory from\n  bun's global cache and crashes. (Output location is unaffected — it defaults\n  to `<repo>/.video-work/` via `import.meta.url`.)\n- **ghostty-opentui is a transitive dep** (via tuistory) with an exports map:\n  import `\"ghostty-opentui/image\"` (not `.../dist/image.js`), resolved\n  relative to tuistory — `@hunk/term-video/capture`'s `createKeyframer` does\n  the `Bun.resolveSync` dance.\n- **Playwright must match the Chromium it drives.** The portable setup above\n  installs `playwright` and `playwright-core` together, then downloads their\n  matching Chromium build. It works on macOS and Linux and is preferred when\n  bandwidth and browser downloads are available; leave `CHROMIUM_PATH` unset\n  so Playwright uses that managed browser. On Linux, if Chromium reports\n  missing system libraries, run `bunx playwright install-deps chromium` (it\n  may require sudo) before retrying.\n\n  To reuse an existing system, CI, or sandbox Chromium instead, install the\n  `playwright-core` version provided by that environment and set its executable\n  explicitly:\n\n  ```sh\n  cd .video-work\n  bun add playwright-core@<matching-version>\n  cd ..\n  CHROMIUM_PATH=/path/to/chromium node scripts/launch-video/compose.mjs .video-work\n  ```\n\n  Common executable locations include `$(command -v chromium)` or\n  `$(command -v google-chrome)` on Linux and\n  `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` on macOS.\n  In an environment with a preinstalled Playwright toolchain, read that\n  toolchain's `package.json` to get the exact driver version. For example, the\n  Anthropic sandbox exposes it through `/opt/pw-browsers/.links/*`:\n\n  ```sh\n  cat \"$(cat /opt/pw-browsers/.links/* | head -1)/package.json\" | grep '\"version\"'\n  # e.g. \"1.56.1\" -> bun add playwright-core@1.56.1\n  ```\n\n  `compose.mjs` picks its browser as `$CHROMIUM_PATH`, then\n  `/opt/pw-browsers/chromium` when present, then Playwright's managed browser.\n  If a dependency update leaves Playwright asking for an uninstalled browser\n  revision, either rerun `bunx playwright install chromium` in `.video-work/`\n  or set `CHROMIUM_PATH` to a known system browser such as `/usr/bin/chromium`.\n\n- **Give `.video-work/` its own `package.json` before `bun add`.** Without\n  one, bun walks up and installs into the repo's `package.json` — revert with\n  `git checkout package.json bun.lock` if that happens.\n- **Chromium needs `--allow-file-access-from-files`** (already in\n  `compose.mjs`): the stage samples each keyframe through a canvas to\n  color-match the window background, and file:// images taint the canvas\n  without it.\n- **mp4 needs an ffmpeg with libx264.** Sandbox: `apt-get install ffmpeg`\n  (run `apt-get update` first if packages 404). macOS: `brew install ffmpeg`.\n  Verify `ffmpeg -encoders | grep -E 'libx264|libvpx-vp9'` shows both before\n  encoding — playwright's bundled `ffmpeg-*/ffmpeg-linux` only does VP8/WebM\n  and cannot produce the mp4.\n- **Caption font**: JetBrains Mono ships inside ghostty-opentui;\n  `findCaptionFont` in `packages/term-video/src/compose.mjs` searches bun's\n  isolated layout (`node_modules/.bun/node_modules/…`) then a hoisted\n  `node_modules/…`. If it still throws `caption font not found`, locate the\n  file with `find node_modules -name jetbrains-mono-nerd.ttf` and pass it as\n  `fontPath` to `composeStoryboard`.\n\n## Authoring scenes (capture.ts)\n\n- Shared geometry is 140x32 cells rendered at fontSize 16 / dpr 2 → 2688x1536\n  PNGs. Keep every scene at this size so all frames fit one window.\n- Keep time-sensitive fixtures recent and timezone-stable. The canonical\n  history scene derives commit dates from the current UTC day and launches\n  Hunk with `TZ=UTC`, so day groups and relative-age labels remain useful on\n  future runs. Preserve that policy in custom history scenes.\n- Helpers: `createDemoRepo()` and `launchHunkShell()` are hunk-side glue in\n  the script (git repo built from `examples/2-mini-app-refactor`; interactive\n  bash with a real `hunk` command on PATH and a clean `❯` prompt); `snap`,\n  `typeCommand`, `launchApp`/`launchShell`, and `createCommandWrapper` come\n  from `@hunk/term-video/capture`.\n- Always `waitForText` on scene-specific content before the first snap, and\n  call `ensureKeyboardIsLive()` before scripted keypresses — the first key\n  after startup can be dropped (real race, the helper toggles `?` to prove\n  keys land).\n- **Animation = one snap per keypress.** Cursor walks and typing effects are\n  just every `j`/`k`/character captured as its own frame and played back at\n  0.2–0.3s per frame. Prefer this over sparse keyframes: three stills read as\n  a slideshow, per-press frames read as motion.\n- `renderTerminalToImage` auto-trims trailing blank rows, so short outputs\n  (CLI scenes) produce short PNGs — the stage handles this by sampling the\n  image's bottom-left pixel and painting the window body to match.\n- `manifest.json` is a capture-side inventory of the _current run_ only;\n  `compose.mjs` ignores it (frames resolve by name from `SHOTS`), and after a\n  `SCENES=` run it is partial while `frames/` stays cumulative.\n- Demo content that must exist: STML notes come from\n  `examples/9-agent-markup-notes` (launch with `--experimental`), extension\n  scenes from `examples/extensions/` loaded via `--extension <path>` (explicit\n  paths skip the repo trust prompt). The pager pipe is `git diff | hunk pager`\n  — bare `hunk` on piped stdin prints help. Sidebar toggle is `s`; comment\n  draft is `c`, save with Ctrl+S (`\\x13`).\n\n## Storyboard model (compose.mjs)\n\n- `SHOTS` is the whole edit: one entry per shot, `dur` in seconds, played as\n  unique frames + per-frame durations in an ffmpeg concat list (holds cost one\n  frame, so runtime is dominated by transitions, not length).\n- `capKey` is caption identity: the caption slides in only when `capKey`\n  changes, and continuation shots that share a `capKey` without restating\n  `caption` keep the previous caption on screen. Sequences (walks, typing) are\n  generated with `Array.from` spreads.\n- **Sequence lengths must match capture loop bounds**: the open capture's\n  four-step tree walk plus its select and expand presses produce\n  `open-tree-0..5`, consumed by a six-entry `Array.from` spread in `SHOTS`.\n  Change one side and the other breaks — the preflight check names any frame\n  that's missing.\n- `enter: true` fades/scales the surface in — use it for cards and the first\n  terminal shot only.\n- Terminal shots may set normalized `camera: { x, y, scale }` targets and\n  `highlight: { x, y, width, height, label? }` rectangles. `motion` controls\n  the transition length in seconds. The planner interpolates camera changes\n  and outline geometry, while the stage keeps the camera inside the captured\n  image and paints an animated glow around the source-aligned region. Set the\n  same `cameraKey` or `highlightKey` on related keyframes when their normalized\n  coordinates share one capture geometry. Cross-image camera changes cut to\n  their target unless `cameraKey` explicitly permits a pan, and outlines never\n  leak across unrelated images.\n- Caption HTML vocabulary: `<span class=\"badge\">NEW</span>` amber pill,\n  `<span class=\"hl\">` amber highlight, `<span class=\"dim\">` muted. Cards use\n  `badge` / `h1`/`h2` / `sub` / `cmds`+`cmd` / `foot` classes from\n  `packages/term-video/src/stage.html`.\n- Target pacing: money shots hold 3–4s, context shots 2–3s, typing/walk frames\n  0.2–0.6s; keep the total near 60s.\n\n## Camera and callout composition\n\nTreat `camera` and `highlight` as one composition. Both use normalized source\ncoordinates, but the stage projects highlights through the active camera. A\nrectangle that fits within the source can still lose an edge after zooming.\n\nUse this framing process:\n\n1. Start with a full-frame establishing shot, then pan or zoom to one readable\n   subject. Use `motion` around 0.7–0.9s for a deliberate move rather than a\n   cut disguised as animation.\n2. Fit the target plus its outline inside the camera viewport. Leave about 1%\n   of the visible width on each side and at least one terminal row above and\n   below the meaningful content. If that does not fit, reduce `camera.scale`.\n3. Put outline edges in actual blank rows. Mathematical padding can move a\n   border onto a neighboring date heading, status row, or metadata line.\n4. Check both sides of the capture. A zoom that preserves left-aligned subjects\n   can still crop hashes, authors, or status text aligned to the right.\n5. Give irregular states explicit rectangles. A growing commit range that\n   crosses day separators should use measured per-keyframe heights instead of\n   assuming every added item occupies the same vertical distance.\n6. Share `cameraKey` or `highlightKey` only when captures use the same source\n   geometry. This allows pans and outline morphs across related captures;\n   unrelated keyframes cut or fade separately.\n\nThe outline should direct attention, not become the subject. Use one callout at\na time, keep labels short, and remove the outline once the viewer has enough\ncontext to follow the interaction unaided.\n\n## Command and output legibility\n\nTerminal commands and their results must be readable at normal playback size.\nNever rely on a full-width terminal prompt or output region as the only way the\nviewer can understand a command scene; text that looks acceptable in a 1080p\nsource frame is often illegible in an embedded player or social feed.\n\nUse this default treatment:\n\n1. **Present the exact command in large type.** The easiest treatment is a\n   `cmd` card or an oversized editorial callout immediately before the real\n   terminal result. A typing animation may remain for motion, but it does not\n   replace the large command treatment.\n2. **Zoom the real output.** Crop or scale the captured terminal frame around\n   the meaningful result so both the command and the important output lines\n   are comfortably readable. A brief full-window establishing shot is fine,\n   but the result's main hold must use the focused view.\n3. **Keep the evidence real.** Never fabricate terminal output. Large command\n   text may reproduce the exact command as an editorial overlay; any enlarged\n   output must come from the captured PTY frame. If reusable crop/zoom controls\n   are missing, add them to `@hunk/term-video` rather than baking one-off image\n   edits into a storyboard.\n4. **Trim before shrinking.** Prefer fewer relevant lines and a tighter crop\n   over fitting a long transcript into the frame. Split a workflow across\n   multiple focused shots when one crop cannot keep every important line\n   legible.\n\nTreat command legibility as a release gate: if the command or its result cannot\nbe read when the 1920x1080 video is displayed at 50% size, revise the shot.\n\n## Content accuracy (learned the hard way)\n\n- **Verify install commands against reality**, not the README: check\n  `npm view hunkdiff dist-tags`. A prerelease needs `npm i -g hunkdiff@beta`;\n  `brew install hunk` only serves stable (homebrew-core Autobump, lags npm) —\n  omit brew on prerelease cards.\n- **Label demo extensions as examples.** The triage board, CSS palette, and\n  semver views are `examples/extensions/`, not shipped features — caption them\n  with a dimmed `example:` prefix. The real features are the APIs (sidebars,\n  file views, commands, dialogs).\n- Window titles are decorative but must not lie: the shell scenes run bash,\n  so keep their titles generic (`shell — …`) rather than naming a shell the\n  capture doesn't launch.\n- STML requires `--experimental`; say so on the outro card.\n- The video is silent — never imply audio in the video or its announcement copy.\n\n## Verification and delivery\n\n- Eyeball keyframes in `.video-work/frames/` (Read renders PNGs) after\n  capture — especially new scenes — before compositing.\n- Inspect every command scene at 50% display size. Confirm the exact command is\n  shown in large type and the meaningful real output is zoomed tightly enough\n  to read; a readable caption does not compensate for unreadable terminal text.\n- Composite and encode the MP4 first. Inspect it before spending time on the\n  slower VP9/WebM encode; produce both final formats only after the visual pass.\n- After encoding, return to the repository root, set `VIDEO` to the produced\n  MP4, and inspect a settled frame for each callout, a mid-animation point,\n  each new scene, and the outro:\n\n  ```sh\n  VIDEO=.video-work/hunk-feature-demo.mp4 # or .video-work/launch.mp4\n  ffmpeg -y -ss 2 -i \"$VIDEO\" -frames:v 1 -update 1 .video-work/check.png\n  ffprobe -v error -show_entries format=duration,size \\\n    -show_entries stream=codec_name,width,height,r_frame_rate \\\n    -of default=noprint_wrappers=1 \"$VIDEO\"\n  ```\n\n  Captions must persist through extracted animation frames. Every callout must\n  show all four borders, keep its label clear of terminal text, leave visible\n  padding around its subject, and avoid clipping right-aligned metadata.\n\n- Compare the storyboard planner's reported duration with `ffprobe`; they\n  should agree within one frame. The concat manifest declares the PNG input\n  rate so 30 fps animation timestamps stay exact. Investigate larger drift\n  before delivery.\n\n- Outputs stay under `.video-work/`: the full-release recipe creates\n  `launch.mp4`/`launch.webm`, while the single-feature recipe above creates\n  `hunk-feature-demo.mp4`/`hunk-feature-demo.webm`. `.video-work/` is\n  gitignored — never commit the video or its frames. For a standalone video\n  request, send both files to the user directly (mp4: social/Slack; webm: web\n  embeds), report duration and file sizes, and flag if the mp4 exceeds ~10 MB\n  (Slack) or ~15 MB (X). When invoked by `hunk-release`, hand both files back\n  to that workflow instead; it owns versioned filenames, GitHub's\n  user-attachment limit, public embedding, and inline-player verification.\n  Copy them elsewhere only if the user names a destination.\n","skills/hunk-release/SKILL.md":"---\nname: hunk-release\ndescription: Prepares, publishes, verifies, and curates Hunk releases. Use for release metadata, benchmarks, tags, publishing, release videos, backports, or recovery.\nmetadata:\n  internal: true\n---\n\n# Hunk release workflow\n\nMaintainer-focused and source-checkout only. The tag workflow publishes `hunkdiff` plus five platform packages, attests the binary archives, and creates the GitHub release.\n\n## Safety\n\n- Ask when the version, release branch, previous tag, or channel is ambiguous.\n- Get explicit confirmation before pushing a tag, triggering publication, or editing a public release.\n- Never reuse an npm version or move a tag after publication.\n- Never bypass a benchmark regression without an approved, recorded reason.\n- Never retry a partial publish before inventorying every package and artifact.\n\n## 1. Confirm the release\n\nRecord the version, tag, branch, previous tag, and expected channel:\n\n- newer stable: npm `latest` and GitHub Latest;\n- prerelease: npm `beta`, not GitHub Latest;\n- older-series backport: npm `backport-X.Y`, leaving both latest pointers unchanged.\n\nFor backports, include only commits present between the previous tag and the release tip on that maintenance branch. Inspect `.changeset/pre.json` before changing prerelease state.\n\n## 2. Prepare\n\nStart from a clean, current release branch:\n\n```sh\ngit status --short --branch\ngit fetch origin --tags --prune\ngit tag --sort=-version:refname | head -10\nbun install --frozen-lockfile\nbun run changeset:status\n```\n\nRead the pending Changesets, then generate metadata and the published release notes:\n\n```sh\nbun run release:version\nbun run generate:changelog\ngit diff -- package.json bun.lock packages CHANGELOG.md .changeset website\n```\n\nVerify the intended versions, consumed Changesets, and new changelog section. Do not force a bump by hand-editing generated versions.\n\n`generate:changelog` projects `CHANGELOG.md` into `hunk.dev/changelog`. Never hand-edit its output under `website/src/content/docs/changelog/`, `website/public/changelog/`, `website/releases/dates.json`, or `website/releases/latest.json`. For a minor or major release, add the hand-authored parts to `website/releases/notes.json` under the `major.minor` key and regenerate:\n\n- `summary` — one sentence; replaces the changelog's `### Highlights` lead paragraph on the page.\n- `tagline` — short phrase for the landing-page release ribbon.\n- `links` — docs pages the highlights describe, rendered as \"Related documentation\".\n- `video` — `mp4` plus optional `webm`, `poster`, `duration`, and `title`, filled in at step 5.\n\nThe release tag does not exist yet at this point. A stable version is rendered as `Unreleased` without an install command, while an undated prerelease remains off the site entirely; the tag date is picked up by the next generation. Recorded dates in `website/releases/dates.json` are never recomputed, which is what lets `check:changelog` gate CI without Git tags.\n\nGenerate and compare the committed release benchmark:\n\n```sh\nbun run bench:release\nbun run bench:release:compare\n```\n\nA material regression blocks the release unless the user approves an `acceptedRegressions` entry following `benchmarks/release/README.md`.\n\nRun the validation required by `AGENTS.md`, plus the release packaging checks:\n\n```sh\nbun run check:docs\nbun run check:changelog\nbun run check:pack\nbun run build:prebuilt:npm\nbun run check:prebuilt-pack\nbun run smoke:prebuilt-install\n```\n\nCommit the generated metadata and `benchmarks/release/bench-X.Y.Z.json`, then follow normal review\npolicy. The Firecracker evidence must come from that reviewed release tip, not the pre-generation\ncommit. Push the reviewed tip before using the manual workflow.\n\nRun the full Firecracker install compatibility suite once from the clean reviewed release tip on a\nLinux x64 host with working KVM, either locally or through the manually dispatched\n`install-vm.yml` workflow:\n\n```sh\nset -euo pipefail\nmkdir -p tmp/install-vm/runs\nresult_dir=$(mktemp -d tmp/install-vm/runs/release-XXXXXXXX)\nbun run test:install-vm -- --output \"$result_dir\"\nbun run ./test/cli/install-vm/validate-release-result.ts \"$result_dir/result.json\"\n```\n\nThe explicit output directory prevents a failed invocation from falling back to stale evidence. The\nvalidator requires the complete checked-in scenario manifest, passing statuses, and the current\ncheckout's source identity. A skipped result does not satisfy release validation. For the manual\nworkflow, dispatch the full suite from the reviewed tip, download its `result.json` beneath the\nignored `tmp/install-vm/` directory of a checkout at that exact tip, and run the validator there; a\ngreen job alone is insufficient because unsupported runners may use the intentional skip path.\nFirecracker validates Linux x64 packaging behavior, while the existing native release jobs remain\nresponsible for macOS, Windows, and other architectures. Wait for required CI before continuing.\n\n## 3. Tag and publish\n\nImmediately before tagging, fail unless the reviewed branch tip is clean and matches its remote:\n\n```sh\nset -euo pipefail\nbranch=$(git branch --show-current)\ntest -n \"$branch\"\ngit fetch origin \"$branch\" --tags\ntest \"$(git rev-parse HEAD)\" = \"$(git rev-parse \"origin/$branch\")\"\nif [ -n \"$(git status --porcelain --untracked-files=all)\" ]; then\n  git status --short\n  exit 1\nfi\ngit log -1 --format='%H %s'\n```\n\nPresent the tag, commit, benchmark result, validation, and release highlights. After explicit confirmation:\n\n```sh\nversion=X.Y.Z\ntag=\"v$version\"\ngit tag -a \"$tag\" -m \"$tag\"\ngit push origin \"$tag\"\n```\n\nThe tag push starts `.github/workflows/release-prebuilt-npm.yml`. Find only the run for that tagged commit and propagate failure:\n\n```sh\nrelease_sha=$(git rev-parse \"$tag^{}\")\nrun_id=$(\n  gh run list \\\n    --workflow release-prebuilt-npm.yml \\\n    --event push \\\n    --commit \"$release_sha\" \\\n    --limit 1 \\\n    --json databaseId \\\n    --jq '.[0].databaseId // empty'\n)\ntest -n \"$run_id\"\ngh run view \"$run_id\" --json url,status,conclusion\ngh run watch \"$run_id\" --exit-status\n```\n\nIf lookup is empty, repeat it after GitHub registers the run; never select an unrelated run. Success requires the benchmark, all builds, staging smoke test, npm publish, attestation, and GitHub release jobs.\n\n## 4. Verify publication\n\nCheck the meta-package, platform packages, dist-tags, release body, and five binary archives:\n\n```sh\nnpm view \"hunkdiff@$version\" version\nfor package in \\\n  hunkdiff-darwin-arm64 hunkdiff-darwin-x64 \\\n  hunkdiff-linux-arm64 hunkdiff-linux-x64 \\\n  hunkdiff-windows-x64; do\n  npm view \"$package@$version\" version\ndone\nnpm view hunkdiff dist-tags --json\ngh release view \"$tag\" --json tagName,name,isPrerelease,url,assets,body\n```\n\nStop if versions, channel, tag, archives, or attestations disagree.\n\nThen record the tag date and publish the release notes. The published page is what the update notice and the GitHub release body point at, so it is part of the release, not follow-up work:\n\n```sh\nbun run generate:changelog\nbun run generate:og\ngit diff --stat -- website\n```\n\nThis backfills the new tag's date, publishes its notes, and redraws the social cards whose contents changed. Stable releases also add the install command and move the landing-page ribbon; prereleases publish a clearly labeled series without advancing either stable surface. Commit it to `main` — the diff should only touch `website/releases/`, `website/src/content/docs/changelog/`, `website/public/changelog/rss.xml`, and the redrawn cards under `website/public/changelog/og/`. Run this for backports too: they publish into their older series without advancing the latest stable release.\n\nPrereleases publish to `hunk.dev/changelog` after their tag exists. A beta contributes its series page, exact version anchor, index row, feed item, and social card, while the stable latest marker, landing ribbon, and default install instructions continue to name the newest stable release.\n\n## 5. Add the release video and final notes\n\nOnly after publication verifies, create a detached worktree at the released tag and follow `skills/hunk-launch-video/SKILL.md`'s full-release recipe:\n\n```sh\ngit worktree add --detach \"../hunk-release-video-$version\" \"$tag\"\n```\n\nThat skill owns capture, encoding, and media checks; release videos use its promo style kit, with this release's content in a new `scripts/launch-video/promo/storyboards/<version>.mjs`. Keep generated media out of Git and Git LFS. Commit the release's storyboard file and any capture scenes it needs only with separate approval, so the next release can start from them.\n\nPublish the video so the release page can carry it, then record it in `website/releases/notes.json` under the series' `video` key and regenerate. Host it where the site can serve it rather than committing it: the 1080p master stays out of Git and Git LFS.\n\nDraft the final body from the released changelog and actual branch diff. Replace GitHub's generated PR inventory with editorial notes that help someone decide whether to install the release:\n\n- Open with one short paragraph describing the release's product theme and user impact.\n- Group a small number of meaningful changes under descriptive headings. Explain what users can now do; do not restate commit titles or reproduce the changelog.\n- Include both upgrade and npm installation instructions using the exact release: `hunk update <version>` for npm- and curl-managed installs and `npm install -g hunkdiff@<version>` for npm installs or first-time npm users. Homebrew cannot select an exact version: tell its users to run plain `hunk update` only after the Homebrew readiness gate below passes, or label Homebrew propagation as pending.\n- Add a clearly labeled compatibility section for runtime requirements, changed CLI interpretation, extension API variants, migrations, or other upgrade risks.\n- Add a **Community contributors** section that names every external contributor in the release, links each relevant PR, and briefly describes their contribution. Derive this from the actual release diff; do not limit acknowledgment to first-time contributors and do not bury contributors in an autogenerated list.\n- Preserve a complete PR inventory inside a collapsed GitHub `<details>` block after the editorial sections. Use one concise bullet per merged PR with its author and link, include maintenance work there, and verify the list against the actual previous-tag comparison. The collapsed inventory is for completeness; it does not replace the curated highlights or contributor acknowledgments.\n- Link the comparison from the actual previous tag and point every release at its `hunk.dev` series page, which carries the full notes, video, and docs links.\n- Keep maintenance-only PRs out of the editorial highlights unless they materially affect installation, compatibility, security, or performance.\n\nUse this shape rather than a flat `What's Changed` list:\n\n````md\n## <release name or product theme>\n\n<one short release summary>\n\n```sh\nhunk update <version>\nnpm install -g hunkdiff@<version>\n```\n\nhttps://github.com/user-attachments/assets/<video-id>\n\n### <user-facing theme>\n\n<what changed, why it matters, and links to the defining PRs>\n\n### Compatibility notes\n\n- <upgrade requirement or changed behavior>\n\n### Community contributors\n\n- [@contributor](https://github.com/contributor) <concise contribution>. [#123](PR URL)\n\n<details>\n<summary>All merged pull requests</summary>\n\n- <concise PR title> by @author in [#123](PR URL)\n\n</details>\n\n**Release notes**: https://hunk.dev/changelog/<major.minor>/\n**Full changelog**: https://github.com/modem-dev/hunk/compare/<previous-tag>...<new-tag>\n````\n\nPresent the local MP4 and draft notes for explicit confirmation. Then attach the H.264 MP4 in GitHub's release editor, replace the placeholder with its generated user-attachment URL, and apply the reviewed body:\n\n```sh\ngh release edit \"$tag\" --notes-file /tmp/hunk-release-notes.md\n```\n\nOpen the public release in a browser and verify inline playback, final notes, and the unchanged five immutable binary archives.\n\n## 6. Distribution channels\n\nOnly stable releases that advance `latest` should propagate to Homebrew and mise. Let Homebrew Autobump update `Homebrew/homebrew-core`; use `brew bump-formula-pr` only if maintainers request it or Autobump stalls.\n\nTreat Homebrew availability as an announcement check only. Never delay or fail the tag, npm publication, GitHub release, changelog, release notes, video, or other release completion work because Homebrew is pending. Homebrew detects the published release on its own schedule and merges a separate formula PR. Immediately before an external promotional announcement that names Homebrew or recommends `hunk update` without qualifying the install method, read the live formula API and require its stable version to match:\n\n```sh\nversion=X.Y.Z\nformula_version=$(\n  curl -fsSL https://formulae.brew.sh/api/formula/hunk.json |\n    bun -e 'console.log(JSON.parse(await Bun.stdin.text()).versions.stable)'\n)\nprintf 'Homebrew formula: %s (release: %s)\\n' \"$formula_version\" \"$version\"\ntest \"$formula_version\" = \"$version\"\n```\n\nIf the check fails, still mark the software release complete, but hold the external promotional announcement unless it explicitly says that Homebrew is still propagating and offers npm or the install script meanwhile. Check for an open or merged Autobump PR:\n\n```sh\ngh api --method GET /search/issues \\\n  -f q=\"repo:Homebrew/homebrew-core is:pr in:title \\\"hunk $version\\\"\" \\\n  --jq '.items[] | [.html_url, .state, .title] | @tsv'\n```\n\nDetermine the next expected Autobump scan from Homebrew's live workflow rather than relying on a remembered cadence. This command prints the current cron expression and, while Homebrew retains its present three-hour schedule, the next nominal UTC scan. Scheduled GitHub Actions can start late, so report this as an estimate rather than an availability promise:\n\n```sh\nautobump_workflow=$(\n  gh api repos/Homebrew/homebrew-core/contents/.github/workflows/autobump.yml \\\n    --jq .content | base64 --decode\n)\nautobump_cron=$(\n  printf '%s\\n' \"$autobump_workflow\" |\n    awk -F'\"' '/^[[:space:]]*- cron:/ { print $2; exit }'\n)\nprintf 'Homebrew Autobump schedule: %s\\n' \"$autobump_cron\"\nAUTOBUMP_CRON=\"$autobump_cron\" bun -e '\n  const cron = process.env.AUTOBUMP_CRON;\n  if (cron !== \"45 1-23/3 * * *\") {\n    console.error(\"Homebrew changed its Autobump schedule; inspect the workflow before announcing.\");\n    process.exit(1);\n  }\n  const next = new Date();\n  next.setUTCSeconds(0, 0);\n  do next.setUTCMinutes(next.getUTCMinutes() + 1);\n  while (next.getUTCMinutes() !== 45 || (next.getUTCHours() - 1) % 3 !== 0);\n  console.log(`Next nominal Homebrew Autobump scan: ${next.toISOString()}`);\n'\n```\n\nAfter the formula version matches, run `brew update`, verify `brew info hunk` reports the release, and only then describe Homebrew as available. Verify mise independently against fresh registry data:\n\n```sh\nMISE_AQUA_BAKED_REGISTRY=false mise latest hunk\n```\n\nUse `mise cache clear` only when cached registry data is stale. Do not claim Homebrew or mise support for prereleases or older-series backports.\n\n## Failure invariants\n\n- Before tag push: fix, regenerate, validate, and request confirmation again.\n- After any npm publication: keep the version and tag; inventory all six packages before recovery.\n- Keep a verified software release intact when video or notes fail; retry only the approved media/edit step.\n","website/src/content/docs/docs/agents/agent-context-and-stml.md":"---\ntitle: Agent context and STML\ndescription: Load review notes from a JSON sidecar and opt into experimental rich note markup.\n---\n\n[Live agent comments](/docs/agents/comments-and-annotations/) are the recommended workflow. A sidecar file is useful when the notes already exist before Hunk starts, or need to travel with a patch.\n\n## Load a JSON sidecar\n\n```bash\nhunk diff --agent-context notes.json\nhunk patch change.patch --agent-context notes.json\n```\n\nUse `--agent-context -` to read the sidecar from stdin. With `--watch`, Hunk also reloads when the sidecar file changes, so an agent can rewrite the file and the open review updates; a stdin sidecar cannot be watched.\n\nSidecar annotations are agent notes, so they start hidden. Press `a` to show them, or launch with `--agent-notes`.\n\n## Sidecar format\n\n```json\n{\n  \"version\": 1,\n  \"files\": [\n    {\n      \"path\": \"src/search.ts\",\n      \"summary\": \"Scores and sorts matches instead of returning the first substring hit.\",\n      \"annotations\": [\n        {\n          \"newRange\": [15, 35],\n          \"summary\": \"Prefix and exact matches now outrank weaker substring hits.\",\n          \"rationale\": \"The old behavior made every match look equally good.\",\n          \"author\": \"sonnet\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nTop level:\n\n| Field     | Type   | Notes                                                                            |\n| --------- | ------ | -------------------------------------------------------------------------------- |\n| `version` | number | Optional; defaults to `1`.                                                       |\n| `summary` | string | Optional changeset summary. Extensions can read it; the TUI does not display it. |\n| `files`   | array  | One entry per file you want to describe.                                         |\n\nEach `files[]` entry:\n\n| Field         | Type   | Notes                                                                         |\n| ------------- | ------ | ----------------------------------------------------------------------------- |\n| `path`        | string | Required. Matches the file's current path, or its previous path for a rename. |\n| `summary`     | string | Optional. Used by the file filter, not shown as a card.                       |\n| `annotations` | array  | Optional notes for this file.                                                 |\n\nEach `annotations[]` entry:\n\n| Field                                             | Type           | Notes                                                                                                                  |\n| ------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- |\n| `summary`                                         | string         | Required. The note's headline.                                                                                         |\n| `newRange` / `oldRange`                           | `[start, end]` | Optional 1-based, inclusive line range on the new or old side. A note with no range attaches to the file's first hunk. |\n| `rationale`                                       | string         | Optional longer explanation shown under the summary.                                                                   |\n| `author`                                          | string         | Optional label for the card, such as `sonnet note`.                                                                    |\n| `markup`                                          | string         | Optional [STML](#opt-into-stml) body. Ignored unless the session launched with `--experimental`.                       |\n| `id`, `tags`, `confidence`, `source`, `createdAt` | various        | Optional metadata kept with the note. `confidence` is `low`, `medium`, or `high`.                                      |\n\nFiles listed in the sidecar appear first in the review, in sidecar order; other changed files follow in their usual order. Hunk rejects a sidecar with a missing `summary` or `path`, or a range whose end is before its start.\n\nKeep it concise: short file summaries and only rationale that improves the review. A fuller example lives at [`examples/3-agent-review-demo/agent-context.json`](https://github.com/modem-dev/hunk/blob/main/examples/3-agent-review-demo/agent-context.json).\n\n## Opt into STML\n\nSTML is experimental markup for richer note bodies: boxes, rows, badges, lists, and code. It is off by default and only the launch flag turns it on:\n\n```bash\nhunk --experimental diff --agent-context notes.json\n```\n\nA reload cannot turn STML on later, and there is no config key for it. Every note still needs a plain `summary`, which is the fallback whenever markup is unavailable. Without `--experimental`, a sidecar's `markup` is ignored and a live `--markup` comment is rejected.\n\nBefore sending markup, an agent should check support and width:\n\n```bash\nhunk session context --repo . --json\nhunk markup guide\nhunk markup render - --width <reported-noteMarkupWidth>\n```\n\nOnly send `--markup` when `experimentalFeatures` includes `stml`. `hunk markup guide` prints the tag reference and copy-paste patterns. Keep colors symbolic and markup compact so notes stay close to their code.\n","website/src/content/docs/docs/agents/comments-and-annotations.md":"---\ntitle: Comments and annotations\ndescription: Attach human or agent review notes to the code and navigate them in context.\n---\n\nNotes are hunk-specific and render beside the rows they explain. Hunk keeps them in the review flow rather than in a separate comments screen.\n\n## Kinds of notes\n\n| Term                   | What it is                                                                                           |\n| ---------------------- | ---------------------------------------------------------------------------------------------------- |\n| **Note**               | Any card shown beside the code.                                                                      |\n| **Agent comment**      | A note an agent adds to a live window with `hunk session comment`. This is the recommended path.     |\n| **Sidecar annotation** | A note loaded at launch from an [`--agent-context` JSON file](/docs/agents/agent-context-and-stml/). |\n| **Your note**          | A note you write in Hunk with `c`.                                                                   |\n| **Highlight**          | A temporary mark on a character range. It draws attention but is not a note.                         |\n\nAgent comments and sidecar annotations are both **agent notes**. They start hidden: press `a` to show or hide them, or launch with `--agent-notes`. Your own notes are always visible.\n\nNotes live in the Hunk window. Closing the window discards them.\n\n## Add one agent comment\n\n```bash\nhunk session comment add \\\n  --repo . \\\n  --file README.md \\\n  --new-line 103 \\\n  --summary \"Tighten this wording\" \\\n  --focus\n```\n\nChoose exactly one `--old-line` or `--new-line` target. `--focus` moves the user's viewport to the new note and turns agent notes on; use it for the first note in a session and when a note should actively steer the review.\n\nOptional fields:\n\n- `--rationale <text>`: a longer explanation shown under the summary.\n- `--author <name>`: the label on the note card, such as `Claude note` instead of `Agent note`.\n- `--markup <stml>`: an experimental rich body; see [Agent context and STML](/docs/agents/agent-context-and-stml/#opt-into-stml).\n\n## Apply a batch\n\n```bash\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' \\\n  | hunk session comment apply --repo . --stdin --focus\n```\n\nEach item needs a `summary` and either:\n\n- `filePath` with exactly one target: `hunk`, `hunkNumber`, `oldLine`, or `newLine`, or\n- `replyTo` by itself, to reply to an existing note.\n\nItems also accept `rationale`, `author`, and `markup`. Hunk validates the complete batch before changing the live session. `--focus` jumps to the first note in the batch.\n\n## Reply to a note\n\nA reply inherits the parent note's file and line:\n\n```bash\nhunk session comment add --repo . --reply-to user:123 --summary \"Addressed in the latest revision\"\n```\n\n## Hand your notes to the agent\n\nYour notes are a way to ask the agent questions in context:\n\n1. Press `c` on a line and write the question.\n2. Ask your agent to read your notes. It runs `hunk session comment list --repo . --type user --json` or `hunk session review --repo . --include-notes --json`.\n3. The agent answers with `--reply-to <note-id>`, and the reply appears under your note.\n\nAgents can remove your notes during cleanup but cannot create or edit them.\n\n## Inspect and clean up\n\n```bash\nhunk session comment list --repo .\nhunk session comment list --repo . --type all\nhunk session comment rm --repo . <comment-id>\nhunk session comment clear --repo . --file README.md --yes\n```\n\n`comment list` shows agent comments by default. `--type` selects other notes:\n\n| `--type`         | Lists                                         |\n| ---------------- | --------------------------------------------- |\n| `live` (default) | Comments added through `hunk session comment` |\n| `user`           | Your notes                                    |\n| `ai`             | Sidecar annotations                           |\n| `agent`          | Agent-authored notes                          |\n| `all`            | Every note                                    |\n\n`comment clear` removes agent comments. Add `--all` (or its synonym `--include-user`) to clear your notes too. Clearing always requires `--yes`.\n\n## Add your own note\n\nIn the TUI, move to a line and press `c`, or click an add-note affordance. Press `Ctrl+S` to save or Escape to cancel. Human and agent notes are labeled by source. Press `{` and `}` to move through annotated hunks across the review stream.\n","website/src/content/docs/docs/agents/live-session-control.md":"---\ntitle: Live session control\ndescription: Inspect, target, navigate, and reload Hunk windows through the local session broker.\n---\n\nEach normal Hunk TUI registers with one loopback daemon. `hunk session ...` finds a registered window and sends it review actions.\n\n## Find the session\n\n```bash\nhunk session list\nhunk session get --repo .\nhunk session context --repo .\n```\n\nUse `--repo <path>` for normal worktrees. Use an explicit session ID when multiple windows share a repository.\n\n## Inspect without overloading context\n\n```bash\nhunk session review --repo . --json\n```\n\nThis returns files and hunks. Add flags only when required:\n\n```bash\nhunk session review --repo . --include-notes --json\nhunk session review --repo . --include-patch --json\n```\n\n## Navigate the visible window\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 . --next-comment\nhunk session navigate --repo . --comment <comment-id>\n```\n\nHunk numbers are 1-based. Absolute navigation needs a file and exactly one hunk, old-line, or new-line target. `--next-comment` and `--prev-comment` move between annotated hunks; `--comment` jumps to one note by the id that `comment list --json` returns.\n\n## Reload the review\n\nAlways place `--` before the nested Hunk command:\n\n```bash\nhunk session reload --repo . -- diff\nhunk session reload --repo . -- show HEAD~1 -- README.md\n```\n\nAdvanced reloads can target the live window by `--session-path` and load from a separate `--source` directory. Prefer `--repo` until those roles genuinely need to differ.\n\n## Security and access\n\nThe daemon listens on `127.0.0.1:47657` and refuses non-loopback addresses. Session commands authenticate with an owner-private credential that Hunk discovers automatically, so only your user account can control your windows; there are no credential flags to pass. The daemon starts with the first Hunk window and shuts down about a minute after the last one closes.\n\n| Variable                         | Effect                                                                                     |\n| -------------------------------- | ------------------------------------------------------------------------------------------ |\n| `HUNK_MCP_HOST`, `HUNK_MCP_PORT` | Move the daemon to another loopback host or port, for example to avoid a port conflict.    |\n| `HUNK_MCP_DISABLE=1`             | Start the TUI without registering a session.                                               |\n| `HUNK_MCP_UNSAFE_ALLOW_REMOTE=1` | Allow a non-loopback host. Anyone who can reach the port can attempt to connect; avoid it. |\n\nIf a visible Hunk window does not appear in `session list`, an agent sandbox may be blocking loopback access. Grant the agent local network access and retry; don't expose the daemon remotely. `hunk daemon serve` starts the daemon in the foreground for debugging.\n\n## After upgrading Hunk\n\nA daemon from the previous build keeps running while any older Hunk window is open. Until it is replaced:\n\n- a window from the new build shows a status-bar notice naming both builds, and\n- every `hunk session` command fails with a `daemon-build-mismatch` error (reported in-band under `--json`).\n\nTo fix it:\n\n1. Run `hunk daemon status` to see the daemon's build and its attached windows.\n2. Run `hunk daemon restart` and confirm. Windows from the new build reattach on their own.\n3. Relaunch any windows from the old build. Their notes are lost.\n\nAgents should report the mismatch and ask before restarting the daemon.\n","website/src/content/docs/docs/agents/review-skill.md":"---\ntitle: Hunk review skill\ndescription: Install the versioned skill that teaches coding agents Hunk's live review commands.\n---\n\nEvery Hunk installation bundles a `hunk-review` skill. It teaches agents how to find a live session, inspect the review efficiently, navigate, reload, and leave notes, and it tells them not to launch the interactive TUI themselves.\n\n## Install it\n\nInstall the skill with the `skills` CLI:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-review\n```\n\n`-g` installs the skill for your user instead of one project. Add `-a <agent>` (for example `-a claude-code`) to install it for a single agent.\n\nThe installed file is a pointer: it tells the agent to run `hunk skill show hunk-review` and follow the output. The instructions therefore come from the Hunk binary on your `PATH` and stay in step with it after every upgrade; you don't need to reinstall the skill.\n\n## Load it without the skills CLI\n\n| Agent setup                             | Command                                               |\n| --------------------------------------- | ----------------------------------------------------- |\n| Loads instructions on demand            | `hunk skill show` prints the skill to stdout.         |\n| Loads or symlinks skill files from disk | `hunk skill path` prints the bundled file's path.     |\n| Can only fetch URLs                     | Use the [published copy](/docs/hunk-review-skill.md). |\n\nThe published copy tracks the newest Hunk release, so prefer `hunk skill show` when your installed Hunk might be older.\n\n## Other bundled skills\n\n`hunk-extensions` teaches agents to build [extensions](/docs/extend/extensions/) against the `hunkdiff/extension` API:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-extensions\nhunk skill show hunk-extensions\n```\n\nFor the human side of the workflow, start with [Review with an agent](/docs/agents/review-with-an-agent/).\n","website/src/content/docs/docs/agents/review-with-an-agent.md":"---\ntitle: Review with an agent\ndescription: Let a coding agent inspect and guide a live Hunk review without giving up the human review UI.\n---\n\nThe Hunk window stays with you. Your agent uses non-interactive `hunk session` commands from another terminal to inspect the same review, navigate it, and leave inline notes.\n\n## Install the review skill\n\nThe `hunk-review` skill teaches your agent the session commands. Install it once with the `skills` CLI:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-review\n```\n\nThe installed skill is a small pointer that runs `hunk skill show`, so the instructions your agent reads always match the Hunk version on your `PATH`. Add `-a <agent>` (for example `-a claude-code`) to install it for a single agent.\n\nIf your agent has no skill system, ask it to run `hunk skill show` and follow the output. See [Hunk review skill](/docs/agents/review-skill/) for other ways to load it.\n\n## Start the review\n\n```bash\nhunk diff\n```\n\nKeep that window open. Normal Hunk sessions register with a local loopback daemon so the session CLI can find them.\n\n## Ask the agent to review\n\nIn your agent, ask for the review:\n\n```text\nUse the hunk-review skill to walk me through the changes in my open Hunk session.\n```\n\nThe skill tells agents not to launch the interactive TUI themselves. A typical agent flow is:\n\n```bash\nhunk session list\nhunk session get --repo .\nhunk session review --repo . --json\nhunk session navigate --repo . --file src/App.tsx --hunk 2\nhunk session comment add --repo . --file src/App.tsx --new-line 42 --summary \"Check this boundary\" --focus\n```\n\n`review --json` exposes structure without forcing the full patch into agent context. The agent should request `--include-patch` only when it actually needs raw unified diff text.\n\n## See the agent's notes\n\nAgent notes are hidden until you turn them on. Press `a` to show or hide them, or launch with `--agent-notes` (or set `agent_notes = true` in your [config](/docs/configure/configuration/)) to show them from the start. When the agent adds a note with `--focus`, Hunk jumps to it and turns agent notes on for you.\n\n![A Hunk review with agent rationale rendered directly beside the annotated diff hunk](/docs/images/agent-comments.webp)\n\nAgent notes stay attached to the code they explain. Press `{` and `}` to move between annotated hunks while keeping the full changeset visible.\n\n## Keep control\n\nThe agent can move the visible selection and add notes, but you stay in the review stream and can navigate normally. Ask it to summarize when finished, then walk the annotated hunks with `{` and `}`.\n\nTo hand questions back to the agent, leave your own notes with `c`; see [Comments and annotations](/docs/agents/comments-and-annotations/#hand-your-notes-to-the-agent).\n\n## Give the agent the docs\n\nThese docs are published as plain Markdown so an agent can read them without scraping HTML:\n\n- [/llms.txt](https://hunk.dev/llms.txt): an index of every page, for pulling only what is needed.\n- [/llms-small.txt](https://hunk.dev/llms-small.txt): a compact corpus for tight context budgets.\n- [/llms-full.txt](https://hunk.dev/llms-full.txt): every page and the full changelog in one file.\n\nAny docs page URL also returns its Markdown source with `.md` appended, so `https://hunk.dev/docs/reference/cli.md` is the CLI reference as the agent should read it.\n"},"files":{".agents/skills/hunk-extensions/SKILL.md":"---\nname: hunk-extensions\ndescription: Maps the `hunkdiff/extension` authoring surface for Hunk, the terminal diff viewer — hiding or reordering reviewed files, docked panes, alternate file views, commands and key bindings, dialogs, workspace writes, themes, syntax languages, VCS backends, lifecycle events. Use when writing, debugging, or installing a Hunk extension, or when a request asks Hunk itself to behave differently. Not for reviewing a diff in a live session — that is hunk-review.\n---\n\n<!-- generated by `bun run generate:skill` -->\n\n# hunk-extensions\n\nThis file is a pointer. The instructions live in the installed `hunk` CLI so they always match its version.\n\nBefore anything else, load them and follow them:\n\n```bash\nhunk skill show hunk-extensions\n```\n\nIf `hunk` is not on PATH, ask the user to install Hunk first: https://hunk.dev/docs/start/install/\n",".agents/skills/hunk-review/SKILL.md":"---\nname: hunk-review\ndescription: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files, hunks, and exact lines, reloads session contents, adds inline review comments, and paints attention marks on character ranges. Use when the user has a Hunk session running or wants to review diffs interactively.\n---\n\n<!-- generated by `bun run generate:skill` -->\n\n# hunk-review\n\nThis file is a pointer. The instructions live in the installed `hunk` CLI so they always match its version.\n\nBefore anything else, load them and follow them:\n\n```bash\nhunk skill show hunk-review\n```\n\nIf `hunk` is not on PATH, ask the user to install Hunk first: https://hunk.dev/docs/start/install/\n","AGENTS.md":"# hunk agent notes\n\n## purpose\n\n- Terminal-first diff viewer for understanding coding-agent changesets.\n- Product target is \"modern desktop diff tool in a terminal\", not a pager-style TUI.\n\n## architecture\n\n### workspace map\n\n```text\npackages/hunk/                 published CLI, application, and public extension/OpenTUI facades\npackages/hunk-vcs/             private dependency-bottom VCS helpers\npackages/hunk-{git,jj,sapling}/ private bundled VCS providers\npackages/hunk-gh/              private bundled GitHub review extension\npackages/session-broker-core/  low-level broker protocol and state\npackages/session-broker/       runtime-neutral broker, daemon, auth, and connection lifecycle\npackages/session-broker-{bun,node}/ runtime listener adapters\npackages/term-video/           private terminal capture tooling\nwebsite/                       product/documentation site, not the browser review client\n```\n\n### application flow\n\n```text\nmain.tsx -> app/startup.ts -> app/cli.ts\n  -> headless command/history plan returned to main.tsx, or\n  -> app plan: extensionBootstrap.ts -> sessionBootstrap.ts -> normalized Changeset / DiffFile[]\n     -> main.tsx lazy-loads runInteractiveApp.tsx -> HunkSessionHost -> AppHost -> App\n     -> pane/diff planning -> Pierre-backed terminal rows\n```\n\n### shared review seam\n\nTerminal, agent/session, broker/protocol, extension, and HTTP/SSE consumers share review semantics.\nThe browser client and UI remain planned. Do not recreate semantic review behavior in a surface:\n\n```text\nDiffFile[] -> projectReviewDocument -> ReviewDocumentV1 -> ReviewStore\nReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer -> surface projection\n```\n\n- **Model:** `packages/hunk/src/core/review/{types,document,identity}.ts` owns the ordered, JSON-safe document.\n  File order is review/sidebar order; use `key` (referenced as `fileKey` elsewhere),\n  `contentIdentity`, and `sourceIdentity` (cached source text additionally requires\n  `sourceAttested`) — not runtime IDs or indexes — across reloads/surfaces.\n- **Shared derivations:** `geometry.ts`, `expansion.ts`, `anchors.ts`, `stml.ts`, and\n  `contentManifest.ts` own ranges, gaps, source splitting, note targets/ownership, tag roles, and\n  parity manifests. Consume them; never re-derive those facts in a renderer.\n- **State:** `state.ts` is semantic state; `actions.ts` transitions; `reducer.ts` pure/no-I/O;\n  `selectors.ts` shared policies; `store.ts` synchronous observable storage. New cross-surface\n  operations start as intents. Callers supply mutable-note IDs/timestamps; core derives identities.\n- **Surfaces/publishers:** `useTerminalReview.ts` is the TUI adapter and\n  `reviewNoteMapping.ts` is terminal-only. Rows, measurement, scrolling, layout, themes, DOM\n  mechanics, and source I/O stay local. `useHunkSessionBridge.ts` publishes the current terminal\n  session export; `registration.ts` builds its metadata/initial snapshot and `bridge.ts` receives\n  agent commands. This broker export is not a full `ReviewState` mirror.\n- **Other consumers:** Web/API consumers reuse the model, derivations, state, intents, and the\n  producer/protocol tier. Never build a parallel protocol. Keep presentation/client-local state\n  local; host/extension commands need explicit remote capabilities. See\n  `docs/browser-review-rebuild.md` for the rollout and current boundaries.\n- **Conformance:** `test/review-conformance/` has hand-authored semantic fixtures covering every\n  registered core, terminal, producer, broker, protocol, and extension projection. Every new\n  semantic consumer registers its real projection and runs the whole corpus.\n  `scripts/quality/source-boundaries.test.ts` keeps the seam\n  renderer/platform-free; its Node-debt list is shrink-only and tombstone lists append-only. A\n  repaid seam finding deletes copies, adds a file or banned-symbol tombstone and adversarial\n  fixture, registers consumers, and updates `docs/browser-review-seam-audit.md`.\n\n- Bundled VCS implementations live in the private `packages/hunk-{git,jj,sapling}` workspaces and consume\n  the public `hunkdiff/extension` contract plus explicit `@hunk/vcs/*` implementation leaves;\n  `packages/hunk/src/app` composes their registrations into the\n  provider-neutral core VCS catalog. Do not add provider commands, spawning, or source readers under `packages/hunk/src/core`.\n- `hunk daemon serve` is the one loopback daemon for all live sessions; sessions auto-start and\n  register with it rather than opening per-TUI ports. Reuse `classifyReviewPublication` and\n  `ReviewChunkAssembler` for publication ordering and bounded, digest-verified resources. Browser\n  review stays same-origin with no CORS; each session mints its capability and gives the daemon only\n  its digest. Transport semantics come from the browser-safe review protocol modules and the\n  existing intent path. See `docs/browser-review-rebuild.md` and the relevant module headers.\n- User extensions, bundled VCS providers, bundled core commands, and bundled UI share one public\n  registration API and registry model. VCS and UI use dedicated registries; bundled core commands\n  load first into each session-owned extension registry so delegated resources share its lifecycle. `ExtensionSession` owns the\n  user registry across routed surfaces. Keep `packages/hunk/src/extension-api/types.ts` import-free,\n  bundled VCS renderer-free, repo-local extensions trust-gated, and bundled extensions active under\n  `--no-extensions`. See `docs/extension-architecture.md`, `docs/extensions.md`, and\n  `packages/hunk/skills/hunk-extensions/SKILL.md`.\n- Sidecar file order is intentional sidebar and review-stream order.\n- **Session wire:** the daemon and every window exchange `HUNK_SESSION_DAEMON_VERSION`\n  (`packages/hunk/src/session/protocol.ts`) in the signed hello and require an exact match. Any\n  change to what a session registers or snapshots — under `packages/hunk/src/session/**`,\n  `packages/hunk/src/core/reviewDescriptor.ts`, `packages/hunk/src/app/session/registration.ts`, or\n  anything else that appears in `packages/hunk/src/session/broker/fixtures/session-wire.v<N>.json`\n  — requires a bump. `wire.snapshot.test.ts` enforces it; bump, run\n  `bun run generate:session-wire`, and delete the previous fixture in the same change. The\n  cross-revision admin scope (`status`/`stop`) is frozen separately and never grows in place.\n- Derive shared rendering, navigation, scrolling, and note behavior from one planning layer. Make\n  shared geometry explicit, and remove obsolete paths instead of retaining parallel implementations.\n\n## architectural rules\n\n- `bun run deps:check` enforces both the `packages/*` workspace graph and\n  `packages/hunk/src/*` tiers; `.dependency-cruiser.cjs` is authoritative and\n  `docs/module-boundaries.md` explains the model. Standalone packages do not import Hunk internals.\n  Bundled providers use only provider-local modules, platform built-ins, `hunkdiff/extension`, and\n  explicit `@hunk/vcs/*` leaves. The known-violations baseline is shrink-only: fix an edge, rerun\n  `bun run deps:baseline`, and never add to it.\n- Keep the app review-first: the main pane is a single top-to-bottom stream of all visible file diffs.\n- The sidebar is for navigation. Selecting a file jumps to that file in the main review stream; it should not collapse the main pane to one file.\n- Keep Pierre as the diff engine and renderer foundation. Do not switch the main renderer back to OpenTUI's built-in `<diff>` widget.\n- Keep split and unified views terminal-native and driven from the same normalized diff model.\n- Preserve mouse + keyboard parity for primary actions.\n- Keep the chrome restrained: top menu bar, minimal borders, no redundant metadata headers.\n\n## component guidance\n\n- `HunkSessionHost` owns history/review routing in one React root. `AppHost` owns reload\n  serialization, extension adoption, and broker/content/React commit ordering. `App` owns review\n  interaction, navigation, layout, theme, filtering, and pane coordination. Pane and diff modules\n  own rendering and geometry.\n- `InteractiveSessionInitialization` in `packages/hunk/src/core/session/initialization.ts` carries\n  finalized launch inputs whose lifetime spans routed surfaces. Add a concern there only when\n  `HunkSessionHost` owns that cross-surface lifetime; keep surface-local bootstrap data out.\n- Confirmation prompts with a small set of choices should reuse `ConfirmDialog` (body rows plus a clickable key-legend action row) instead of composing `ModalFrame` with a hand-rolled footer; keyboard handling for its actions stays in `useAppKeyboardShortcuts`.\n- Extend existing components or add focused components rather than growing `App` into a monolith.\n- Shared formatting, ids, and small derivations belong in helpers, not repeated inline.\n- When refactoring logic that spans helpers and UI components, add tests at the level where the user-visible behavior actually lives, not only at the lowest helper layer.\n\n## theme guidance\n\n- Built-in theme ids and source metadata live in `packages/hunk/src/core/theme/catalog.ts`; `packages/hunk/src/ui/themes.ts`\n  derives Hunk's semantic `AppTheme` values.\n- When adding or renaming a built-in theme, update validation, public exports, docs/examples, the\n  appropriate Changeset, and tests. Keep source palette tokens separate from semantic mappings and\n  cover non-trivial derived colors.\n- `BUNDLED_SHIKI_THEME_DIFF_COLORS` in `packages/hunk/src/core/theme/catalog.ts` is generated. Edit the sourcing policy in `scripts/generate/generate-theme-diff-colors.ts`, then run `bun run generate:theme-colors`.\n\n## testing\n\n- Colocate unit tests with the code they cover (`packages/hunk/src/core/foo.ts` + `packages/hunk/src/core/foo.test.ts`, `packages/hunk/src/ui/AppHost.*.test.tsx`, `packages/hunk/src/ui/lib/*.test.ts`).\n- Put shared unit-test helpers in `test/helpers/`.\n- Name test helpers so they explicitly include `Test` and are clearly test-only (`createTestDiffFile`).\n- Use repo-level `test/` directories by intent:\n  - `test/cli/` for black-box CLI contract coverage.\n  - `test/session/` for daemon/session integration and end-to-end flows.\n  - `test/pty/` for PTY-backed live UI integration tests.\n  - `test/review-conformance/` for the shared review model's golden fixtures and per-consumer conformance suites.\n  - `test/session-broker-node/` for real Node listener/adapter conformance.\n  - `test/session-broker-runtime/` for shared Bun/Node broker fixtures.\n  - `test/smoke/` for opt-in terminal transcript smoke coverage.\n- `bun run test` does not include review conformance, PTY, TTY smoke, or real-Node adapter\n  conformance under `test/session-broker-node/`. Run the dedicated command documented in\n  `test/README.md` when changing those areas.\n\n## source formatting\n\n- Separate functions, class methods, nested helpers, and test declarations with a blank line; keep JSDoc and test-specific comments attached to their declaration.\n- Use blank lines between logical phases inside functions. Keep related statements grouped rather than packing an entire workflow into one dense block.\n\n## code comments\n\n- Add short JSDoc-style comments to functions and helpers.\n- Write header comments in active voice: the first sentence says what the module or function\n  does (\"Applies one action to the review state and returns the next state.\"), followed by its\n  invariants. Avoid passive or self-important framing (\"The one place where…\", \"the single\n  source of truth for…\") — name the behavior, not the architecture's opinion of itself.\n- For orchestration and controller modules, explain the product workflow before the mechanics:\n  name the user-visible triggers that converge there, state what the module owns, identify the\n  neighboring authority it deliberately leaves elsewhere, and call out preservation or\n  non-reloadable invariants. Prefer concrete flows (\"watch changes and manual refresh both rebuild\n  the mounted review\") over abstract labels (\"handles refresh\").\n- Add inline comments for intent, invariants, ownership, or non-obvious tradeoffs. Do not narrate\n  syntax, praise the implementation, address the reader conversationally, or record temporary\n  implementation history that belongs in Git or maintainer docs.\n\n## naming\n\n- Prefer names that match the role the code plays in the product and architecture.\n- Use `layout` for structural placement or arrangement data.\n- Use `geometry` for aggregate spatial data used by rendering, scrolling, or interaction.\n- Use `bounds` for one concrete visible extent within a larger structure.\n\n## review behavior\n\n- Default behavior is a multi-file review stream in sidebar order.\n- Layout modes are `auto`, `split`, and `unified`. `auto` chooses split on wide terminals and unified\n  on narrow ones; explicit modes override it.\n- `[` and `]` navigate hunks across the full review stream. Do not reintroduce `j`/`k` hunk navigation unless the user asks.\n- Agent context belongs beside the code, not hidden in a separate mode or workflow.\n- Agent notes are hunk-specific: show notes for the selected hunk, render them in the diff flow near the annotated row, and keep a clear spatial relationship to the code they explain.\n- Keep note behavior explicit. If the UI intentionally prioritizes one note, one selection, or one active target, encode that as a named policy rather than scattering array-index assumptions through the codebase.\n- STML markup notes (experimental) live in `packages/hunk/src/ui/lib/stml/`. The layout engine is deliberately a deterministic line layout, not OpenTUI flexbox: the row-windowed review stream needs exact note heights before mount, so `(markup, width)` must always produce the same lines. Colors stay symbolic until render time so measurement never needs a theme. Do not \"simplify\" this into flexbox renderables, and keep note-card geometry in `agentNoteGeometry` as the single source for rendering, measurement, and agent-facing width reporting.\n- Keep temporary sidecars concise and review-oriented. Their file order is intentional, while the\n  visible note UI remains hunk-note driven rather than showing generic explainer cards.\n- Agents review via `packages/hunk/skills/hunk-review/SKILL.md` using `hunk session *` commands; do not run interactive TUI commands directly.\n- `packages/hunk/skills/hunk-review/SKILL.md` is generated. Edit `packages/hunk/src/hunk-review/skillDocument.ts`, `packages/hunk/src/session/agent/surface.ts`, or `packages/hunk/src/session/agent/errors.ts`, then run `bun run generate:skill`; never hand-edit the skill file.\n\n## binary notes\n\n- Installed `hunk` is a compiled snapshot, not linked to source.\n- After source changes, rebuild/reinstall with `bun run install:bin`.\n- For rendering verification, prefer a real TTY smoke run over redirected stdout capture.\n\n## verification\n\n- For rendering changes: run `bun run typecheck`, `bun run test`, `bun run test:integration`,\n  `bun run test:tty-smoke`, and do one real TTY smoke run on an actual diff.\n- For interaction, layout, scrolling, navigation, windowing, or other terminal-native behavior: add or update PTY integration coverage in `test/pty/*-integration.test.ts` and run it with `bun run test:integration`.\n- For broker/runtime changes, run `bun run test:session-broker-node` in addition to the relevant\n  Bun tests.\n- For CLI, config, or pager work: make sure the relevant source invocation still works (`diff`, `show`, `patch`, or `pager`).\n- Preserve current interaction model unless the user asks to change it explicitly.\n\n## cross-platform support\n\n- Hunk should work on macOS, Linux, and Windows. Keep tests and CI portable unless a case is explicitly Unix-only (PTY/TTY smoke coverage is Unix-only).\n- In tests, avoid hard-coded POSIX paths, separators, shell syntax, and filenames invalid on Windows; use Node path helpers for real filesystem paths while preserving user-provided/protocol paths when pass-through is intentional.\n- If Windows-only Bun behavior appears around timers, sockets, or line endings, prefer a small compatibility fix or a narrowly scoped skip with a comment over broadening Unix assumptions.\n\n## releases\n\n- User-visible changes require a Changeset; maintenance-only changes require an empty Changeset.\n  Follow `.changeset/README.md` and do not edit `CHANGELOG.md` directly.\n- For release preparation, publishing, backports, and post-release verification, read `skills/hunk-release/SKILL.md`.\n- Never push a release tag or trigger publishing without explicit user confirmation.\n- `hunk.dev/changelog` is generated from `CHANGELOG.md` by `bun run generate:changelog`; hand-author only `website/releases/notes.json`, and never edit its output. `docs/changelog-on-hunk-dev.md` explains how release dates and the pre-tag window work.\n\n## repo notes\n\n- Local review artifacts are ignored on purpose. Leave them alone unless the user explicitly wants them updated, and do not commit them.\n- Before committing or preparing a PR, follow `CONTRIBUTING.md`.\n","CLAUDE.md":"AGENTS.md","examples/extensions/cli-tools/README.md":"# CLI tools extension\n\nDemonstrates a generic top-level command tree. The handler owns every token below `cli-tools`, can stream headless output, receives cooperative cancellation, and can hand terminal ownership to one built-in Hunk command.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx --extension ./examples/extensions/cli-tools cli-tools status\nbun run packages/hunk/src/main.tsx --extension ./examples/extensions/cli-tools cli-tools review\n```\n\n`status` writes to stdout and exits. `review` performs signal-aware asynchronous preprocessing, writes progress to stderr, then delegates to `hunk diff`. A delegating handler must not write stdout or read stdin because the built-in command or TUI takes ownership of both.\n\nThe example parses its raw argument tokens directly. Extensions may use their own parser, make network requests, spawn processes, or access the filesystem under Hunk's ordinary extension trust model.\n","examples/extensions/code-document-file-view/README.md":"# Code-document file view\n\nThis API-v28 example presents one old/new code pair per diff hunk. It declares the complete source documents so Hunk can tokenize with multiline lexical context, while each displayed span references either a complete old-side line or a partial new-side UTF-16 slice. Gutters and the unhighlighted prefix stay ordinary symbolic spans.\n\nHunk owns the language, active syntax theme, workers, caching, terminal safety, and plain fallback. The extension never supplies token colors.\n\nRun it from this checkout against two JavaScript or TypeScript files:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/code-document-file-view \\\n  path/to/before.ts path/to/after.ts\n```\n\nChoose **View → File presentation → Code documents: old / new**. The example intentionally omits `sourceRanges`: syntax references paint code but do not claim note/navigation ownership. A visible note therefore makes Hunk use the raw diff, as required by the all-or-raw note policy.\n","examples/extensions/inline-edit/README.md":"# Inline edit extension\n\nA miniature line editor for the file under review. Press `Ctrl-E`, type into the diff, press `Ctrl-S`, and Hunk writes the file back to your working tree after asking you first.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\nIt exists to demonstrate that Hunk's interactive extension surfaces compose, so it uses them all at once:\n\n| Capability                              | Where this extension uses it                                                                                                                                                                                                                                           |\n| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `ctx.workspace`                         | `canWriteDocument` gates the affordance, `readDocument(\"new\")` fills the buffer, `writeDocument` performs the consented write and reloads the review.                                                                                                                  |\n| `mode` on a registered file view        | `onKey` claims arrows, editable characters, Backspace, Enter, and `Ctrl-S`, while `]`, `?`, and `q` pass through so navigation, help, and quit keep working. `Ctrl-S` is recognized with `matchesKey`, so the bare control byte terminals send for it matches too.     |\n| `fileViews.enterMode(viewId)`           | One call makes the view the file's presentation _and_ gives its mode the keyboard, so `Ctrl-E` opens the editor in a single press.                                                                                                                                     |\n| `fileViews.refresh(viewId, { fileId })` | Every buffer or caret change re-derives the layout. A view's layout is a pure function of `(file, width)`, so this is the only way a stateful presentation redraws — and the buffer belongs to one file, so the refresh is scoped to it and no other file re-lays out. |\n\n## Try it from this checkout\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/inline-edit\n```\n\n## Install it globally\n\nCopy the whole folder — it has no dependencies:\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/inline-edit ~/.config/hunk/extensions/\n```\n\nHunk discovers the folder automatically on later launches. Open **View** and choose **File presentation: Inline edit**, or press `Ctrl-E`. The command is named `inline-edit.edit` for `[keybindings]` customization.\n\n## Keys\n\n| Key                           | Does                                                                          |\n| ----------------------------- | ----------------------------------------------------------------------------- |\n| `Ctrl-E`                      | Starts editing the selected file, showing the view if it was not already.     |\n| `↑` `↓` `←` `→`               | Move the caret. `←`/`→` wrap across line ends.                                |\n| any other printable character | Types at the caret — including characters bound to Hunk commands, like `z`.   |\n| `Backspace`                   | Deletes back one character, joining with the previous line at column 0.       |\n| `Enter`                       | Splits the line at the caret.                                                 |\n| `Ctrl-S`                      | Asks Hunk to write the buffer. Hunk confirms, writes, and reloads the review. |\n| `Esc`                         | Leaves the editor and **discards** everything typed since the last write.     |\n| everything else               | Still Hunk's: `]` moves to the next hunk, `?` opens help, `q` quits.          |\n\nThe header row reads `EDITING — Esc exits · ctrl+s writes`, plus a `MODIFIED` marker whenever the buffer differs from the text on disk.\n\n## How the pieces fit\n\n`Ctrl-E` is one press. The command gates on `canWriteDocument`, starts reading the document, and calls `fileViews.enterMode` before awaiting that read. Entry therefore uses the same selected file the command captured; a selection change cannot attach a late buffer to another file. `enterMode` makes the view that file's presentation and gives its mode the keyboard together, then the completed read builds the buffer and refreshes the view. When entry refuses (no `mode`, a file Hunk keeps on raw diff, a view that does not match), it warns by name and returns `false` without installing a buffer.\n\nThe editor slot is claimed synchronously, before that command's first `await`. Reading the document suspends the handler, so a guard that only checked \"is a session live?\" would let a second `Ctrl-E` through the window in between, and one of the two handlers would then be parked forever on a session nothing could end. The claim is released on every way out — an unwritable review, an unreadable document, a refused `enterMode` — and once a session is live it is the live session that answers the next press.\n\n`onKey` has to answer synchronously — its return value _is_ the routing decision — and the mode context carries only `file` and `fileViews`. So a keystroke can never write anything itself. What `Ctrl-S` does instead is post a request into the edit session, and the command handler that entered the mode is still awaiting that session: `ctx.workspace` is valid for the whole life of a command handler's promise, so the handler is the mode's async runtime.\n\nThat loop ends the same way from every direction, because `onExit` runs on every exit path:\n\n- **Saved.** A successful `writeDocument` reloads the session, the reload exits the mode, `onExit` ends the loop.\n- **Escaped.** Escape is host-owned, so it exits the mode without ever reaching `onKey`, and the loop ends the same way.\n- **Cancelled write.** `{ ok: false, reason: \"cancelled\" }` is the user answering, not a failure. The editor keeps running.\n- **Failed write.** The `detail` sentence is shown as a warning and the editor keeps running, so nothing typed is lost to a full disk.\n\n## Limitations\n\nThis is a demonstration, not an editor:\n\n- **No wrapping.** Lines are truncated at the pane width with `…`. A file-view layout must be deterministic for `(file, width)`, and wrapping would be a second layout problem on top of the one this example is about.\n- **Whole-line truncation.** The caret can move past the visible edge; the row does not scroll horizontally to follow it.\n- **One buffer, no undo.** Escape discards everything since the last write, with no confirmation beyond the notice it leaves behind.\n- **Writes are working-tree only**, which is a property of `ctx.workspace`: `hunk show`, `hunk patch`, a staged diff, and a file-pair diff have no working-tree document to replace, so the command refuses instead of opening an editor that could not save.\n- **Agent notes describe the document as it was loaded.** They stay on the lines they were written about, but nothing re-reads the changeset while you type.\n\n## Source bindings and provenance\n\nRows carry `sourceRanges` so Hunk can place its own inline notes inside this presentation, and a row may only bind a line it honestly still _is_. Row position cannot answer that once you split or join a line, so the edit session keeps **provenance**: for each buffer line, the document line it came from, or nothing at all.\n\n| Edit               | Provenance                                                               |\n| ------------------ | ------------------------------------------------------------------------ |\n| typing in a line   | kept — an edited line is still the line it came from                     |\n| `Enter` (split)    | the first line keeps it; the new tail gets none                          |\n| `Backspace` (join) | the merged line presents both source lines so either note stays attached |\n\nSo a line you inserted binds nothing, the lines below a split keep the numbers they had, and a joined row keeps every source line it now presents. The hunk extents in `hunkRows` are derived from the same provenance, so the hunk highlight follows the rows still holding a hunk's lines instead of drifting down by however many lines you added above them. A join across two distinct hunks is refused because one bound row cannot belong to two hunk extents.\n\nOne thing this shows that is easy to get wrong: Hunk accepts a binding only on a row exactly one `hunkRows` extent owns, and rejects the whole layout otherwise — so a last pass drops the bindings no extent owns, and context lines outside every hunk are presented without one. The [rendered Markdown example](../rendered-markdown/) ends its layout with the same pass, for the same reason.\n\n## Where this is documented\n\n- [`docs/extensions.md` → Interactive file views](../../../docs/extensions.md#interactive-file-views)\n- [`docs/extensions.md` → Reading and writing a reviewed file](../../../docs/extensions.md#reading-and-writing-a-reviewed-file)\n- [`docs/extensions.md` → `hunk.registerFileView(view)`](../../../docs/extensions.md#hunkregisterfileviewview-experimental)\n","examples/extensions/jsx-file-view-gallery/README.md":"# JSX file-view gallery\n\nThree opt-in presentations exercise the constrained React/OpenTUI row contract against checked-in, realistic file pairs. Run one command from the repository root, then press **F8** or choose **Extensions → Toggle JSX demo for current file**. Press F8 again to restore Pierre's raw diff.\n\n## 1. Change atlas\n\nNested boxes, responsive meters, semantic color, and selected-hunk styling summarize a multi-hunk TypeScript refactor. It uses no source parser and works from public hunk/change metadata alone.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/before.ts \\\n  examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/after.ts\n```\n\n## 2. CSS palette delta\n\nThe extension lazily reads both exact documents, associates changed opaque three- or six-digit hexadecimal custom properties with each real diff hunk, and paints old/new terminal color swatches inside deterministic two-row rectangles.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/css-palette/before.css \\\n  examples/extensions/jsx-file-view-gallery/fixtures/css-palette/after.css\n```\n\n## 3. Dependency delta\n\nA conservative package-file parser highlights only the changed semantic-version segment: patch-only changes emphasize the patch number, minor upgrades emphasize the minor number, and major upgrades emphasize the full old/new strings. It retains positional bounds for every parsed hunk; invalid JSON or unavailable source falls back to raw diff.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/before/package.json \\\n  examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/after/package.json\n```\n\n## Mixed five-file review\n\nTo see all three preview types retained together between ordinary raw diffs—and enough content to exercise stream scrolling—run:\n\n```bash\nbun run ./examples/extensions/jsx-file-view-gallery/mixed-review/run.ts\n```\n\nFollow the short activation sequence in [`mixed-review/README.md`](./mixed-review/README.md).\n\n## Contract illustrated\n\n- Every painter stays inside a declared fixed-height row; geometry, scrolling, windowing, and hunk navigation remain host-owned.\n- Every row keeps useful symbolic spans for row-local error fallback, clipped to the same fixed rectangle.\n- Semantic data is captured in closures during `layout`; painters receive only bounded paint props and use Hunk's live paint-only semantic theme palette.\n- The demos intentionally have no pointer handlers. Registered commands are the supported interaction path.\n- Layouts return `null` when exact source is unavailable or no supported semantic row can be attributed. Mixed diffs may retain neutral summary rows for non-semantic hunks so navigation stays positional.\n- Rows bind conservatively attributed exact-source ranges. Hunk renders a note inside the alternate view only when its preferred-side anchor resolves uniquely; otherwise the complete file falls back to raw diff.\n\nThis gallery is experimental and is not loaded unless you explicitly pass or install its folder. See [`docs/file-view-jsx-poc.md`](../../../docs/file-view-jsx-poc.md) for the full contract.\n","examples/extensions/jsx-file-view-gallery/mixed-review/README.md":"# Mixed preview review\n\nThis launches one real five-file Git working-tree review that is intentionally taller than a terminal viewport:\n\n- `README.md` — raw Markdown diff;\n- `package.json` — dependency version-segment highlights;\n- `scripts/deploy.py` — raw Python diff;\n- `src/invoice.ts` — responsive change-atlas cards;\n- `styles/theme.css` — exact-source color swatches.\n\nThe launcher creates a temporary repository, commits the checked-in `before` fixtures, copies the `after` fixtures into its working tree, and starts Hunk from this checkout. The repository is removed when Hunk exits.\n\nFrom the Hunk repository root:\n\n```bash\nbun run ./examples/extensions/jsx-file-view-gallery/mixed-review/run.ts\n```\n\nRaw diff is deliberately the default. To build the mixed stream:\n\n1. Click `package.json` in the sidebar and press **F8**.\n2. Click `src/invoice.ts` and press **F8**.\n3. Click `styles/theme.css` and press **F8**.\n4. Click `README.md` to return to the top, then scroll through the main pane.\n\nSelecting files only jumps the main review stream; it does not collapse other files. The three preview selections therefore remain active together, interleaved with the two raw Pierre diffs. Use `[` and `]` while scrolling to verify that hunk navigation crosses raw and custom sections using the same host-owned geometry.\n","examples/extensions/jsx-file-view/README.md":"# JSX file-view POC extension\n\nAn opt-in proof of concept for fixed-height React/OpenTUI rows in alternate file presentations. It appears only for files with at least two parsed hunks and creates two custom rows per hunk, with stable row IDs and explicit hunk bounds.\n\nRun it from this checkout against a multi-hunk working-tree change:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/jsx-file-view\n```\n\nChoose **Extensions → Toggle JSX hunk cards (POC)**. The row component uses a React state hook and OpenTUI `box`/`text` elements. Its registered F8 command/menu item is the supported keyboard path. A cooperatively delivered, un-dragged left-button mouse-up toggles local detail and stops propagation; wheel, drag, and unhandled input remain host-owned. The row is a non-focusable paint surface, with no portal, renderer, focus, or input-delivery guarantee. Each component is a closure over the hunk summary; Hunk passes it only bounded paint props, including a live semantic theme palette that does not participate in layout. The `spans` on every row are the host-rendered fallback, clipped to the same declared fixed height if the component fails. Hook state survives selected-hunk updates while mounted, but is intentionally lost when windowing unmounts the row or a new layout generation replaces it.\n\nThis example is deliberately opt-in and experimental. See [`docs/file-view-jsx-poc.md`](../../../docs/file-view-jsx-poc.md) for constraints.\n","examples/extensions/pane-layout/README.md":"# Pane layout extension\n\nRegisters a resizable right pane and fixed two-row top and bottom panes.\n\nRun it from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/pane-layout\n```\n\nPress `ctrl+p` or use the **Extensions** menu. Drag the right divider to resize.\n\nCopy this directory to your Hunk extensions directory to install it.\n","examples/extensions/rendered-markdown/README.md":"# Rendered Markdown extension\n\nAn optional Markdown preview for Hunk's experimental file-view API. It parses Markdown with [Marked](https://marked.js.org/) and presents headings, inline formatting, links, lists, quotes, tables, and fenced code as host-owned symbolic rows.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\n## Try it from this checkout\n\nThe repository's root install supplies the example's development dependency:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/rendered-markdown \\\n  before.md after.md\n```\n\n## Install it globally\n\nCopy the whole folder, then install its local dependency:\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/rendered-markdown ~/.config/hunk/extensions/\ncd ~/.config/hunk/extensions/rendered-markdown\nbun install\n```\n\nHunk discovers the folder automatically on later launches. Open **View** and choose **File presentation: Rendered Markdown**, or press `F8`. The command is named `rendered-markdown.toggle-rendered-markdown` for `[keybindings]` customization.\n\nRaw Pierre diff remains the default and fallback. The preview reads exact text through `input.readDocument`, returns hunk row bounds in source-hunk order, binds rendered blocks to exact new-side ranges, and retains hunk navigation and selection highlighting. Hunk inserts inline note cards at uniquely bound rows; if any visible note cannot be resolved, the complete file temporarily returns to raw rendering so review data is never hidden.\n\n## Why it returns rows instead of an OpenTUI Markdown component\n\nOpenTUI includes a capable `MarkdownRenderable`, but mounting it would make the extension own opaque layout geometry. Hunk needs exact rows before mount for review-stream measurement, windowing, scrolling, hunk navigation, and fallback. This example therefore uses the same Marked parser OpenTUI uses, then translates its tokens into the file-view contract's generic tones and text attributes.\n","examples/extensions/review-note-navigator/README.md":"# Review note navigator extension\n\nLists every note currently saved in Hunk's shared ReviewStore, then navigates to a selected visible note's authoritative source anchor. The example shows how `ctx.review.snapshot()` complements dialogs and live navigation: selection and note events alone cannot recover notes that already existed before the extension began listening.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-note-navigator\n```\n\nSave one or more review notes, then run **Extensions → Navigate saved review note…** (`F8`). Each choice includes its reconciliation status, file, preferred line, side, and summary.\n\n- Active notes reveal their current source line.\n- Stale notes reveal the last authoritative anchor Hunk retained.\n- Orphaned notes remain visible in the inventory but produce a warning because they have no current review location.\n- A note whose file is hidden by Hunk's current file filter remains in the inventory, but guarded navigation refuses the hidden target with a warning; clear the filter and retry.\n- Drafts and static sidecar annotations that never entered ReviewStore are intentionally absent.\n\nThe command captures the complete note inventory before opening its selector. After the user chooses, it reads the authoritative snapshot again and resolves the selected note by stable note id, so an edit made by another review surface cannot make it navigate using an obsolete anchor. A reload cancels the dialog and retires the command's review controls.\n","examples/extensions/review-snapshot-export/README.md":"# Review snapshot export extension\n\nExports Hunk's authoritative saved review state as JSON. The example shows why `ctx.review.snapshot()` is more useful than accumulating `note_created` events: one command receives every note currently retained by the shared ReviewStore, including stale and orphaned notes an exporter must handle explicitly.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-snapshot-export\n```\n\nAdd one or more review notes, then run **Extensions → Export review snapshot…** (`F9`) and choose a new output path. Relative paths resolve from the review's working directory; the example refuses to overwrite an existing file.\n\nThe JSON includes:\n\n- the opaque producer generation and ReviewStore revision\n- every reviewed file's stable `fileKey`, runtime navigation id, content identity, status, and path\n- all saved live and reviewer notes with their resolved old/new anchors and reconciliation status\n\nDraft notes and static sidecar annotations that never entered ReviewStore are intentionally absent. Saved notes appear in live-arrival order followed by reviewer-creation order.\n\nThe command captures a snapshot before opening its path dialog, then reads the controls again before writing. If either the generation or revision changed, it refuses the stale export and asks the user to rerun the command. Publishers can use the same check before an irreversible network request.\n\n## Trust\n\nThis example uses `node:fs` directly to write the user-selected export path. Hunk extensions run with the user's full permissions; this is distinct from `ctx.workspace`, which mediates writes to reviewed files and asks for consent. Install and run only extensions you trust.\n","examples/extensions/review-triage/README.md":"# Review triage extension\n\nA session-local hunk review board for Hunk. It records which hunks you have visited and lets you mark the selected hunk **approved**, **investigate**, or **blocked** with an optional rationale.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-triage\n```\n\nOr copy the directory to your Hunk extensions directory and keep its `package.json`; its manifest makes the folder a single `review-triage` extension.\n\n## Use\n\nOpen **Extensions → Toggle review triage** (`Y`). The right pane lists each visible file's hunks; click a hunk to navigate the review stream. Use **Extensions → Mark selected hunk…** (`x`) to choose a status and enter an optional rationale. **Center current review line**, **Set review focus…**, and **Clear triage decisions** are menu-only commands.\n\nThe board intentionally keeps state only for the running Hunk session. Reloading reconciles decisions against the newly parsed hunks and drops entries that no longer match, rather than silently transferring a decision to changed code.\n\n## API surface exercised\n\n- `registerPane` renders public file/hunk summaries and navigates with pane actions.\n- `registerCommand` supplies the Extensions-menu items and user-remappable defaults; `ctx.commands.execute` delegates the dedicated centering action to Hunk's public semantic command.\n- `dialogs.select`, `dialogs.input`, and `dialogs.confirm` implement the review decision and clear flows.\n- Lifecycle handlers track changeset loads, reloads, selection, viewed hunks, Hunk notes, filters, and pending watch reloads through a `useSyncExternalStore` bridge.\n- `hunk.events` publishes decisions and listens for `review-triage:open`, so another extension can reveal the board without importing its state.\n","examples/extensions/vim-navigation/README.md":"# Vim navigation extension\n\nA small Vim-style normal mode for Hunk's whole review stream. It demonstrates session keyboard modes and public semantic command execution without accessing scroll boxes, renderer objects, or viewport coordinates.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\n## Try it from this checkout\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/vim-navigation\n```\n\nPress `F6` or choose **Extensions → Toggle Vim navigation**. The persistent status badge shows when the mode owns review-level keys; click the badge, choose the host-owned exit menu item, or press `Esc` to leave.\n\n## Install it globally\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/vim-navigation ~/.config/hunk/extensions/\n```\n\n## Keys\n\n| Key                 | Action                                                      |\n| ------------------- | ----------------------------------------------------------- |\n| `j` / `k`           | Move the current review line down/up                        |\n| `[` / `]`           | Move to the previous/next hunk                              |\n| `gg` / `G`          | Jump to the start/end of the review                         |\n| `zt` / `zz` / `zb`  | Align the current line at the top/center/bottom             |\n| `Ctrl-D` / `Ctrl-U` | Move down/up by half pages                                  |\n| positive digits     | Prefix the next relative motion, for example `5j` or `3]`   |\n| `:`                 | Open the host-rendered Vim command line                     |\n| `Esc`               | Exit the mode (host-owned; the extension never receives it) |\n| everything else     | Pass through to normal Hunk routing                         |\n\nCounts are parsed by the extension and capped at 10,000. Once a normal-mode sequence resolves, the extension calls `ctx.commands.execute(id, { count })` exactly once, so Hunk applies movement atomically. A bare `0` passes to Hunk's normal layout shortcut; `0` can extend a count that already began with `1`–`9`.\n\nPressing `:` passes the key to the example's registered command, which opens `ctx.prompts.line()` on the status row with a `:` prefix. That focused host input captures typed keys ahead of the still-active session mode until Enter submits, or Escape clears the buffer and a second Escape cancels. The deliberately small Ex-style command set is:\n\n| Command   | Action                          |\n| --------- | ------------------------------- |\n| `:top`    | Jump to the start of the review |\n| `:bottom` | Jump to the end of the review   |\n\nUnsupported commands produce an attributed warning. Absolute source-line commands such as Vim's `:100` are intentionally absent because Hunk does not expose source-line targeting as a public semantic command; relative counted movement such as `100j` remains available in normal mode.\n\nThe example enables Hunk's host-owned current-line marker on entry so the `z*` alignment commands have a target. It resets all pending prefix/count state on entry and exit. Invalid continuations clear pending state and pass the current key back to Hunk.\n","packages/hunk-git/README.md":"# @hunk/git\n\nPrivate, statically bundled Git provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-tree, revision, and stash\nreviews; history; exact old/new source reads; staged changes; untracked and large-file handling;\nand filesystem-assisted watch plans.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-git` and `bun run deps:check` after changes.\n","packages/hunk-jj/README.md":"# @hunk/jj\n\nPrivate, statically bundled Jujutsu provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-copy and revision reviews,\nhistory, exact source reads where the revision shape permits them, and signature-based refresh.\nStaged and stash reviews are not supported.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-jj` and `bun run deps:check` after changes.\n","packages/hunk-sapling/README.md":"# @hunk/sapling\n\nPrivate, statically bundled Sapling provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-copy and revision reviews,\nuntracked-file discovery, and signature-based refresh. History, staged and stash reviews, exact\nsource reads, and filesystem watch plans are not supported.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-sapling` and `bun run deps:check` after changes.\n","packages/hunk-vcs/README.md":"# @hunk/vcs\n\nPrivate implementation helpers shared by Hunk's bundled VCS providers. This `0.0.0` workspace is\nnot a public or versioned SDK.\n\nThe package has no root export. Providers import only the leaf they need:\n\n- `@hunk/vcs/async-process`\n- `@hunk/vcs/diff-target`\n- `@hunk/vcs/large-file`\n- `@hunk/vcs/path`\n- `@hunk/vcs/source`\n\n`@hunk/vcs` sits at the bottom of the workspace graph and does not import other workspace packages.\nProvider implementations combine these helpers with the public `hunkdiff/extension` contract; Hunk\nregisters them in `packages/hunk/src/extensions/default/vcs/index.ts` and composes the catalog in\n`packages/hunk/src/app/vcsCatalog.ts`.\n\nKeep unit tests beside the source. Run `bun test packages/hunk-vcs` and `bun run deps:check` after\nchanging this package.\n","packages/hunk/README.md":"# hunk\n\nHunk is an interactive terminal diff viewer for reviewing code changes, built on [OpenTUI](https://github.com/anomalyco/opentui) and [Pierre diffs](https://www.npmjs.com/package/@pierre/diffs).\n\n**[hunk.dev](https://hunk.dev)** · [Documentation](https://hunk.dev/docs/)\n\n[![CI status](https://img.shields.io/github/actions/workflow/status/modem-dev/hunk/ci.yml?branch=main&style=for-the-badge&label=CI)](https://github.com/modem-dev/hunk/actions/workflows/ci.yml?branch=main)\n[![Latest release](https://img.shields.io/github/v/release/modem-dev/hunk?style=for-the-badge)](https://github.com/modem-dev/hunk/releases)\n[![MIT License](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE)\n[![Join the Discord community](https://img.shields.io/badge/Discord-Join%20community-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/WZFjaP6Gt8)\n\n- multi-file review stream with sidebar navigation\n- inline AI and agent annotations beside the code\n- split, unified, and responsive auto layouts\n- watch mode for auto-reloading file and Git-backed reviews\n- keyboard, mouse, pager, and Git difftool support\n\n<table>\n <tr>\n   <td width=\"60%\" align=\"center\">\n     <img width=\"845\" alt=\"image\" src=\"https://raw.githubusercontent.com/modem-dev/hunk/refs/heads/main/website/public/docs/images/review-stream.webp\" />\n     <br />\n     <sub>Split view with sidebar and inline AI notes</sub>\n   </td>\n   <td width=\"40%\" align=\"center\">\n     <img width=\"507\"alt=\"image\" src=\"https://raw.githubusercontent.com/modem-dev/hunk/refs/heads/main/website/public/docs/images/agent-comments.webp\" />\n     <br />\n     <sub>Unified view and mouse-selectable menus</sub>\n   </td>\n </tr>\n</table>\n\n## Install\n\nOn macOS and Linux, install the standalone binary:\n\n```bash\ncurl -fsSL https://hunk.dev/install.sh | sh\n```\n\nWindows users can install with npm or mise. Other installation methods are also available:\n\n```bash\nnpm i -g hunkdiff                    # macOS, Linux, or Windows; requires Node.js 22+\nbrew install hunk                    # macOS or Linux\nmise use -g hunk                     # macOS, Linux, or Windows\n```\n\n> [!NOTE]\n> If you previously installed hunk via `modem-dev/tap`, be sure to uninstall it first with `brew uninstall modem-dev/tap/hunk`.\n\nWindows requires mise 2026.8.6 or newer. Nix users can use the `default` package exported in `flake.nix`; see [the Nix guide](https://github.com/modem-dev/hunk/blob/main/nix/README.md) for details. Hunk also ships as a default tool in [Omarchy](https://omarchy.org), installed through mise.\n\nRequirements:\n\n- macOS (arm64, x64), Linux (arm64, x64; glibc), or Windows (x64); prebuilt binaries are not published for Windows on arm64 or musl-based Linux such as Alpine\n- On x86-64, a CPU with SSE4.2 (Intel Nehalem 2008+, AMD Bulldozer 2011+); arm64 has no CPU feature floor\n- Node.js 22+ for the npm install; the install script, Homebrew, mise, and Nix ship a standalone binary that does not require Node.js\n- Git recommended for most workflows\n\n### Update Hunk\n\nFor npm, Homebrew, and default install-script installs (Hunk 0.20+):\n\n```bash\nhunk update          # install the newest release\nhunk update --check  # check without installing\nhunk update 0.20.0   # select an exact npm or default install-script release\n```\n\nOn an older release, update once with the installer or package manager that installed Hunk, then use `hunk update` going forward. Custom `HUNK_INSTALL_DIR` installs must re-run the installer with the same directory; mise, Nix, and source installs use their owning tools instead.\n\n## Quick start\n\n```bash\nhunk           # show help\nhunk --version # print the installed version\n```\n\n### Working with Git\n\nUse familiar Git commands to open changes in Hunk:\n\n```bash\nhunk diff                      # review current repo changes, including untracked files\nhunk diff --watch              # auto-reload as the working tree changes\nhunk show                      # review the latest commit\nhunk show HEAD~1               # review an earlier commit\nhunk log                       # browse history on a terminal; print when redirected\nhunk log --static              # force static output, paging when needed\nhunk status                    # view workspace status and sibling worktrees (Git)\nhunk status --static           # print one read-only workspace snapshot\nhunk status --json             # emit one versioned workspace snapshot\n```\n\n`hunk status` is read-only: it does not fetch or change the repository. See [workspace status](docs/status.md) for details.\n\n`hunk log` opens an interactive history browser in a terminal and prints text when piped or redirected. Use `--oneline` for compact output. In the browser, enable **Graph view** from the View menu to see commit branches, or press `v` to select a commit range. Quit a commit review to return to history.\n\n### Working with GitHub\n\nHunk bundles `hunk gh`, so pull requests, commits, and comparisons open directly without the\nGitHub CLI or a separate extension:\n\n```bash\nhunk gh pr                         # infer the open PR for the current branch\nhunk gh pr 123                     # infer the repository from origin\nhunk gh pr 'modem-dev/hunk#123'    # quote this form for shells that treat # as a comment\nhunk gh commit a1b2c3d --repo modem-dev/hunk\nhunk gh compare main...feature --repo modem-dev/hunk\n```\n\nPublic repositories work anonymously. Set `GH_TOKEN`, or `GITHUB_TOKEN` when it is absent, for\nprivate repositories and higher API limits. GitHub Enterprise is not currently supported.\n\n### Working with Jujutsu and Sapling\n\nHunk auto-detects Jujutsu and Sapling checkouts, so `hunk diff [revset]` and `hunk show [revset]` use native revsets inside jj or Sapling workspaces. `hunk log --vcs jj` also reads JJ history directly, including in a non-colocated workspace. To override VCS detection, set `vcs = \"git\"` or `vcs = \"jj\"` or `vcs = \"sl\"` in [config](#config).\n\n### Viewing complete files and browsing directories\n\n```bash\nhunk open                 # browse the current directory\nhunk open README.md       # view a complete, syntax-highlighted document\nhunk open path/to/project # browse another directory without requiring a repository\n```\n\nUse arrows and Enter to navigate, Tab to switch focus, `i` to show hidden/Git-ignored entries, and `e` to edit the displayed file in `$EDITOR`. See [the open workflow](docs/open.md) for details and file limits.\n\n### Working with raw files and patches\n\n```bash\nhunk diff --files before.ts after.ts        # compare two files directly\nhunk diff --files before.ts after.ts --watch # auto-reload when either file changes\ngit diff --no-color | hunk patch -          # review a patch from stdin\n```\n\n### Working with agents\n\n1. Install the review skill once: `npx skills add modem-dev/hunk -g --skill hunk-review`.\n2. Open Hunk in another terminal with `hunk diff` or `hunk show`.\n3. Ask your agent to use the hunk-review skill against the live Hunk session.\n4. Press `a` in Hunk to show agent notes; they start hidden.\n\nIf your agent has no skill system, ask it to run `hunk skill show` and follow the output.\n\nSee [Review with an agent](https://hunk.dev/docs/agents/review-with-an-agent/) for live sessions, `--agent-context`, and experimental rich notes.\n\n## Feature comparison\n\n| Capability                           | [hunk](https://github.com/modem-dev/hunk) | [lumen](https://github.com/jnsahaj/lumen) | [difftastic](https://github.com/Wilfred/difftastic) | [delta](https://github.com/dandavison/delta) | [diff-so-fancy](https://github.com/so-fancy/diff-so-fancy) | [diff](https://www.gnu.org/software/diffutils/) |\n| ------------------------------------ | ----------------------------------------- | ----------------------------------------- | --------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |\n| Review-first interactive UI          | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Multi-file review stream + sidebar   | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Inline agent / AI annotations        | ✅                                        | ❌                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Responsive auto split/unified layout | ✅                                        | ❌                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Mouse support inside the viewer      | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Runtime view toggles                 | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Syntax highlighting                  | ✅                                        | ✅                                        | ✅                                                  | ✅                                           | ❌                                                         | ❌                                              |\n| Structural diffing                   | ❌                                        | ❌                                        | ✅                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Pager-compatible mode                | ✅                                        | ❌                                        | ✅                                                  | ✅                                           | ✅                                                         | ✅                                              |\n\n## Advanced\n\n### Config\n\nYou can persist preferences to a config file:\n\n- `$XDG_CONFIG_HOME/hunk/config.toml` (default `~/.config/hunk/config.toml`)\n- `.hunk/config.toml` in a repository\n\nPersistent state uses `$XDG_STATE_HOME/hunk/state.json` when the variable is\nabsolute, or `~/.local/state/hunk/state.json` otherwise. Hunk preserves existing\nstate by copying the old config-directory file on first use, keeping the original\nand falling back to it if migration fails. Global extension paths are unchanged.\n\nFor example:\n\n```toml\ntheme = \"github-dark-default\"\nmode = \"auto\"               # auto, split, unified\nauto_split_min_width = 160  # minimum available diff columns for split in auto mode\nline_numbers = true\nwrap_lines = false\n```\n\nSee the [configuration guide](https://hunk.dev/docs/configure/configuration/) for command overrides and examples, the [full reference](https://hunk.dev/docs/reference/config/) for every option, or the [theme guide](https://hunk.dev/docs/configure/themes/) to customize colors.\n\n### Keybindings\n\nRemap shortcuts with a `[keybindings]` table in your user config. See [the keybinding guide](https://hunk.dev/docs/configure/keybindings/) for available commands, default keys, and examples.\n\n### Git integration\n\nSet Hunk as your Git pager so `git diff` and `git show` open in Hunk automatically:\n\n> [!NOTE]\n> Untracked files are auto-included only for Hunk's own `hunk diff` working-tree loader. If you open `git diff` through `hunk pager`, Git still decides the patch contents, so untracked files will not appear there.\n\n```bash\ngit config --global core.pager \"hunk pager\"\n```\n\nOr in your Git config:\n\n```ini\n[core]\n    pager = hunk pager\n```\n\nIf you want to keep Git's default pager and add opt-in aliases instead:\n\n```bash\ngit config --global alias.hdiff \"-c core.pager=\\\"hunk pager\\\" diff\"\ngit config --global alias.hshow \"-c core.pager=\\\"hunk pager\\\" show\"\n```\n\n### Jujutsu pager integration\n\nTo use Hunk as jj's pager, run `jj config edit --user` and update:\n\n```toml\n[ui]\npager = [\"hunk\", \"pager\"]\ndiff-formatter = \":git\"\n```\n\n### Sapling pager integration\n\nTo use Hunk as Sapling's pager, run `sl config -u` and update:\n\n```ini\n[pager]\npager = hunk pager\n```\n\n### Extensions (experimental)\n\nThe extension API is experimental and may change in breaking ways between\nminor releases while it stabilizes; breaking changes are called out in\nrelease notes.\n\nHunk loads plain TypeScript extensions from `~/.config/hunk/extensions/`, from a\nrepository's `.hunk/extensions/` (after you explicitly trust that repository),\nand from `--extension <path>` for development. `--no-extensions` turns those off\nfor one run; Hunk's own bundled backends (Git, Jujutsu, and Sapling) stay loaded.\n\nExtensions can add commands, themes, VCS backends, custom sidebars, and more. For example, filter lockfiles out of a review:\n\n```ts\n// ~/.config/hunk/extensions/collapse-lockfiles.ts\nimport type { HunkExtensionAPI } from \"hunkdiff/extension\";\n\nexport default function (hunk: HunkExtensionAPI) {\n  hunk.transformChangeset((changeset, ctx) => {\n    const files = changeset.files.filter((file) => !file.path.endsWith(\".lock\"));\n    ctx.notify(`Collapsed ${changeset.files.length - files.length} lockfiles`);\n    return { ...changeset, files };\n  });\n}\n```\n\nExtensions shared as git repositories install straight from their host, and a\n`hunk-extension` GitHub topic marks community ones:\n\n```bash\nhunk extension install acme/hunk-word-diff@v1.2.0   # or git:host/path, a URL, a local path\nhunk extension list                                 # then update [name] / remove <name>\n```\n\nBrowse community extensions at\n[github.com/topics/hunk-extension](https://github.com/topics/hunk-extension);\npublish yours by pushing the extension to a repository root and adding that\ntopic.\n\nSee [the extension guide](https://hunk.dev/docs/extend/extensions/) for the API, configuration, and publishing instructions.\nInstallable examples include\n[review triage](https://github.com/modem-dev/hunk/tree/main/examples/extensions/review-triage),\n[review snapshot export](https://github.com/modem-dev/hunk/tree/main/examples/extensions/review-snapshot-export), an optional\n[rendered Markdown file view](https://github.com/modem-dev/hunk/tree/main/examples/extensions/rendered-markdown), and a\n[Vim navigation mode](https://github.com/modem-dev/hunk/tree/main/examples/extensions/vim-navigation) built from public semantic commands.\n\n### OpenTUI component\n\nHunk also publishes `HunkDiffView` and lower-level primitives from `hunkdiff/opentui` for embedding the same diff renderer in your own OpenTUI app.\n\nSee [the OpenTUI component guide](https://hunk.dev/docs/reference/opentui-components/) for install, API, and runnable examples.\n\n## Examples\n\nReady-to-run demo diffs live in [the examples directory](https://github.com/modem-dev/hunk/tree/main/examples).\n\nEach example includes the exact command to run from the repository root.\n\n## Contributing\n\n💬 _Chat with users/contributors on the [Modem Discord server](https://discord.gg/WZFjaP6Gt8)_\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution guidelines.\n\n## Sponsor\n\nSponsored by [Modem](https://modem.dev?utm_source=github&utm_medium=oss&utm_campaign=oss_hunk&utm_content=readme_footer).\n\n<a href=\"https://modem.dev?utm_source=github&utm_medium=oss&utm_campaign=oss_hunk&utm_content=readme_footer\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://modem.dev/images/logo/svg/modem-combined-white.svg\">\n    <source media=\"(prefers-color-scheme: light)\" srcset=\"https://modem.dev/images/logo/svg/modem-combined-black.svg\">\n    <img src=\"https://modem.dev/images/logo/svg/modem-combined-black.svg\" alt=\"Modem\" width=\"220\">\n  </picture>\n</a>\n\n## License\n\n[MIT](https://github.com/modem-dev/hunk/blob/main/LICENSE)\n","packages/hunk/skills/hunk-extensions/SKILL.md":"---\nname: hunk-extensions\ndescription: Maps the `hunkdiff/extension` authoring surface for Hunk, the terminal diff viewer — hiding or reordering reviewed files, docked panes, alternate file views, commands and key bindings, dialogs, workspace writes, themes, syntax languages, VCS backends, lifecycle events. Use when writing, debugging, or installing a Hunk extension, or when a request asks Hunk itself to behave differently. Not for reviewing a diff in a live session — that is hunk-review.\n---\n\n# Building Hunk extensions\n\nA Hunk extension is **one TypeScript (or JSX/JS) file that default-exports a\nfactory**. Hunk imports it at startup and hands it an API object. No build step,\nno manifest required.\n\n```ts\n// ~/.config/hunk/extensions/hello.ts\nimport type { HunkExtensionAPI } from \"hunkdiff/extension\";\n\nexport default function (hunk: HunkExtensionAPI) {\n  hunk.on(\"startup\", (_event, ctx) => ctx.notify(\"Hello\"));\n}\n```\n\nThis skill is a map of the touchpoints, not a recipe. Decide what to build from\nthe user's request; use the table below to find the call, then read the linked\nmaterial before writing code.\n\n## Sources of truth — read before writing\n\n| Source                                     | What it answers                                              |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `docs/extensions.md`                       | The authoring guide. Every call, every rule. Start here.     |\n| `packages/hunk/src/extension-api/types.ts` | The contract — exact field names, optionality, doc comments. |\n| `examples/extensions/*`                    | Working extensions. Copy these patterns rather than invent.  |\n| `docs/extension-architecture.md`           | Hunk's internals. Needed only when changing the host.        |\n| `docs/keybindings.md`, `docs/themes.md`    | Chord grammar and theme token rules that extensions inherit. |\n\nOutside a Hunk checkout the guide is split across\n<https://hunk.dev/docs/extend/extensions/> (discovery, trust, config) and its\ncompanion pages — extension-api, file-previews, vcs-adapters, custom-panes —\nand the contract ships as `node_modules/hunkdiff/dist/npm/extension/index.d.ts`.\n\nThe examples, by what they demonstrate:\n\n- `review-triage/` — pane + commands + all three dialog shapes + lifecycle\n  events + the extension event bus + a `useSyncExternalStore` bridge.\n- `inline-edit/` — an interactive file-view `mode` driving `ctx.workspace` writes;\n  its README explains the async lifetime rules better than anything else in tree.\n- `rendered-markdown/` — a file view producing host-rendered rows from parsed\n  Markdown, and a folder extension with an npm dependency.\n- `code-document-file-view/` — API-v28 host-owned syntax paint over complete old/new\n  code documents, including semantic gutters and partial UTF-16 ranges.\n- `jsx-file-view/`, `jsx-file-view-gallery/` — the experimental fixed-height JSX\n  row component contract.\n\n## Where extensions live\n\n| Source                                     | Trust            |\n| ------------------------------------------ | ---------------- |\n| `--extension <path>` (repeatable)          | runs immediately |\n| `[extensions] paths` in user config        | runs immediately |\n| `~/.config/hunk/extensions/` (XDG-aware)   | runs immediately |\n| `.hunk/extensions/` or repo-config `paths` | **trust prompt** |\n\nOnly the repo-local group is gated. Everything else — including `--extension`,\neven when its path points inside the repository under review — is read as\nexplicit user intent and executes with full user permissions, no prompt. Never\npass or suggest a path you have not read, including one copied from a\nrepository's own README.\n\nA directory matches `*.ts`/`*.tsx`/`*.js`/`*.jsx`/`*.mjs` at its top level, plus\none level of folder extensions. A folder is an extension if it has a\n`package.json` with `{\"hunk\": {\"extensions\": [\"./index.ts\"]}}`, or an\n`index.{ts,tsx,js,jsx,mjs}`. Reach for a folder only when you need npm\ndependencies, helper modules, or a README; a single file keeps the install to one\n`cp`. A `.hunk/extensions/` folder extension's `node_modules` has to exist on\nevery machine that loads it — keep a repo-shared extension dependency-free.\n\nShared extensions install from git with `hunk extension install <source>`\n(`owner/repo[@ref]`, `git:host/path[@ref]`, a git URL, or a local path) into\n`~/.config/hunk/extensions/installed/<repo-name>/`, where they load with global\norigin; `list`, `update`, and `remove` manage them. Declared `dependencies` are\n`bun install`ed at install time. The manifest may state\n`{\"hunk\": {\"apiVersion\": N}}` — the minimum extension API version — and an older\nHunk refuses the extension with a startup notice instead of failing mid-factory.\nTo publish, push the folder-extension layout to a git repository's root with\nreal `name`/`version`/`description`, tag releases for `@ref` pins, and add the\n`hunk-extension` GitHub topic so it appears at\n<https://github.com/topics/hunk-extension>.\n\nThe **id** is the file stem, or the folder name for a folder extension — unless\nits manifest declares several entries, in which case each entry is its own\nextension named by its own stem (numeric suffix on collision). The id is the\nnamespace it owns: commands are `<id>.<commandId>`, panes and keyboard modes\nare `<id>:<localId>`, config `[extension.<id>]`. Ids match\n`/^[A-Za-z0-9][A-Za-z0-9_-]*$/`; `hunk`, `git`, `jj`, and `sl` are reserved. A\nbad or duplicate id is skipped with a startup notice. `[extensions] disabled = [\"<id>\"]`\nfilters a user extension before import or trust prompting. `--disable-extension <id>` and\n`--enable-extension <id>` are ordered one-run overrides; `--no-extensions` remains the hard switch\nfor all user extensions. Bundled capabilities use separate host-owned selection ids such as\n`hunk.gh`, even when their public registrations remain under the `hunk` namespace.\n\n## Pick the touchpoint\n\n| To do this                                               | Call                                                          |\n| -------------------------------------------------------- | ------------------------------------------------------------- |\n| Keep demo/training view settings temporary               | `hunk.configureSession(options)`                              |\n| Add a selectable color theme                             | `hunk.registerTheme(theme)`                                   |\n| Highlight an extension, exact filename, or filename glob | `hunk.registerFileLanguage(matcher, lang)`                    |\n| Support another VCS (`git`/`jj`/`sl` are reserved)       | `hunk.registerVcsAdapter(adapter)`                            |\n| Report read-only workspace and sibling status            | Optional `registerVcsAdapter({ status })` capability (API 31) |\n| Add a navigation/list/status pane beside the review      | `hunk.registerPane(pane)`                                     |\n| Present a file as something other than a raw diff        | `hunk.registerFileView(view)` (experimental)                  |\n| Mark character ranges inside diff lines                  | `hunk.registerLineHighlighter(highlighter)`                   |\n| Interpret review keys as a temporary global mode         | `hunk.registerKeyboardMode(mode)`                             |\n| Add a generic top-level CLI command tree                 | `hunk.registerCliCommand(command, handler)`                   |\n| Bind a key / add an Extensions-menu entry                | `hunk.registerCommand(command, handler)`                      |\n| Show persistent text on the bottom status row            | `ctx.statusLine.set(item)` in a handler                       |\n| Ask for one line of text inline, `less`-style            | `ctx.prompts.line(options)` in a command                      |\n| Hide, reorder, retitle files before review               | `hunk.transformChangeset(fn)`                                 |\n| React to loads, selection, view movement, notes, reloads | `hunk.on(event, handler)`                                     |\n| Coordinate with another loaded extension                 | `hunk.events.emit` / `hunk.events.on`                         |\n| Reload after an external agent changes reviewed inputs   | `ctx.review.requestReload()` in an event                      |\n| Read user-supplied settings                              | `hunk.config` (`[extension.<id>]` table)                      |\n| Snapshot stable files and every saved review note        | `ctx.review.snapshot()` in a command                          |\n| Branch on the API generation (currently `32`)            | `hunk.apiVersion`                                             |\n\nRegistration is only valid while the factory runs — Hunk seals the API object\nafterwards.\n\nPromise-returning VCS `watchSignature` hooks and watch cancellation require API\nversion 25. Declare `{\"hunk\": {\"apiVersion\": 25}}` in the manifest, or branch on\n`hunk.apiVersion` and return signatures synchronously on older hosts. Use async\nI/O and honor `ctx.signal` on API 25; existing synchronous hooks remain supported.\n\n### Workspace status providers (API 31)\n\nThe optional `registerVcsAdapter({ status })` capability offers cancellable, read-only\n`read`, `readSiblings`, and `planReview` operations, plus optional async `watchPlan`.\nRead [Workspace status capability](../../../../docs/extensions.md#workspace-status-capability)\nand the `ExtensionVcsStatusCapability` contract before implementing it.\n\n- Action ids are supported semantic ids, not opaque strings: `staged` reviews the index;\n  `unstaged` reviews the working copy. Offer each at most once with provider-owned labels.\n  A no-index provider offers only `unstaged` and omits `paths[].index`.\n- Return `{ cwd, input: ExtensionVcsStatusReviewInput }` from `planReview`. Preserve the\n  inspected target and opaque token validation. This exported type excludes pathspecs while\n  preserving exclusive `range` / `rangeEndpoints` comparisons. Any `pathspecs` field, even\n  `[]` or `undefined`, rejects; `input.staged` must match the offered action id.\n- Sibling ids and paths must each be unique and share the current `repositoryId`. If a row\n  matches the current worktree's id or path, both must match. Embedded ready sibling facts\n  and `readSiblings` results follow the same checks; unavailable/error rows never mean clean.\n- Bound collections before returning: 20,000 changed paths, 100 sibling rows, five operations,\n  256 watch targets, four sources per target, 20,000 watch roots/entries per target, and\n  40,000 aggregate collection entries per result (including nested and outer arrays).\n  Defensive action-array validation caps at 32, but only two unique ids are supported.\n  Oversized results reject before per-element schema traversal. Mark bounded sibling scans\n  `truncated`; honor `context.signal`, bound I/O, and reap subprocesses.\n\n### Generic CLI handlers\n\nRegister one lowercase-kebab top-level token; the handler owns every raw token\nbelow it. Built-ins and aliases cannot be shadowed, and discovery order makes\nthe first extension claim win. During development, place the explicit path\nbefore the extension command:\n\n```bash\nhunk --extension ./my-ext.ts my-command sync --help\n```\n\nThe handler receives frozen args plus `ctx.cwd`, `ctx.signal`, streaming\n`ctx.stdin`, and leased `ctx.stdout`/`ctx.stderr` writers. `summary` and\n`usage` are listed when a token reaches discovery unclaimed, so write them as\none short line each. Return `{ kind:\n\"exit\", code? }` or `{ kind: \"delegate\", argv: [\"diff\", ...] }`. Delegation is\nbuilt-in-only and one-time: do not write stdout or read stdin before delegating;\nuse stderr for progress. Reading stdin is an exit-only workflow. Respect cancellation promptly.\nRepo-local providers remain trust-gated; `--no-extensions` performs no discovery\nor import, while a leading explicit `--extension` path is immediate consent.\n\nUse `packages/hunk-gh/src/` as the reference for a complete bundled CLI preprocessor: direct\nauthenticated HTTP with cancellation, temporary artifacts with platform-accurate permission claims\nretained through delegated startup, cleanup on `shutdown`, and a one-time handoff to built-in\n`patch` without touching stdin or stdout.\n\n## What handlers receive\n\nEvery event, bus, command, and file-view mode handler — plus every changeset\ntransform — gets `ctx.cwd` and `ctx.notify(message, type?)`. A file view's\n`matches` and `layout` get no context at all. Beyond that:\n\n- **Event and bus handlers** also get `ctx.panes` (open/close/toggle/isOpen on\n  any pane), live `ctx.navigation`, attributed `ctx.dialogs`, `ctx.statusLine`\n  (set/clear this extension's status-row items), review reloads through\n  `ctx.review.requestReload()`, and\n  `ctx.events.emit`. `ctx.sidebars` is a deprecated alias for `ctx.panes`.\n- **Command handlers** get `ctx.panes`, `ctx.fileViews` (select/toggle/isActive/\n  refresh/enterMode/exitMode), `ctx.highlights` (refresh prepared line marks,\n  whole or `{ fileId }`-scoped), `ctx.selection` (a snapshot of file, hunk index,\n  nullable current `{ side, line }` source address, and `files`, the visible files\n  in review order), `ctx.navigation` (live,\n  guarded `selectFile`/`selectHunk`/`revealLine`, the\n  last landing one exact `(side, line)` near the viewport top), `ctx.commands`\n  (`isEnabled`/`execute` for public semantic `hunk.*` commands),\n  `ctx.keyboardModes` (enter/exit/probe this extension's session modes), `ctx.review`\n  (deeply immutable snapshots of stable files and complete saved store notes),\n  `ctx.dialogs` (`confirm`/`select`/`input`, queued and attributed),\n  `ctx.statusLine` (set/clear persistent status-row items), `ctx.prompts`\n  (`line`: an inline status-row input resolving the text or `null`, queued and\n  attributed like dialogs), and\n  `ctx.workspace` (`readDocument`, `canWriteDocument`, `writeDocument` with consent).\n- **Pane components** get frozen `files`, selection, placement, exact dimensions,\n  nullable immutable delegated-source `review` metadata, optional `currentLine` paint\n  (with `{ side, line }` when opted in), semantic `theme`, resolved `keybindings`, and\n  guarded navigation/notification `actions`. Availability callbacks receive the same\n  `review` value, so a pane can consume no geometry for ordinary reviews.\n- **File-view `layout`** gets `file`, `width`, `signal`, `changes`, and a lazy\n  `readDocument(side)`.\n- **File-view `mode` handlers** get `ctx.file` and `ctx.fileViews`. `onKey`,\n  `onEnter`, and `onExit` must answer **synchronously** — `onKey`'s return value\n  (`\"handled\"`/`\"pass\"`/`\"exit\"`) is the routing decision, so kick off async work\n  and report it later through `notify` or `refresh`. A passed key reaches any\n  active session keyboard mode before ordinary Hunk routing. Escape is host-owned\n  and never reaches `onKey`.\n- **Session keyboard-mode handlers** get only `ctx.commands`, `ctx.highlights`,\n  `ctx.statusLine`, and activation-scoped `ctx.keyboardModes` beyond the standard\n  context. A prompt-shaped interaction is a command plus `ctx.prompts.line()`,\n  not a mode. Those controls become inert on\n  exit, and lifecycle callbacks cannot change keyboard ownership. Keys are frozen\n  snapshots; dialogs, focused inputs, and file-view modes outrank them. When the\n  session mode owns input, Escape exits it; the status badge and Extensions menu\n  are unconditional host-owned exits.\n\nEvent payloads, pane props, and a command's selection all hand you frozen\n`ExtensionDiffFile` / `ExtensionDiffHunk` views. A changeset transform is the\nexception: it receives the live changeset and is expected to return a new one.\n`metadata` is unfrozen either way — it is the renderer's parsed diff, so pass it\nthrough untouched.\n\n## Rules that bite\n\nMost extension bugs are one of these:\n\n- **Registering a surface does not show it.** Panes need `defaultOpen`,\n  `replaces: \"hunk:files\"`, or a command that opens them. File views remain raw\n  until selected from the **View** menu.\n- **A rejected file-view layout silently becomes raw diff.** `hunkRows` needs one\n  in-bounds, inclusive entry per parsed hunk at the same array index, and\n  `sourceRanges` may not overlap on a side; invalid, oversized, cancelled, and\n  throwing layouts warn once and fall back.\n- **Syntax paint stays declarative.** File views declare complete `codeDocuments` and\n  map exact symbolic spans with `syntax`; Hunk owns tokenization, theme colors,\n  visible-window demand, resource limits, and plain fallback. Keep gutters and\n  separators in non-syntax spans, and use `sourceRanges` separately for notes/navigation.\n- **Never bundle or vendor React.** Hunk serves its own `react` and `@opentui/*`\n  to extension files; a second copy means a second hooks dispatcher and the\n  component fails to render. Import them normally. OpenTUI intrinsics (`box`,\n  `text`, `scrollbox`) need no import.\n- **`layout` is a pure derivation of `(file, width)`.** A stateful view keeps\n  painting its first answer until `ctx.fileViews.refresh(viewId)` — scope it with\n  `{ fileId }` when the state belongs to one file.\n- **Handler state must live outside the component.** Panes unmount when closed;\n  bridge module-level state into React with `useSyncExternalStore` and immutable\n  snapshots (`review-triage/index.tsx` is the working version).\n- **Use `ctx.review.snapshot()` for complete saved-note state.** `note_created` and\n  `note_edited` are incremental UI events, not an authoritative collection. Snapshots\n  include stale and orphaned saved notes, exclude drafts and static sidecar annotations,\n  and should be re-read before irreversible async work; compare both generation and revision.\n  `review-note-navigator` shows how to join stable note ids and file keys back to guarded\n  navigation after awaiting a selector; file filters can still refuse hidden targets.\n- **Retained review controls expire on reload.** An old handler cannot control\n  replacement content: pane/navigation calls become inert, dialogs cancel, and\n  workspace reads or not-yet-started writes return `null`/`unavailable`. A\n  consented write already in progress reports its real outcome, holds graceful\n  exit until it settles, and reconciles the active review on success. `shutdown`\n  runs after revocation, so use it only\n  to release extension-owned resources.\n- **A reload keeps your factory and renames the files.** Factories re-run only\n  after a trust grant or a cwd change, so module state survives — but a file's\n  `id` encodes its position in the changeset, so a reload that adds or drops a\n  file renumbers the rest. Key durable per-file state by `path`, or reconcile it\n  on `changeset_loaded`. Pick one deliberately.\n- **Transforms must preserve `metadata`** (spreading a file does), keep ids\n  unique, and return a real changeset — otherwise the transform is skipped with a\n  warning and the previous changeset carries forward.\n- **Chords are defaults.** Users remap by command id in `[keybindings]`; built-ins\n  win conflicts, refused one chord at a time. Bind the character shift produces\n  (`\"!\"`, not `\"shift+1\"`).\n- **Keyboard modes are grammar, not behavior.** Keep pending sequences and\n  numeric prefixes in the extension, then call one public `ctx.commands.execute`\n  after resolving an action. `vim-navigation` demonstrates counts, Ctrl chords,\n  and a `:` key passed to a registered command whose host input dialog temporarily\n  outranks the still-active mode.\n- **`ctx.commands` invokes Hunk, not other extensions.** Probe with\n  `isEnabled(\"hunk.review.nextHunk\")`, then call `execute(id, { count })` for an\n  explicitly public built-in. Counts are positive whole numbers up to 10,000,\n  applied atomically to movement; one-shot actions run once. Unknown, disabled,\n  private, extension-owned, or stale commands return `false`.\n- **Repo config can set `[extension.<id>]` for a globally installed extension.**\n  Treat `hunk.config` as untrusted for anything exec-adjacent (binary paths,\n  shell commands, module loading).\n- **`ctx.workspace` writes only apply to reloadable, unstaged working-tree\n  reviews**, by reviewed file id, inside the review root, with consent. Everything\n  else returns `{ ok: false, reason }` — check `canWriteDocument` first.\n- **File-view note placement is all-or-raw per file**: an unbound or range-less\n  visible note makes Hunk render the complete raw diff instead of guessing.\n- **Failures are contained, not sandboxed.** A throwing factory is rolled back to\n  zero registrations and a throwing handler is a warning naming the extension —\n  containment against bugs, not against code that should not have been loaded.\n- **The API touches nothing outside the review.** No clipboard, no filesystem, no\n  process surface beyond `ctx.workspace` — an extension is ordinary code, so shell\n  out for the rest. Never write to stdout: the renderer owns it. For the same\n  reason `hunk.log` is collected as diagnostics and printed nowhere; `ctx.notify`\n  is how a user hears from you.\n- **`HunkExtensionUserError`** (detected structurally by `name`) buys the full\n  treatment — message plus `suggestions`, no stack trace — only from a VCS adapter\n  operation, which is where Hunk formats it for the CLI. From a command or event\n  handler only the message survives, as a warning toast.\n\n## Verifying\n\nHunk's TUI needs a real terminal, and the review UI is the user's — **do not\nlaunch `hunk diff`/`hunk show` to test, and do not reach for a pipe.** No\ninvocation applies extensions headlessly: `hunk diff … | cat` still starts the\napp and still takes the keyboard, so it hangs holding the user's terminal.\nPractical checks, in order of cost:\n\n1. **Typecheck.** In a checkout, `bun run typecheck` covers\n   `examples/extensions/**` via the `hunkdiff/extension` path mapping. Standalone,\n   add `hunkdiff` as a dev dependency and run `tsc --noEmit`; for a `.tsx`\n   extension also add `react`, `@types/react` (React ships no declarations of its\n   own), `@opentui/core`, and `@opentui/react` as **dev** dependencies and set\n   `\"jsx\": \"react-jsx\"` with\n   `\"jsxImportSource\": \"@opentui/react\"`, or every `<box>` and `<text>` is an\n   untyped intrinsic. Types only — shipping those packages is the second-React bug.\n2. **Unit-test the logic.** When parsing, matching, or formatting is worth\n   testing, put it in helper modules with plain `bun test` coverage.\n3. **PTY integration.** In a checkout, `test/pty/extensions-integration.test.ts`\n   launches Hunk over a PTY with `--extension <path>` and asserts on rendered\n   snapshots; extend it via `test/pty/harness.ts` and run `bun run test:integration`.\n4. **Hand it to the user** to run: `hunk diff --extension ./my-ext`. `--extension`\n   loads immediately with no trust prompt, so it is the iteration path. Ask them\n   what the footer notices and toasts said.\n5. **Triage with `--no-extensions`** to confirm a symptom belongs to an extension\n   (bundled VCS backends, the built-in files pane, and the `/` content search stay loaded\n   either way).\n\n## If it does not load\n\n- No startup notice at all → a successful load is silent, so either it loaded and\n  nothing opened it, or discovery never saw the file. Check the directory, the\n  entry suffix, or the folder's `package.json` `hunk.extensions` paths.\n- Notice naming the extension → id rejected (reserved, malformed, or already\n  claimed), import failure, missing default export, or a throwing factory.\n- Repo-local extension silently absent → the trust prompt was dismissed or denied;\n  decisions are stored per repo root in `~/.config/hunk/state.json`.\n- Pane closes with a toast → the component threw; a second React copy is\n  the usual cause.\n- Pane or file view never appears → nothing opened it (no `defaultOpen`, no\n  command), `matches` returned false, or the layout was rejected.\n- Command never fires → its chord lost to a built-in or an earlier extension (a\n  warning says so); it is still reachable from the **Extensions** menu and\n  bindable by `<id>.<commandId>`.\n\n## Changing Hunk itself\n\nOnly when the work is in the `hunk` repo rather than in a user extension:\n\n- Shipped VCS backends, `hunk gh`, the built-in files pane, and the `/` content search are\n  **bundled extensions**, registering through the same public API. That dogfooding is deliberate —\n  if the public contract cannot express something, that is a real gap, not a reason for a private\n  path. `default/vcs/` loads from VCS adapter resolution and must stay renderer-free;\n  `default/core/` loads session-owned CLI capabilities from private workspaces such as\n  `packages/hunk-gh`; and `default/ui/` holds process-cached rendering registrations.\n- `packages/hunk/src/extension-api/types.ts` must stay **import-free**; declaration emission\n  publishes whatever it reaches, and `scripts/packaging/check-pack.ts` fails the pack\n  otherwise. Shapes shared with internal code are declared there and re-exported\n  inward.\n- New API surface means updating `docs/extensions.md` (its examples are\n  typechecked as consumer code), the matching hand-written page under\n  `website/src/content/docs/docs/extend/` (only `cli.md` and `config.md` are\n  generated), `docs/extension-architecture.md` if ownership moves, and a changeset.\n- `AGENTS.md` and `docs/extension-architecture.md` own the rest of these rules.\n","packages/hunk/skills/hunk-review/SKILL.md":"---\nname: hunk-review\ndescription: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files, hunks, and exact lines, reloads session contents, adds inline review comments, and paints attention marks on character ranges. 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\n10. hunk session highlight add ...                      # light up the exact range you are explaining\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 (<session-id> | --repo <path>) [--json]\nhunk session context (<session-id> | --repo <path>) [--json]\nhunk session review (<session-id> | --repo <path>) [--include-patch] [--include-notes] [--json]\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- `review --include-notes` also returns the live review notes alongside the file and hunk structure\n\n### Navigate\n\n```bash\nhunk session navigate (<session-id> | --repo <path>) --file <path> (--hunk <n> | --old-line <n> | --new-line <n>) [--json]\nhunk session navigate (<session-id> | --repo <path>) --comment <id> [--json]\nhunk session navigate (<session-id> | --repo <path>) (--next-comment | --prev-comment) [--json]\n```\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\nExact comment navigation uses the `commentId` returned by `hunk session comment list --json` and does not require `--file`:\n\n```bash\nhunk session navigate --repo . --comment comment-1\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- A line target lands the user's viewport on that exact line (falling back to its hunk when the line is inside a collapsed region); `--hunk` lands on the hunk\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 (<session-id> | --repo <path> | --session-path <path>) [--source <path>] [--json] -- diff [ref] [-- <pathspec...>]\nhunk session reload (<session-id> | --repo <path> | --session-path <path>) [--source <path>] [--json] -- show [ref] [-- <pathspec...>]\n```\n\nExamples:\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 (<session-id> | --repo <path>) (--reply-to <note-id> | --file <path> (--old-line <n> | --new-line <n>)) --summary <text> [--rationale <text>] [--author <name>] [--markup <stml>] [--focus] [--json]\nhunk session comment apply (<session-id> | --repo <path>) --stdin [--focus] [--json]\nhunk session comment list (<session-id> | --repo <path>) [--file <path>] [--type <live|all|ai|agent|user>] [--json]\nhunk session comment rm (<session-id> | --repo <path>) <comment-id> [--json]\nhunk session comment clear (<session-id> | --repo <path>) [--file <path>] [--include-user|--all] --yes [--json]\n```\n\nExamples:\n\n```bash\nhunk session comment add --repo . --file README.md --new-line 103 --summary \"Tighten this wording\"\nhunk session comment add --repo . --reply-to user:123 --summary \"Addressed in the latest revision\"\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' | hunk session comment apply --repo . --stdin\n```\n\n- Agent notes start hidden: the user sees them only after pressing `a` or setting `agent_notes = true`. `--focus` jumps to the new note (or the first note in a batch) and turns agent notes on, so pass it on the first note you leave in a session, or tell the user to press `a`\n- `comment list` shows the comments added through `hunk session comment` by default; `--type user` lists notes the user wrote with `c`, `--type ai` lists notes loaded from an `--agent-context` sidecar, `--type agent` lists agent-authored notes, and `--type all` lists every note\n- `comment add` is best for one note; `comment apply` is best when an agent already has several notes ready\n- Root `comment add` notes require `--file`, `--summary`, and exactly one of `--old-line` or `--new-line`; replies use `--reply-to <note-id>` with `--summary` and inherit the parent's anchor\n- `comment apply` items require `summary` plus either `replyTo` by itself or `filePath` with 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- `comment list` and `comment clear` accept optional `--file`\n- Quote `--summary` and `--rationale` defensively in the shell\n\n### Attention marks\n\nHighlights paint character ranges inside the diff lines the user is looking at — use them to light up the exact expression you are explaining while you narrate.\n\n```bash\nhunk session highlight add (<session-id> | --repo <path>) --file <path> (--old-line <n> | --new-line <n>) --start <n> --end <n> [--tone <tone>] [--focus] [--json]\nhunk session highlight clear (<session-id> | --repo <path>) [--file <path>] [--json]\n```\n\nExamples:\n\n```bash\nhunk session highlight add --repo . --file src/App.tsx --new-line 42 --start 6 --end 19\nhunk session highlight add --repo . --file src/App.tsx --new-line 42 --start 6 --end 19 --tone warning --focus\nhunk session highlight clear --repo .\n```\n\n- `highlight add` requires `--file`, exactly one of `--old-line` or `--new-line`, and the `--start` / `--end` offsets\n- `--start` is a 0-based inclusive offset into the line's text and `--end` is exclusive, counted in UTF-16 code units — the same `[start, end)` range extensions use\n- Tones: `match` (default), `info`, `warning`, `error`, `dim`; `current` renders as reverse video and is best reserved for the one range under discussion\n- Pass `--focus` to also land the viewport on the marked line\n- Marks survive scrolling, navigation, and reloads that leave the marked file's content unchanged; a reload that changes that file drops its marks, and `highlight clear` removes them explicitly (optionally per `--file`)\n- Marks are visual only — pair them with a `comment add` when the explanation should persist as a note\n\n### Experimental rich markup notes (STML)\n\nOnly use STML when `hunk session context --json` lists `stml` in `experimentalFeatures`. The user opts into that experience by launching the review with `--experimental`; do not ask a normal session to render markup.\n\nFor an opted-in session, `--markup` (or a `markup` field on apply items) renders the note body as STML — a small HTML-like markup for terminal UI (boxes, rows, gauges, badges, lists, code). Keep `--summary` a real sentence: it is the fallback and the `comment list` text.\n\nBefore writing markup, run `hunk markup guide` once — it has copy-paste patterns and the width rules. The session context also reports `noteMarkupWidth` (the live render width); preview with `hunk markup render - --width <that>`. Comment responses echo `markupWidth` and return `markupNotes` when markup degraded — fix what they flag.\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 `highlight add --focus` to steer the user's eyes to the exact expression while you explain it, and `highlight clear` before moving to the next topic\n- Use `comment apply` for agent-generated batches and `comment add` for one-off notes\n- Use `--focus` on your first note so agent notes become visible, then sparingly when a note 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 diff file matches ...\"** -- the file is not in the loaded review. Check `context`, then `reload` if needed.\n- **\"No active Hunk sessions\"** -- if Hunk is visibly running, localhost may be blocked by the agent sandbox; retry with network/sandbox escalation. Otherwise ask the user to open Hunk.\n- **\"Multiple active sessions match\"** -- pass `<session-id>` explicitly.\n- **\"No active 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 exactly one comment target\"** -- pass `comment add` one of `--old-line` or `--new-line`.\n- **\"Specify exactly one highlight target\"** -- pass `highlight add` one of `--old-line` or `--new-line`.\n- **\"Highlight --end must be greater than --start\"** -- offsets are `[start, end)` UTF-16 code units into the line text; end is exclusive.\n- **\"Specify either --next-comment or --prev-comment, not both.\"** -- choose one comment-navigation direction.\n- **\"The session daemon is ...\"** -- a `daemon-build-mismatch` (the `--json` error carries `daemon`, `cli`, `attachedSessions`, and `recommendedAction`). Tell the user which build is newer and how many windows are attached, then **ask** before running `hunk daemon restart --yes`; never restart unprompted. After the restart, windows that failed to register attach on their own, so re-run `hunk session list` instead of relaunching anything. When `recommendedAction` is `use-newer-hunk`, the daemon is the newer build: use that Hunk instead.\n- **\"Could not read the raw diff for ...\"** -- the session reloaded or closed while `--include-patch` was reading it. Re-run `review`; drop `--include-patch` if you only need file and hunk structure.\n","packages/session-broker-bun/README.md":"# @hunk/session-broker-bun\n\nPrivate Bun HTTP and websocket adapter for `@hunk/session-broker`.\n\nUse this workspace to serve a runtime-neutral `SessionBrokerDaemon` through `Bun.serve(...)`.\nIt is not a published, versioned SDK package. See the main broker README for current internal API\nconstruction and the [session broker SDK contract](../../docs/session-broker-sdk.md) for the future\npublic package.\n\n## What it does\n\n- binds a broker daemon to a Bun HTTP server\n- upgrades websocket requests on the daemon socket path\n- forwards websocket messages and close events into the daemon\n- exposes a `stopped` promise compatible with Hunk's daemon lifecycle\n- lets callers override or add custom HTTP routes before the daemon's built-in routes\n\n## Usage\n\nCreate the daemon with steps 1–2 in\n[`@hunk/session-broker`](../session-broker/README.md), then bind it to Bun:\n\n```ts\nimport type { SessionBrokerDaemon } from \"@hunk/session-broker\";\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-bun\";\n\ndeclare const daemon: SessionBrokerDaemon;\n\nconst server = serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n## Custom routes\n\nYou can override or extend request handling with `handleRequest`.\n\n```ts\nconst server = serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n  handleRequest: async (request) => {\n    const url = new URL(request.url);\n    if (url.pathname === \"/health\") {\n      return Response.json({ ok: true, overridden: true });\n    }\n\n    return undefined;\n  },\n});\n```\n\nReturn `undefined` to fall through to the daemon's built-in routes. The raw `/broker` HTTP API is available only when the daemon was created with `exposeHttpApi: true`.\n\n## License\n\nMIT\n","packages/session-broker-core/README.md":"# @hunk/session-broker-core\n\nLow-level shared primitives for the session broker packages.\n\nThis package is an **internal foundation layer**, not the main entrypoint you should build against in most cases.\n\n## Use this package when\n\n- you are working on broker internals\n- you need the shared envelope types and parsers directly\n- you are implementing a higher-level broker package on top of it\n\n## Prefer these packages for normal use\n\n- `@hunk/session-broker` — main runtime-neutral broker API\n- `@hunk/session-broker-bun` — Bun runtime adapter\n- `@hunk/session-broker-node` — Node runtime adapter\n\n## What this package includes\n\n- shared session envelope types\n- registration and snapshot wire parsing helpers\n- low-level in-memory `SessionBrokerState`\n- selector helpers for `sessionId`, `sessionPath`, and `repoRoot`\n- generic terminal metadata capture\n\n## What this package does not include\n\n- daemon behavior\n- session-side websocket lifecycle helpers\n- Bun or Node listener setup\n- app-specific command semantics or projections\n\nThose higher-level concerns live in the packages above.\n\n## Package boundary\n\nThe intended split is:\n\n- **`@hunk/session-broker-core`** — low-level primitives\n- **`@hunk/session-broker`** — main broker API\n- **runtime adapters** — Bun and Node listener bindings\n\n## Quick example\n\n```ts\nimport {\n  brokerWireParsers,\n  parseSessionRegistrationEnvelope,\n  parseSessionSnapshotEnvelope,\n  SessionBrokerState,\n} from \"@hunk/session-broker-core\";\n```\n\nIf you find yourself reaching for this package directly in app code, double-check whether `@hunk/session-broker` would be the better fit.\n\n## License\n\nMIT\n","packages/session-broker-node/README.md":"# @hunk/session-broker-node\n\nPrivate Node HTTP and websocket adapter for `@hunk/session-broker`.\n\nUse this workspace to serve the broker daemon under Node instead of Bun. It is not a published,\nversioned SDK package. See the main broker README for current internal API construction and the\n[session broker SDK contract](../../docs/session-broker-sdk.md) for the future public package.\n\n## What it does\n\n- serves a runtime-neutral `SessionBrokerDaemon` through Node HTTP\n- upgrades websocket requests with `ws`\n- forwards websocket messages and close events into the daemon\n- exposes async startup and shutdown helpers\n- keeps the runtime-specific listener code out of `@hunk/session-broker`\n\n## Usage\n\nCreate the daemon with steps 1–2 in\n[`@hunk/session-broker`](../session-broker/README.md), then bind it to Node:\n\n```ts\nimport type { SessionBrokerDaemon } from \"@hunk/session-broker\";\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-node\";\n\ndeclare const daemon: SessionBrokerDaemon;\n\nconst server = await serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n## Why this package exists\n\nThis package runs the shared broker API against Node listener and websocket primitives.\n\nIf the Node adapter needs an abstraction the shared package does not provide, the fix should happen in `@hunk/session-broker`, not as Node-only glue.\n\n## License\n\nMIT\n","packages/session-broker/README.md":"# @hunk/session-broker\n\nRuntime-neutral session broker daemon and connection helpers.\n\nThe [session broker SDK contract](https://github.com/modem-dev/hunk/blob/main/docs/session-broker-sdk.md)\ndescribes the future supported public package. These private `0.0.0` source workspaces predate that\ncontract and do not yet satisfy all of its security, compatibility, supervision, packaging, or\nrelease gates.\n\nThe current package provides signed producer/caller hello, short-lived caller sessions, replay\nadmission, and default-deny raw HTTP authorization primitives. Hunk discovers app-scoped\ncredentials, starts session producers, and authenticates callers in its own runtime adapters. The\nlegacy Hunk WebSocket and custom session routes remain internal-only; no bearer fallback is\navailable. Browser-review capabilities are separate from broker caller credentials.\n\nThis is the **main broker package** in the workspace. It owns the reusable broker behavior without committing to Bun or Node server APIs.\n\nUse this package when you want to:\n\n- track live sessions\n- register and update session snapshots\n- route commands to one live session\n- expose broker health and optional raw list/get/dispatch APIs\n- manage session-side websocket connection state\n\n## Current workspace roles\n\nThe source tree is currently split into four private workspaces:\n\n- `@hunk/session-broker-core` — low-level shared primitives and envelope parsing\n- `@hunk/session-broker` — **main runtime-neutral broker API**\n- `@hunk/session-broker-bun` — Bun HTTP/websocket adapter\n- `@hunk/session-broker-node` — Node HTTP/websocket adapter\n\nThe public SDK contract consolidates these into one `@hunk/session-broker` package whose root\nselects Node or Bun automatically. Until that migration lands, these names and examples describe\ninternal workspace usage only.\n\n## What this package owns\n\n- `SessionBroker` raw session registry\n- `SessionBrokerDaemon` runtime-neutral daemon engine\n- `SessionBrokerConnection` runtime-neutral session-side websocket helper\n- raw broker HTTP request types\n- health handling and optional capabilities API handling\n- stale-session pruning and idle shutdown\n\n## What this package does not own\n\n- Bun `Bun.serve(...)`\n- Node `http` / `ws` listener setup\n- app-specific command semantics\n- app-specific projections like Hunk review exports, comments, or selected hunks\n- daemon process launch policy\n\n## Current internal quick start\n\n### 1. Create a broker\n\n```ts\nimport {\n  SessionBroker,\n  brokerWireParsers,\n  createSessionBrokerProtocolParsers,\n  parseSessionRegistrationEnvelope,\n  parseSessionSnapshotEnvelope,\n} from \"@hunk/session-broker\";\n\ninterface SessionInfo {\n  title: string;\n}\n\ninterface SessionState {\n  selectedIndex: number;\n}\n\nfunction parseInfo(value: unknown): SessionInfo | null {\n  const record = brokerWireParsers.asRecord(value);\n  if (!record) {\n    return null;\n  }\n\n  const title = brokerWireParsers.parseRequiredString(record.title);\n  return title === null ? null : { title };\n}\n\nfunction parseState(value: unknown): SessionState | null {\n  const record = brokerWireParsers.asRecord(value);\n  if (!record) {\n    return null;\n  }\n\n  const selectedIndex = brokerWireParsers.parseNonNegativeInt(record.selectedIndex);\n  return selectedIndex === null ? null : { selectedIndex };\n}\n\nconst protocolParsers = createSessionBrokerProtocolParsers({\n  appRevision: 1,\n  features: [],\n  parseRegistration: (value) => parseSessionRegistrationEnvelope(value, parseInfo),\n  parseSnapshot: (value) => parseSessionSnapshotEnvelope(value, parseState),\n  commands: [\n    {\n      command: \"select\",\n      version: 1,\n      parseInput: (value) => (brokerWireParsers.parseNonNegativeInt(value) === null ? null : value),\n      parseResult: (value) => (value === true ? true : null),\n    },\n  ],\n});\n\nconst broker = new SessionBroker({ protocolParsers });\n```\n\n### 2. Create a daemon engine\n\n```ts\nimport { createSessionBrokerDaemon } from \"@hunk/session-broker\";\n\nconst daemon = createSessionBrokerDaemon({\n  broker,\n  capabilities: {\n    version: 1,\n    name: \"example-broker\",\n  },\n});\n```\n\nAt this point the daemon can:\n\n- handle health requests\n- process websocket register/snapshot/heartbeat/result messages\n- prune stale sessions and request idle shutdown\n\nThe raw HTTP broker API is opt-in and fails closed: `exposeHttpApi: true` exposes no control route\nunless an explicit immutable `appId`, a singleton `appRevision`, a `callerAuthenticator`, and an app\n`authorizer` are supplied. The included\n`SessionBrokerAuthenticator` implements Ed25519 challenge/proof and signed requests; applications\ninject app-scoped grants, public verifiers, daemon signing identity, revocation policy, and their\nown default-deny authorization hook. It performs no filesystem, environment, coordinator, or Hunk\ncredential discovery.\n\n### 3. Serve it through a runtime adapter\n\n#### Bun\n\n```ts\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-bun\";\n\nconst server = serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n#### Node\n\n```ts\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-node\";\n\nconst server = await serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n## Session-side connection helper\n\nUse `SessionBrokerConnection` when an app window or live process needs to stay registered with the broker.\n\n```ts\nimport {\n  createNativeSessionBrokerLifecycleClock,\n  createSessionBrokerConnection,\n  type SessionBrokerSocketLike,\n} from \"@hunk/session-broker\";\n\nconst lifecycleClock = createNativeSessionBrokerLifecycleClock();\nconst connection = createSessionBrokerConnection({\n  url: \"ws://127.0.0.1:47657/session\",\n  createSocket: (url) => new WebSocket(url) as unknown as SessionBrokerSocketLike,\n  registration,\n  snapshot,\n  protocolParsers,\n  lifecycleClock,\n  bridge: {\n    dispatchCommand: async (message) => {\n      if (message.command !== \"select\") throw new Error(\"Unsupported command.\");\n      selectFile(message.input);\n      return true;\n    },\n  },\n});\n\nconnection.start();\n```\n\n`lifecycleClock` may be supplied in the connection options when an application needs to share\nlifecycle timing with its launcher or make producer scheduling deterministic. The ordinary\n`SessionBrokerLifecycleClock` contract provides current time, one-shot scheduling, delayed-first\nfixed-rate interval scheduling, and awaitable delay. Scheduled callbacks return idempotent\ndisposers. `createNativeSessionBrokerLifecycleClock()` uses unref'd native timers so pending\nhandshake, heartbeat, reconnect, and polling work does not retain the process.\n\n`createSocket` must return a fresh socket object for every generation because the helper installs\nproperty callbacks that cannot distinguish queued events after object reuse. Reconnect and close\ncallbacks receive a frozen opaque generation token; after foreign work settles, use\n`connection.isGenerationCurrent(token)` before committing app-owned state. These checks fence\ncommits only: they do not cancel foreign promises, cryptography, probes, or other work already in\nprogress.\n\nUnexpected defects in connection-owned native callbacks, scheduled work, or fire-and-forget work\nterminally retire that connection. Applications may supply `onDefect` to observe the event. The\ncallback receives only `SESSION_BROKER_LIFECYCLE_DEFECT_MESSAGE`; thrown values and lifecycle data\nare never forwarded, the callback runs at most once, and callback failures are contained. This\nruntime-neutral package does not write defect reports to process output.\n\nThe helper owns:\n\n- initial `register`\n- later `snapshot` updates\n- heartbeats\n- `command-result` replies\n- queued broker commands until the bridge is ready\n- reconnect scheduling\n\n## Raw broker API\n\nThe daemon always serves `GET /health`. Its raw capability/control API is intentionally small and\ndisabled by default. When `exposeHttpApi: true` is set together with an explicit `appId`, singleton\n`appRevision`, caller authenticator, and authorizer, it additionally serves:\n\n- `GET /broker/capabilities`\n- `POST /broker`\n\nRequest body shapes:\n\n```ts\n{ action: \"list\" }\n{ action: \"get\", selector: { sessionId: \"...\" } }\n{ action: \"dispatch\", selector: { sessionId: \"...\" }, command: \"...\", commandVersion: 1, input: {...} }\n```\n\nAn omitted `commandVersion` is validated and deliberately defaults to revision `1` for current\ninternal callers. Authentication covers the exact bounded HTTP body bytes before strict UTF-8 and\nJSON decoding. Authenticated responses use `{ body, authentication }`; the signed authentication\nrecord binds daemon generation, broker revision, target application contract when applicable,\nrequest ID, HTTP status, and the canonical structured-body digest.\n\n## Hunk-specific layering\n\nHunk uses this package for generic broker lifecycle and keeps product behavior in `packages/hunk`:\n\n- credential discovery: `packages/hunk/src/session/broker/credentials.ts`\n- daemon and Hunk-specific routes: `packages/hunk/src/session/broker/brokerServer.ts`\n- authenticated agent caller: `packages/hunk/src/session/agent/cliClient.ts`\n- session commands and review projections: `packages/hunk/src/session/`\n\nThe broker owns transport and lifecycle; Hunk owns session meaning and policy.\n\n## License\n\nMIT\n","packages/term-video/README.md":"# @hunk/term-video\n\nKeyframe-based terminal product videos. Instead of screen-recording in real\ntime, a script drives the real TUI over a PTY and snaps discrete styled\nkeyframes; a storyboard then declares how long each state holds and which\ncaption accompanies it; Chromium composites each frame onto a stage (window\nchrome, captions, title cards); ffmpeg encodes the result. Pacing is\ndeterministic, a minute of video is ~300 unique frames, and every frame is an\ninspectable PNG before anything is encoded.\n\n## Entry points\n\n- `@hunk/term-video/capture` (Bun) — PTY driving and keyframe snapping:\n  `createKeyframer`, `launchApp`, `launchShell`, `createCommandWrapper`,\n  `typeCommand`, `ensureKeyboardIsLive`, `makeSceneFilter`.\n- `@hunk/term-video/plan` (Bun or Node) — pure storyboard→frame-plan\n  expansion: `planFrames`, `requiredKeyframes`. Unit-tested; owns caption\n  carry-forward plus camera-pan, zoom, and source-aligned highlight animation.\n- `@hunk/term-video/compose` (Node ≥ 22) — `composeStoryboard` renders the\n  plan in headless Chromium against `src/stage.html` (or a custom stage) and\n  writes PNG frames plus an ffmpeg concat list. `playwright-core` is resolved\n  from the caller's work directory so its version can match the driven\n  Chromium build.\n\n- `@hunk/term-video/timeline` (Node ≥ 22) — `renderTimeline` steps a free-form\n  motion page frame by frame instead of planning stage states. The page owns\n  all of its motion and exposes `timelineReady`, `TIMELINE_DURATION`, and a\n  pure `renderAt(t)`, so any frame renders on its own for review stills.\n  `frameTimes` and `encodeArgs` are the unit-tested sampling and encoding\n  pieces.\n\n## Consumers\n\n`scripts/launch-video/` holds Hunk's current storyboard (scenes, captions,\ncards) on top of this package, and `scripts/launch-video/promo/` holds Hunk's\nrelease-promo style kit rendered through `timeline`; `skills/hunk-launch-video/SKILL.md` documents the\nend-to-end workflow and recipes for single-feature, custom, and full-release\nvideos, environment gotchas included.\n","skills/hunk-launch-video/SKILL.md":"---\nname: hunk-launch-video\ndescription: Produces Hunk videos by driving the real TUI headlessly in a PTY, compositing captioned 1080p frames in Chromium, and encoding with ffmpeg. Use for feature demos, workflow explainers, announcements, launch videos, and full-release roundups.\nmetadata:\n  internal: true\n---\n\n# Hunk video pipeline\n\nSource-checkout only: the pipeline lives in `scripts/launch-video/`, which never ships to npm.\nIt can be used for pull-request demos as well as maintainer release videos. Unix-only — the\ncapture scripts exec `/bin/bash`.\n\nGenerates product videos where every terminal frame is the real Hunk TUI —\nno screen recording, no mockups. Three stages:\n\n```text\ncapture.ts   (bun)               drive Hunk over a PTY, snap styled keyframes to PNG\ncompose.mjs  (node + Playwright) render each PNG on a 1920x1080 HTML stage in Chromium\nffmpeg                           encode the composited PNGs at 30fps to mp4/webm\n```\n\nPlaywright controls headless Chromium: for each planned frame it loads the\nterminal PNG onto the HTML stage, applies the window chrome, cards, captions,\nand transition state, then screenshots the completed stage back to PNG. ffmpeg\nsequences those composited screenshots into the final videos.\n\nThe generic machinery (PTY driving, keyframe rendering, storyboard planning,\nChromium compositing, the stage template) is the `@hunk/term-video` workspace\npackage in `packages/term-video/`; `scripts/launch-video/` holds only Hunk's\nscenes, captions, and cards on top of it.\n\n## Choosing a recipe\n\nThe pipeline is not release-specific. Choose the editorial scope, then use the\nsame capture → composite → encode stages:\n\n- **Single feature:** a short demonstration of one capability or workflow. Use\n  the single-feature recipe below and capture only the required scene.\n- **Full release:** the social promo for a release, in Hunk's house style\n  (`scripts/launch-video/promo/`). Use the full-release recipe: write one\n  storyboard file for the release; the style kit stays unchanged.\n- **Custom video:** author any set of scenes and `SHOTS` for tutorials,\n  comparisons, announcements, or workflow explainers; follow the scene and\n  storyboard rules below.\n\n## Creating a video\n\nExpect ~3–6 min for capture and ~2–4 min for compose — run both with a long\ntimeout (or in the background); each logs per-snap / per-shot progress.\n`compose.mjs` needs node ≥ 18 on PATH (bun alone is not enough).\n\n```sh\n# 0. dependencies (tuistory is a devDependency; ghostty-opentui arrives transitively)\nbun install    # if a postinstall hook fails in a sandbox, retry with --ignore-scripts\n\n# 1. capture the current storyboard's default scenes. Output defaults to\n#    <repo>/.video-work/ regardless of cwd (pass a path argument to override),\n#    but the PROCESS must run from the repo root — see gotchas.\nbun run scripts/launch-video/capture.ts\n\n# 2. one-time portable compositor setup. Playwright installs a Chromium build\n#    that exactly matches its browser driver (see gotchas to reuse a system or\n#    sandbox browser instead).\nprintf '{\"name\":\"hunk-video-work\",\"private\":true}\\n' > .video-work/package.json\ncd .video-work\nbun add playwright playwright-core\nbunx playwright install chromium\ncd ..\n\n# 3. composite the storyboard\nnode scripts/launch-video/compose.mjs .video-work\n\n# 4. encode\ncd .video-work\nffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n  -c:v libx264 -preset slow -crf 18 -movflags +faststart launch.mp4\nffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n  -c:v libvpx-vp9 -b:v 0 -crf 32 -row-mt 1 launch.webm\n```\n\nOverride the default scene set to iterate on one or more scenes:\n\n```sh\nSCENES=review bun run scripts/launch-video/capture.ts   # comma-separated scene names\n```\n\nScene names are the `wants(\"...\")` guards in `capture.ts`'s `main()`. `SCENES=`\nreplaces the canonical default scene set; `compose.mjs` preflights that every\nframe its `SHOTS` table references exists in `frames/` and fails fast listing\nany missing ones, so a full composite still needs every referenced scene\ncaptured at least once.\n\n## Single-feature recipe\n\nFor a short test, demo, or one-feature announcement, capture and composite only\nthe scene for that feature:\n\n1. Pick one user-visible capability and find its scene name in the\n   `wants(\"...\")` guards. If it does not have a scene, author one using the\n   guidance below. Capture only that scene:\n\n   ```sh\n   SCENES=review bun run scripts/launch-video/capture.ts\n   ```\n\n2. Make a scratch compositor beside the canonical one so its imports and\n   repo-relative paths continue to work:\n\n   ```sh\n   cp scripts/launch-video/compose.mjs scripts/launch-video/compose-one-feature.mjs\n   ```\n\n3. In the scratch copy, trim `SHOTS` to an opening card, only the selected\n   feature's frames, and an outro card. Rewrite those cards and captions for\n   the scoped cut. Sequence lengths must still match the captured frame names.\n4. Composite into the same work directory, then run the normal ffmpeg commands\n   with descriptive output names:\n\n   ```sh\n   node scripts/launch-video/compose-one-feature.mjs .video-work\n   cd .video-work\n   ffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n     -c:v libx264 -preset slow -crf 18 -movflags +faststart hunk-feature-demo.mp4\n   ffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n     -c:v libvpx-vp9 -b:v 0 -crf 32 -row-mt 1 hunk-feature-demo.webm\n   cd ..\n   rm scripts/launch-video/compose-one-feature.mjs\n   ```\n\nKeep the scratch compositor uncommitted. The canonical `compose.mjs` remains\nthe checked-in reference storyboard. If you added a capture scene only to make\nPR evidence, revert that scene after encoding; retain it only when it is useful\nchecked-in demo coverage and belongs to the submitted change.\n\n## Full-release recipe\n\nRelease videos use the promo style kit in `scripts/launch-video/promo/`, so\nevery release looks like part of one series. The kit renders through\n`@hunk/term-video/timeline`:\n\n```text\nstoryboards/<version>.mjs   release content: chapters, captions, montage, credits range\nengine.js + stage.html      the style: sting, countdown hook, chapter cards, terminal\n                            scenes, montage, contributors h, outro, transitions\ncontributors.mjs            git range -> contributors.json + avatars/\nrender.mjs                  storyboard + frames + contributors -> promo-<version>.mp4\n```\n\n1. **Pick the headlines.** Read the release section in `CHANGELOG.md` (or its\n   hand-written **Highlights**) and choose 4–6 chapters ordered by impact: new\n   capabilities first, then visible UI/UX changes, then smaller conveniences.\n   Per-PR entries that are too small for a chapter go in the montage. Check\n   that each claim is accurate: a Patch-level fix can earn a chapter, but name\n   what it does rather than calling it new. Confirm the shortlist with the\n   user before capturing.\n2. **Start the storyboard** by copying the newest one, for example\n   `cp storyboards/0.24.0.mjs storyboards/0.25.0.mjs`. Set `version`,\n   rewrite `chapters` and `montage`, set `montage.fixes.count` from the\n   changelog's patch entries, and point `contributors.from`/`to` at the\n   previous release tag and this release's tag. Leave `aliases` from older\n   releases out unless they still apply.\n3. **Capture** each chapter's keyframes, reusing or adding scenes in\n   `capture.ts` (see \"Authoring scenes\"); every `frames[].img` must name a\n   captured keyframe. `SCENES=a,b bun run scripts/launch-video/capture.ts`.\n4. **Credits.** Fetch tags and history (`git fetch --tags --unshallow` in a\n   shallow clone), then run\n   `node scripts/launch-video/promo/contributors.mjs .video-work`. Review the\n   printed table: everyone who authored or co-authored a commit in the range\n   appears once, bots and AI co-author trailers excluded. Map unresolved\n   people to logins with `contributors.aliases` (email or name to a login, or\n   `{ login, id }` when the GitHub users API is unavailable) and rerun; anyone\n   still unresolved is credited by name with an initials tile. Never guess an\n   account.\n5. **Work dir.** Besides `playwright-core` (see gotchas), the kit needs the\n   brand font: `cd .video-work && bun add @fontsource-variable/jetbrains-mono`.\n6. **Stills first.** `node scripts/launch-video/promo/render.mjs .video-work\n--stills 2.6,8,14,...` renders review frames in seconds; check every\n   chapter's callouts and captions before the full render. `render.mjs`\n   validates the storyboard and lists any missing keyframes up front.\n7. **Render** with `node scripts/launch-video/promo/render.mjs .video-work`\n   (about 3 minutes for a minute of video) and verify the cut (see\n   \"Verification\"). When `hunk-release` invoked this workflow, return the\n   approved MP4 to that skill for versioned naming and embedding; do not upload\n   or edit the public release without its confirmation gate.\n\n### Chapter reference\n\nTimes are seconds from the start of the chapter's terminal scene; camera,\nhighlight, and label coordinates are fractions of the captured frame.\n\n- `label`, `title` (lines; prefix a word with `*` to color it), `sub`, `cmd`\n  (typed into the title card's command chip), `winTitle`.\n- `frames: [{ img, dur, key?, fade? }]` plays keyframes in order. `key` shows\n  that keycap pressing as the frame appears, so one frame per keypress reads\n  as typing; `fade` crossfades from the previous frame (use it for layout\n  swaps). The `steps()` helper in the 0.24.0 storyboard builds key-walk runs.\n- `cameras: [[t, { x, y, scale }, motionSeconds?]]` must start at time 0.\n- `highlights: [{ t0, t1, x, y, w, h }]` and `labels: [{ t0, t1, x, y, text }]`\n  outline and annotate source regions; keep edges in blank rows and labels off\n  terminal text (see \"Camera and callout composition\").\n- `captions: [[t, html]]` with `<b>` for the accent color.\n- `bodyH` gives short shell output a compact window instead of a full screen.\n\n### Extending the style\n\nKeep release copy out of `engine.js`: it reads everything release-specific\nfrom the storyboard, and its defaults hold only brand constants (tagline,\ninstall command, site). When a release needs something the kit cannot show,\nadd a reusable scene type or storyboard field to the engine and document it\nhere rather than special-casing one release. The brand geometry in the engine\nfollows `website/public/brand/`; update both together.\n\nThe caption storyboard below (`compose.mjs` on the shared stage) remains the\ntool for single-feature demos, PR evidence, and custom explainers.\n\n## Per-video editorial surface\n\nThis section covers the caption storyboard used for single-feature, PR, and\ncustom videos; release promos keep their content in `promo/storyboards/`. The\ncapture machinery is reusable, but the storyboard is editorial content for one\nvideo. Rewrite it to match the video's scope. The checked-in reference is the\n0.24 release roundup, not a frozen release artifact:\n\n- `compose.mjs`: the whole `SHOTS` table; opening, feature, and outro cards;\n  every caption; camera targets; and callout rectangles. A `NEW` badge is a\n  claim about the video at hand, so drop or move it as features age.\n- `capture.ts`: `SCENES` selects from a reusable scene library. With no\n  override it captures only the current storyboard's default scene; update\n  that default whenever the canonical storyboard changes. Hunk-side glue\n  (`launchHunk`, `launchHunkShell`, demo repositories, and the keyboard probe)\n  remains reusable.\n\nReusable machinery lives in `@hunk/term-video` (`packages/term-video/`) —\nextend it there, don't fork it into the scripts: `createKeyframer`,\n`launchApp`/`launchShell`, `createCommandWrapper`, `typeCommand`,\n`ensureKeyboardIsLive`, `makeSceneFilter` (`src/capture.ts`); the unit-tested\nstoryboard planner with the caption/timing semantics (`src/plan.mjs`);\n`composeStoryboard` with font/Chromium resolution and the missing-keyframe\npreflight (`src/compose.mjs`); the stage template (`src/stage.html`); and\n`renderTimeline` for free-form motion pages such as the promo kit\n(`src/timeline.mjs`).\n\n## Environment gotchas\n\nSandbox-specific bullets are marked; each cost real debugging time.\n\n- **Run `capture.ts` with bun from the repo root.** tuistory uses subpath\n  self-imports (`tuistory/pty`) that only resolve inside this repo's\n  `node_modules`; running the script from elsewhere resolves tuistory from\n  bun's global cache and crashes. (Output location is unaffected — it defaults\n  to `<repo>/.video-work/` via `import.meta.url`.)\n- **ghostty-opentui is a transitive dep** (via tuistory) with an exports map:\n  import `\"ghostty-opentui/image\"` (not `.../dist/image.js`), resolved\n  relative to tuistory — `@hunk/term-video/capture`'s `createKeyframer` does\n  the `Bun.resolveSync` dance.\n- **Playwright must match the Chromium it drives.** The portable setup above\n  installs `playwright` and `playwright-core` together, then downloads their\n  matching Chromium build. It works on macOS and Linux and is preferred when\n  bandwidth and browser downloads are available; leave `CHROMIUM_PATH` unset\n  so Playwright uses that managed browser. On Linux, if Chromium reports\n  missing system libraries, run `bunx playwright install-deps chromium` (it\n  may require sudo) before retrying.\n\n  To reuse an existing system, CI, or sandbox Chromium instead, install the\n  `playwright-core` version provided by that environment and set its executable\n  explicitly:\n\n  ```sh\n  cd .video-work\n  bun add playwright-core@<matching-version>\n  cd ..\n  CHROMIUM_PATH=/path/to/chromium node scripts/launch-video/compose.mjs .video-work\n  ```\n\n  Common executable locations include `$(command -v chromium)` or\n  `$(command -v google-chrome)` on Linux and\n  `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` on macOS.\n  In an environment with a preinstalled Playwright toolchain, read that\n  toolchain's `package.json` to get the exact driver version. For example, the\n  Anthropic sandbox exposes it through `/opt/pw-browsers/.links/*`:\n\n  ```sh\n  cat \"$(cat /opt/pw-browsers/.links/* | head -1)/package.json\" | grep '\"version\"'\n  # e.g. \"1.56.1\" -> bun add playwright-core@1.56.1\n  ```\n\n  `compose.mjs` picks its browser as `$CHROMIUM_PATH`, then\n  `/opt/pw-browsers/chromium` when present, then Playwright's managed browser.\n  If a dependency update leaves Playwright asking for an uninstalled browser\n  revision, either rerun `bunx playwright install chromium` in `.video-work/`\n  or set `CHROMIUM_PATH` to a known system browser such as `/usr/bin/chromium`.\n\n- **Give `.video-work/` its own `package.json` before `bun add`.** Without\n  one, bun walks up and installs into the repo's `package.json` — revert with\n  `git checkout package.json bun.lock` if that happens.\n- **Chromium needs `--allow-file-access-from-files`** (already in\n  `compose.mjs`): the stage samples each keyframe through a canvas to\n  color-match the window background, and file:// images taint the canvas\n  without it.\n- **mp4 needs an ffmpeg with libx264.** Sandbox: `apt-get install ffmpeg`\n  (run `apt-get update` first if packages 404). macOS: `brew install ffmpeg`.\n  Verify `ffmpeg -encoders | grep -E 'libx264|libvpx-vp9'` shows both before\n  encoding — playwright's bundled `ffmpeg-*/ffmpeg-linux` only does VP8/WebM\n  and cannot produce the mp4.\n- **Caption font**: JetBrains Mono ships inside ghostty-opentui;\n  `findCaptionFont` in `packages/term-video/src/compose.mjs` searches bun's\n  isolated layout (`node_modules/.bun/node_modules/…`) then a hoisted\n  `node_modules/…`. If it still throws `caption font not found`, locate the\n  file with `find node_modules -name jetbrains-mono-nerd.ttf` and pass it as\n  `fontPath` to `composeStoryboard`.\n\n## Authoring scenes (capture.ts)\n\n- Shared geometry is 140x32 cells rendered at fontSize 16 / dpr 2 → 2688x1536\n  PNGs. Keep every scene at this size so all frames fit one window.\n- Keep time-sensitive fixtures recent and timezone-stable. The canonical\n  history scene derives commit dates from the current UTC day and launches\n  Hunk with `TZ=UTC`, so day groups and relative-age labels remain useful on\n  future runs. Preserve that policy in custom history scenes.\n- Helpers: `createDemoRepo()` and `launchHunkShell()` are hunk-side glue in\n  the script (git repo built from `examples/2-mini-app-refactor`; interactive\n  bash with a real `hunk` command on PATH and a clean `❯` prompt); `snap`,\n  `typeCommand`, `launchApp`/`launchShell`, and `createCommandWrapper` come\n  from `@hunk/term-video/capture`.\n- Always `waitForText` on scene-specific content before the first snap, and\n  call `ensureKeyboardIsLive()` before scripted keypresses — the first key\n  after startup can be dropped (real race, the helper toggles `?` to prove\n  keys land).\n- **Animation = one snap per keypress.** Cursor walks and typing effects are\n  just every `j`/`k`/character captured as its own frame and played back at\n  0.2–0.3s per frame. Prefer this over sparse keyframes: three stills read as\n  a slideshow, per-press frames read as motion.\n- `renderTerminalToImage` auto-trims trailing blank rows, so short outputs\n  (CLI scenes) produce short PNGs — the stage handles this by sampling the\n  image's bottom-left pixel and painting the window body to match.\n- `manifest.json` is a capture-side inventory of the _current run_ only;\n  `compose.mjs` ignores it (frames resolve by name from `SHOTS`), and after a\n  `SCENES=` run it is partial while `frames/` stays cumulative.\n- Demo content that must exist: STML notes come from\n  `examples/9-agent-markup-notes` (launch with `--experimental`), extension\n  scenes from `examples/extensions/` loaded via `--extension <path>` (explicit\n  paths skip the repo trust prompt). The pager pipe is `git diff | hunk pager`\n  — bare `hunk` on piped stdin prints help. Sidebar toggle is `s`; comment\n  draft is `c`, save with Ctrl+S (`\\x13`).\n\n## Storyboard model (compose.mjs)\n\n- `SHOTS` is the whole edit: one entry per shot, `dur` in seconds, played as\n  unique frames + per-frame durations in an ffmpeg concat list (holds cost one\n  frame, so runtime is dominated by transitions, not length).\n- `capKey` is caption identity: the caption slides in only when `capKey`\n  changes, and continuation shots that share a `capKey` without restating\n  `caption` keep the previous caption on screen. Sequences (walks, typing) are\n  generated with `Array.from` spreads.\n- **Sequence lengths must match capture loop bounds**: the open capture's\n  four-step tree walk plus its select and expand presses produce\n  `open-tree-0..5`, consumed by a six-entry `Array.from` spread in `SHOTS`.\n  Change one side and the other breaks — the preflight check names any frame\n  that's missing.\n- `enter: true` fades/scales the surface in — use it for cards and the first\n  terminal shot only.\n- Terminal shots may set normalized `camera: { x, y, scale }` targets and\n  `highlight: { x, y, width, height, label? }` rectangles. `motion` controls\n  the transition length in seconds. The planner interpolates camera changes\n  and outline geometry, while the stage keeps the camera inside the captured\n  image and paints an animated glow around the source-aligned region. Set the\n  same `cameraKey` or `highlightKey` on related keyframes when their normalized\n  coordinates share one capture geometry. Cross-image camera changes cut to\n  their target unless `cameraKey` explicitly permits a pan, and outlines never\n  leak across unrelated images.\n- Caption HTML vocabulary: `<span class=\"badge\">NEW</span>` amber pill,\n  `<span class=\"hl\">` amber highlight, `<span class=\"dim\">` muted. Cards use\n  `badge` / `h1`/`h2` / `sub` / `cmds`+`cmd` / `foot` classes from\n  `packages/term-video/src/stage.html`.\n- Target pacing: money shots hold 3–4s, context shots 2–3s, typing/walk frames\n  0.2–0.6s; keep the total near 60s.\n\n## Camera and callout composition\n\nTreat `camera` and `highlight` as one composition. Both use normalized source\ncoordinates, but the stage projects highlights through the active camera. A\nrectangle that fits within the source can still lose an edge after zooming.\n\nUse this framing process:\n\n1. Start with a full-frame establishing shot, then pan or zoom to one readable\n   subject. Use `motion` around 0.7–0.9s for a deliberate move rather than a\n   cut disguised as animation.\n2. Fit the target plus its outline inside the camera viewport. Leave about 1%\n   of the visible width on each side and at least one terminal row above and\n   below the meaningful content. If that does not fit, reduce `camera.scale`.\n3. Put outline edges in actual blank rows. Mathematical padding can move a\n   border onto a neighboring date heading, status row, or metadata line.\n4. Check both sides of the capture. A zoom that preserves left-aligned subjects\n   can still crop hashes, authors, or status text aligned to the right.\n5. Give irregular states explicit rectangles. A growing commit range that\n   crosses day separators should use measured per-keyframe heights instead of\n   assuming every added item occupies the same vertical distance.\n6. Share `cameraKey` or `highlightKey` only when captures use the same source\n   geometry. This allows pans and outline morphs across related captures;\n   unrelated keyframes cut or fade separately.\n\nThe outline should direct attention, not become the subject. Use one callout at\na time, keep labels short, and remove the outline once the viewer has enough\ncontext to follow the interaction unaided.\n\n## Command and output legibility\n\nTerminal commands and their results must be readable at normal playback size.\nNever rely on a full-width terminal prompt or output region as the only way the\nviewer can understand a command scene; text that looks acceptable in a 1080p\nsource frame is often illegible in an embedded player or social feed.\n\nUse this default treatment:\n\n1. **Present the exact command in large type.** The easiest treatment is a\n   `cmd` card or an oversized editorial callout immediately before the real\n   terminal result. A typing animation may remain for motion, but it does not\n   replace the large command treatment.\n2. **Zoom the real output.** Crop or scale the captured terminal frame around\n   the meaningful result so both the command and the important output lines\n   are comfortably readable. A brief full-window establishing shot is fine,\n   but the result's main hold must use the focused view.\n3. **Keep the evidence real.** Never fabricate terminal output. Large command\n   text may reproduce the exact command as an editorial overlay; any enlarged\n   output must come from the captured PTY frame. If reusable crop/zoom controls\n   are missing, add them to `@hunk/term-video` rather than baking one-off image\n   edits into a storyboard.\n4. **Trim before shrinking.** Prefer fewer relevant lines and a tighter crop\n   over fitting a long transcript into the frame. Split a workflow across\n   multiple focused shots when one crop cannot keep every important line\n   legible.\n\nTreat command legibility as a release gate: if the command or its result cannot\nbe read when the 1920x1080 video is displayed at 50% size, revise the shot.\n\n## Content accuracy (learned the hard way)\n\n- **Verify install commands against reality**, not the README: check\n  `npm view hunkdiff dist-tags`. A prerelease needs `npm i -g hunkdiff@beta`;\n  `brew install hunk` only serves stable (homebrew-core Autobump, lags npm) —\n  omit brew on prerelease cards.\n- **Label demo extensions as examples.** The triage board, CSS palette, and\n  semver views are `examples/extensions/`, not shipped features — caption them\n  with a dimmed `example:` prefix. The real features are the APIs (sidebars,\n  file views, commands, dialogs).\n- Window titles are decorative but must not lie: the shell scenes run bash,\n  so keep their titles generic (`shell — …`) rather than naming a shell the\n  capture doesn't launch.\n- STML requires `--experimental`; say so on the outro card.\n- The video is silent — never imply audio in the video or its announcement copy.\n\n## Verification and delivery\n\n- Eyeball keyframes in `.video-work/frames/` (Read renders PNGs) after\n  capture — especially new scenes — before compositing.\n- Inspect every command scene at 50% display size. Confirm the exact command is\n  shown in large type and the meaningful real output is zoomed tightly enough\n  to read; a readable caption does not compensate for unreadable terminal text.\n- Composite and encode the MP4 first. Inspect it before spending time on the\n  slower VP9/WebM encode; produce both final formats only after the visual pass.\n- After encoding, return to the repository root, set `VIDEO` to the produced\n  MP4, and inspect a settled frame for each callout, a mid-animation point,\n  each new scene, and the outro:\n\n  ```sh\n  VIDEO=.video-work/hunk-feature-demo.mp4 # or .video-work/launch.mp4\n  ffmpeg -y -ss 2 -i \"$VIDEO\" -frames:v 1 -update 1 .video-work/check.png\n  ffprobe -v error -show_entries format=duration,size \\\n    -show_entries stream=codec_name,width,height,r_frame_rate \\\n    -of default=noprint_wrappers=1 \"$VIDEO\"\n  ```\n\n  Captions must persist through extracted animation frames. Every callout must\n  show all four borders, keep its label clear of terminal text, leave visible\n  padding around its subject, and avoid clipping right-aligned metadata.\n\n- Compare the storyboard planner's reported duration with `ffprobe`; they\n  should agree within one frame. The concat manifest declares the PNG input\n  rate so 30 fps animation timestamps stay exact. Investigate larger drift\n  before delivery.\n\n- Outputs stay under `.video-work/`: the full-release recipe creates\n  `launch.mp4`/`launch.webm`, while the single-feature recipe above creates\n  `hunk-feature-demo.mp4`/`hunk-feature-demo.webm`. `.video-work/` is\n  gitignored — never commit the video or its frames. For a standalone video\n  request, send both files to the user directly (mp4: social/Slack; webm: web\n  embeds), report duration and file sizes, and flag if the mp4 exceeds ~10 MB\n  (Slack) or ~15 MB (X). When invoked by `hunk-release`, hand both files back\n  to that workflow instead; it owns versioned filenames, GitHub's\n  user-attachment limit, public embedding, and inline-player verification.\n  Copy them elsewhere only if the user names a destination.\n","skills/hunk-release/SKILL.md":"---\nname: hunk-release\ndescription: Prepares, publishes, verifies, and curates Hunk releases. Use for release metadata, benchmarks, tags, publishing, release videos, backports, or recovery.\nmetadata:\n  internal: true\n---\n\n# Hunk release workflow\n\nMaintainer-focused and source-checkout only. The tag workflow publishes `hunkdiff` plus five platform packages, attests the binary archives, and creates the GitHub release.\n\n## Safety\n\n- Ask when the version, release branch, previous tag, or channel is ambiguous.\n- Get explicit confirmation before pushing a tag, triggering publication, or editing a public release.\n- Never reuse an npm version or move a tag after publication.\n- Never bypass a benchmark regression without an approved, recorded reason.\n- Never retry a partial publish before inventorying every package and artifact.\n\n## 1. Confirm the release\n\nRecord the version, tag, branch, previous tag, and expected channel:\n\n- newer stable: npm `latest` and GitHub Latest;\n- prerelease: npm `beta`, not GitHub Latest;\n- older-series backport: npm `backport-X.Y`, leaving both latest pointers unchanged.\n\nFor backports, include only commits present between the previous tag and the release tip on that maintenance branch. Inspect `.changeset/pre.json` before changing prerelease state.\n\n## 2. Prepare\n\nStart from a clean, current release branch:\n\n```sh\ngit status --short --branch\ngit fetch origin --tags --prune\ngit tag --sort=-version:refname | head -10\nbun install --frozen-lockfile\nbun run changeset:status\n```\n\nRead the pending Changesets, then generate metadata and the published release notes:\n\n```sh\nbun run release:version\nbun run generate:changelog\ngit diff -- package.json bun.lock packages CHANGELOG.md .changeset website\n```\n\nVerify the intended versions, consumed Changesets, and new changelog section. Do not force a bump by hand-editing generated versions.\n\n`generate:changelog` projects `CHANGELOG.md` into `hunk.dev/changelog`. Never hand-edit its output under `website/src/content/docs/changelog/`, `website/public/changelog/`, `website/releases/dates.json`, or `website/releases/latest.json`. For a minor or major release, add the hand-authored parts to `website/releases/notes.json` under the `major.minor` key and regenerate:\n\n- `summary` — one sentence; replaces the changelog's `### Highlights` lead paragraph on the page.\n- `tagline` — short phrase for the landing-page release ribbon.\n- `links` — docs pages the highlights describe, rendered as \"Related documentation\".\n- `video` — `mp4` plus optional `webm`, `poster`, `duration`, and `title`, filled in at step 5.\n\nThe release tag does not exist yet at this point. A stable version is rendered as `Unreleased` without an install command, while an undated prerelease remains off the site entirely; the tag date is picked up by the next generation. Recorded dates in `website/releases/dates.json` are never recomputed, which is what lets `check:changelog` gate CI without Git tags.\n\nGenerate and compare the committed release benchmark:\n\n```sh\nbun run bench:release\nbun run bench:release:compare\n```\n\nA material regression blocks the release unless the user approves an `acceptedRegressions` entry following `benchmarks/release/README.md`.\n\nRun the validation required by `AGENTS.md`, plus the release packaging checks:\n\n```sh\nbun run check:docs\nbun run check:changelog\nbun run check:pack\nbun run build:prebuilt:npm\nbun run check:prebuilt-pack\nbun run smoke:prebuilt-install\n```\n\nCommit the generated metadata and `benchmarks/release/bench-X.Y.Z.json`, then follow normal review\npolicy. The Firecracker evidence must come from that reviewed release tip, not the pre-generation\ncommit. Push the reviewed tip before using the manual workflow.\n\nRun the full Firecracker install compatibility suite once from the clean reviewed release tip on a\nLinux x64 host with working KVM, either locally or through the manually dispatched\n`install-vm.yml` workflow:\n\n```sh\nset -euo pipefail\nmkdir -p tmp/install-vm/runs\nresult_dir=$(mktemp -d tmp/install-vm/runs/release-XXXXXXXX)\nbun run test:install-vm -- --output \"$result_dir\"\nbun run ./test/cli/install-vm/validate-release-result.ts \"$result_dir/result.json\"\n```\n\nThe explicit output directory prevents a failed invocation from falling back to stale evidence. The\nvalidator requires the complete checked-in scenario manifest, passing statuses, and the current\ncheckout's source identity. A skipped result does not satisfy release validation. For the manual\nworkflow, dispatch the full suite from the reviewed tip, download its `result.json` beneath the\nignored `tmp/install-vm/` directory of a checkout at that exact tip, and run the validator there; a\ngreen job alone is insufficient because unsupported runners may use the intentional skip path.\nFirecracker validates Linux x64 packaging behavior, while the existing native release jobs remain\nresponsible for macOS, Windows, and other architectures. Wait for required CI before continuing.\n\n## 3. Tag and publish\n\nImmediately before tagging, fail unless the reviewed branch tip is clean and matches its remote:\n\n```sh\nset -euo pipefail\nbranch=$(git branch --show-current)\ntest -n \"$branch\"\ngit fetch origin \"$branch\" --tags\ntest \"$(git rev-parse HEAD)\" = \"$(git rev-parse \"origin/$branch\")\"\nif [ -n \"$(git status --porcelain --untracked-files=all)\" ]; then\n  git status --short\n  exit 1\nfi\ngit log -1 --format='%H %s'\n```\n\nPresent the tag, commit, benchmark result, validation, and release highlights. After explicit confirmation:\n\n```sh\nversion=X.Y.Z\ntag=\"v$version\"\ngit tag -a \"$tag\" -m \"$tag\"\ngit push origin \"$tag\"\n```\n\nThe tag push starts `.github/workflows/release-prebuilt-npm.yml`. Find only the run for that tagged commit and propagate failure:\n\n```sh\nrelease_sha=$(git rev-parse \"$tag^{}\")\nrun_id=$(\n  gh run list \\\n    --workflow release-prebuilt-npm.yml \\\n    --event push \\\n    --commit \"$release_sha\" \\\n    --limit 1 \\\n    --json databaseId \\\n    --jq '.[0].databaseId // empty'\n)\ntest -n \"$run_id\"\ngh run view \"$run_id\" --json url,status,conclusion\ngh run watch \"$run_id\" --exit-status\n```\n\nIf lookup is empty, repeat it after GitHub registers the run; never select an unrelated run. Success requires the benchmark, all builds, staging smoke test, npm publish, attestation, and GitHub release jobs.\n\n## 4. Verify publication\n\nCheck the meta-package, platform packages, dist-tags, release body, and five binary archives:\n\n```sh\nnpm view \"hunkdiff@$version\" version\nfor package in \\\n  hunkdiff-darwin-arm64 hunkdiff-darwin-x64 \\\n  hunkdiff-linux-arm64 hunkdiff-linux-x64 \\\n  hunkdiff-windows-x64; do\n  npm view \"$package@$version\" version\ndone\nnpm view hunkdiff dist-tags --json\ngh release view \"$tag\" --json tagName,name,isPrerelease,url,assets,body\n```\n\nStop if versions, channel, tag, archives, or attestations disagree.\n\nThen record the tag date and publish the release notes. The published page is what the update notice and the GitHub release body point at, so it is part of the release, not follow-up work:\n\n```sh\nbun run generate:changelog\nbun run generate:og\ngit diff --stat -- website\n```\n\nThis backfills the new tag's date, publishes its notes, and redraws the social cards whose contents changed. Stable releases also add the install command and move the landing-page ribbon; prereleases publish a clearly labeled series without advancing either stable surface. Commit it to `main` — the diff should only touch `website/releases/`, `website/src/content/docs/changelog/`, `website/public/changelog/rss.xml`, and the redrawn cards under `website/public/changelog/og/`. Run this for backports too: they publish into their older series without advancing the latest stable release.\n\nPrereleases publish to `hunk.dev/changelog` after their tag exists. A beta contributes its series page, exact version anchor, index row, feed item, and social card, while the stable latest marker, landing ribbon, and default install instructions continue to name the newest stable release.\n\n## 5. Add the release video and final notes\n\nOnly after publication verifies, create a detached worktree at the released tag and follow `skills/hunk-launch-video/SKILL.md`'s full-release recipe:\n\n```sh\ngit worktree add --detach \"../hunk-release-video-$version\" \"$tag\"\n```\n\nThat skill owns capture, encoding, and media checks; release videos use its promo style kit, with this release's content in a new `scripts/launch-video/promo/storyboards/<version>.mjs`. Keep generated media out of Git and Git LFS. Commit the release's storyboard file and any capture scenes it needs only with separate approval, so the next release can start from them.\n\nPublish the video so the release page can carry it, then record it in `website/releases/notes.json` under the series' `video` key and regenerate. Host it where the site can serve it rather than committing it: the 1080p master stays out of Git and Git LFS.\n\nDraft the final body from the released changelog and actual branch diff. Replace GitHub's generated PR inventory with editorial notes that help someone decide whether to install the release:\n\n- Open with one short paragraph describing the release's product theme and user impact.\n- Group a small number of meaningful changes under descriptive headings. Explain what users can now do; do not restate commit titles or reproduce the changelog.\n- Include both upgrade and npm installation instructions using the exact release: `hunk update <version>` for npm- and curl-managed installs and `npm install -g hunkdiff@<version>` for npm installs or first-time npm users. Homebrew cannot select an exact version: tell its users to run plain `hunk update` only after the Homebrew readiness gate below passes, or label Homebrew propagation as pending.\n- Add a clearly labeled compatibility section for runtime requirements, changed CLI interpretation, extension API variants, migrations, or other upgrade risks.\n- Add a **Community contributors** section that names every external contributor in the release, links each relevant PR, and briefly describes their contribution. Derive this from the actual release diff; do not limit acknowledgment to first-time contributors and do not bury contributors in an autogenerated list.\n- Preserve a complete PR inventory inside a collapsed GitHub `<details>` block after the editorial sections. Use one concise bullet per merged PR with its author and link, include maintenance work there, and verify the list against the actual previous-tag comparison. The collapsed inventory is for completeness; it does not replace the curated highlights or contributor acknowledgments.\n- Link the comparison from the actual previous tag and point every release at its `hunk.dev` series page, which carries the full notes, video, and docs links.\n- Keep maintenance-only PRs out of the editorial highlights unless they materially affect installation, compatibility, security, or performance.\n\nUse this shape rather than a flat `What's Changed` list:\n\n````md\n## <release name or product theme>\n\n<one short release summary>\n\n```sh\nhunk update <version>\nnpm install -g hunkdiff@<version>\n```\n\nhttps://github.com/user-attachments/assets/<video-id>\n\n### <user-facing theme>\n\n<what changed, why it matters, and links to the defining PRs>\n\n### Compatibility notes\n\n- <upgrade requirement or changed behavior>\n\n### Community contributors\n\n- [@contributor](https://github.com/contributor) <concise contribution>. [#123](PR URL)\n\n<details>\n<summary>All merged pull requests</summary>\n\n- <concise PR title> by @author in [#123](PR URL)\n\n</details>\n\n**Release notes**: https://hunk.dev/changelog/<major.minor>/\n**Full changelog**: https://github.com/modem-dev/hunk/compare/<previous-tag>...<new-tag>\n````\n\nPresent the local MP4 and draft notes for explicit confirmation. Then attach the H.264 MP4 in GitHub's release editor, replace the placeholder with its generated user-attachment URL, and apply the reviewed body:\n\n```sh\ngh release edit \"$tag\" --notes-file /tmp/hunk-release-notes.md\n```\n\nOpen the public release in a browser and verify inline playback, final notes, and the unchanged five immutable binary archives.\n\n## 6. Distribution channels\n\nOnly stable releases that advance `latest` should propagate to Homebrew and mise. Let Homebrew Autobump update `Homebrew/homebrew-core`; use `brew bump-formula-pr` only if maintainers request it or Autobump stalls.\n\nTreat Homebrew availability as an announcement check only. Never delay or fail the tag, npm publication, GitHub release, changelog, release notes, video, or other release completion work because Homebrew is pending. Homebrew detects the published release on its own schedule and merges a separate formula PR. Immediately before an external promotional announcement that names Homebrew or recommends `hunk update` without qualifying the install method, read the live formula API and require its stable version to match:\n\n```sh\nversion=X.Y.Z\nformula_version=$(\n  curl -fsSL https://formulae.brew.sh/api/formula/hunk.json |\n    bun -e 'console.log(JSON.parse(await Bun.stdin.text()).versions.stable)'\n)\nprintf 'Homebrew formula: %s (release: %s)\\n' \"$formula_version\" \"$version\"\ntest \"$formula_version\" = \"$version\"\n```\n\nIf the check fails, still mark the software release complete, but hold the external promotional announcement unless it explicitly says that Homebrew is still propagating and offers npm or the install script meanwhile. Check for an open or merged Autobump PR:\n\n```sh\ngh api --method GET /search/issues \\\n  -f q=\"repo:Homebrew/homebrew-core is:pr in:title \\\"hunk $version\\\"\" \\\n  --jq '.items[] | [.html_url, .state, .title] | @tsv'\n```\n\nDetermine the next expected Autobump scan from Homebrew's live workflow rather than relying on a remembered cadence. This command prints the current cron expression and, while Homebrew retains its present three-hour schedule, the next nominal UTC scan. Scheduled GitHub Actions can start late, so report this as an estimate rather than an availability promise:\n\n```sh\nautobump_workflow=$(\n  gh api repos/Homebrew/homebrew-core/contents/.github/workflows/autobump.yml \\\n    --jq .content | base64 --decode\n)\nautobump_cron=$(\n  printf '%s\\n' \"$autobump_workflow\" |\n    awk -F'\"' '/^[[:space:]]*- cron:/ { print $2; exit }'\n)\nprintf 'Homebrew Autobump schedule: %s\\n' \"$autobump_cron\"\nAUTOBUMP_CRON=\"$autobump_cron\" bun -e '\n  const cron = process.env.AUTOBUMP_CRON;\n  if (cron !== \"45 1-23/3 * * *\") {\n    console.error(\"Homebrew changed its Autobump schedule; inspect the workflow before announcing.\");\n    process.exit(1);\n  }\n  const next = new Date();\n  next.setUTCSeconds(0, 0);\n  do next.setUTCMinutes(next.getUTCMinutes() + 1);\n  while (next.getUTCMinutes() !== 45 || (next.getUTCHours() - 1) % 3 !== 0);\n  console.log(`Next nominal Homebrew Autobump scan: ${next.toISOString()}`);\n'\n```\n\nAfter the formula version matches, run `brew update`, verify `brew info hunk` reports the release, and only then describe Homebrew as available. Verify mise independently against fresh registry data:\n\n```sh\nMISE_AQUA_BAKED_REGISTRY=false mise latest hunk\n```\n\nUse `mise cache clear` only when cached registry data is stale. Do not claim Homebrew or mise support for prereleases or older-series backports.\n\n## Failure invariants\n\n- Before tag push: fix, regenerate, validate, and request confirmation again.\n- After any npm publication: keep the version and tag; inventory all six packages before recovery.\n- Keep a verified software release intact when video or notes fail; retry only the approved media/edit step.\n","website/src/content/docs/docs/agents/agent-context-and-stml.md":"---\ntitle: Agent context and STML\ndescription: Load review notes from a JSON sidecar and opt into experimental rich note markup.\n---\n\n[Live agent comments](/docs/agents/comments-and-annotations/) are the recommended workflow. A sidecar file is useful when the notes already exist before Hunk starts, or need to travel with a patch.\n\n## Load a JSON sidecar\n\n```bash\nhunk diff --agent-context notes.json\nhunk patch change.patch --agent-context notes.json\n```\n\nUse `--agent-context -` to read the sidecar from stdin. With `--watch`, Hunk also reloads when the sidecar file changes, so an agent can rewrite the file and the open review updates; a stdin sidecar cannot be watched.\n\nSidecar annotations are agent notes, so they start hidden. Press `a` to show them, or launch with `--agent-notes`.\n\n## Sidecar format\n\n```json\n{\n  \"version\": 1,\n  \"files\": [\n    {\n      \"path\": \"src/search.ts\",\n      \"summary\": \"Scores and sorts matches instead of returning the first substring hit.\",\n      \"annotations\": [\n        {\n          \"newRange\": [15, 35],\n          \"summary\": \"Prefix and exact matches now outrank weaker substring hits.\",\n          \"rationale\": \"The old behavior made every match look equally good.\",\n          \"author\": \"sonnet\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nTop level:\n\n| Field     | Type   | Notes                                                                            |\n| --------- | ------ | -------------------------------------------------------------------------------- |\n| `version` | number | Optional; defaults to `1`.                                                       |\n| `summary` | string | Optional changeset summary. Extensions can read it; the TUI does not display it. |\n| `files`   | array  | One entry per file you want to describe.                                         |\n\nEach `files[]` entry:\n\n| Field         | Type   | Notes                                                                         |\n| ------------- | ------ | ----------------------------------------------------------------------------- |\n| `path`        | string | Required. Matches the file's current path, or its previous path for a rename. |\n| `summary`     | string | Optional. Used by the file filter, not shown as a card.                       |\n| `annotations` | array  | Optional notes for this file.                                                 |\n\nEach `annotations[]` entry:\n\n| Field                                             | Type           | Notes                                                                                                                  |\n| ------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- |\n| `summary`                                         | string         | Required. The note's headline.                                                                                         |\n| `newRange` / `oldRange`                           | `[start, end]` | Optional 1-based, inclusive line range on the new or old side. A note with no range attaches to the file's first hunk. |\n| `rationale`                                       | string         | Optional longer explanation shown under the summary.                                                                   |\n| `author`                                          | string         | Optional label for the card, such as `sonnet note`.                                                                    |\n| `markup`                                          | string         | Optional [STML](#opt-into-stml) body. Ignored unless the session launched with `--experimental`.                       |\n| `id`, `tags`, `confidence`, `source`, `createdAt` | various        | Optional metadata kept with the note. `confidence` is `low`, `medium`, or `high`.                                      |\n\nFiles listed in the sidecar appear first in the review, in sidecar order; other changed files follow in their usual order. Hunk rejects a sidecar with a missing `summary` or `path`, or a range whose end is before its start.\n\nKeep it concise: short file summaries and only rationale that improves the review. A fuller example lives at [`examples/3-agent-review-demo/agent-context.json`](https://github.com/modem-dev/hunk/blob/main/examples/3-agent-review-demo/agent-context.json).\n\n## Opt into STML\n\nSTML is experimental markup for richer note bodies: boxes, rows, badges, lists, and code. It is off by default and only the launch flag turns it on:\n\n```bash\nhunk --experimental diff --agent-context notes.json\n```\n\nA reload cannot turn STML on later, and there is no config key for it. Every note still needs a plain `summary`, which is the fallback whenever markup is unavailable. Without `--experimental`, a sidecar's `markup` is ignored and a live `--markup` comment is rejected.\n\nBefore sending markup, an agent should check support and width:\n\n```bash\nhunk session context --repo . --json\nhunk markup guide\nhunk markup render - --width <reported-noteMarkupWidth>\n```\n\nOnly send `--markup` when `experimentalFeatures` includes `stml`. `hunk markup guide` prints the tag reference and copy-paste patterns. Keep colors symbolic and markup compact so notes stay close to their code.\n","website/src/content/docs/docs/agents/comments-and-annotations.md":"---\ntitle: Comments and annotations\ndescription: Attach human or agent review notes to the code and navigate them in context.\n---\n\nNotes are hunk-specific and render beside the rows they explain. Hunk keeps them in the review flow rather than in a separate comments screen.\n\n## Kinds of notes\n\n| Term                   | What it is                                                                                           |\n| ---------------------- | ---------------------------------------------------------------------------------------------------- |\n| **Note**               | Any card shown beside the code.                                                                      |\n| **Agent comment**      | A note an agent adds to a live window with `hunk session comment`. This is the recommended path.     |\n| **Sidecar annotation** | A note loaded at launch from an [`--agent-context` JSON file](/docs/agents/agent-context-and-stml/). |\n| **Your note**          | A note you write in Hunk with `c`.                                                                   |\n| **Highlight**          | A temporary mark on a character range. It draws attention but is not a note.                         |\n\nAgent comments and sidecar annotations are both **agent notes**. They start hidden: press `a` to show or hide them, or launch with `--agent-notes`. Your own notes are always visible.\n\nNotes live in the Hunk window. Closing the window discards them.\n\n## Add one agent comment\n\n```bash\nhunk session comment add \\\n  --repo . \\\n  --file README.md \\\n  --new-line 103 \\\n  --summary \"Tighten this wording\" \\\n  --focus\n```\n\nChoose exactly one `--old-line` or `--new-line` target. `--focus` moves the user's viewport to the new note and turns agent notes on; use it for the first note in a session and when a note should actively steer the review.\n\nOptional fields:\n\n- `--rationale <text>`: a longer explanation shown under the summary.\n- `--author <name>`: the label on the note card, such as `Claude note` instead of `Agent note`.\n- `--markup <stml>`: an experimental rich body; see [Agent context and STML](/docs/agents/agent-context-and-stml/#opt-into-stml).\n\n## Apply a batch\n\n```bash\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' \\\n  | hunk session comment apply --repo . --stdin --focus\n```\n\nEach item needs a `summary` and either:\n\n- `filePath` with exactly one target: `hunk`, `hunkNumber`, `oldLine`, or `newLine`, or\n- `replyTo` by itself, to reply to an existing note.\n\nItems also accept `rationale`, `author`, and `markup`. Hunk validates the complete batch before changing the live session. `--focus` jumps to the first note in the batch.\n\n## Reply to a note\n\nA reply inherits the parent note's file and line:\n\n```bash\nhunk session comment add --repo . --reply-to user:123 --summary \"Addressed in the latest revision\"\n```\n\n## Hand your notes to the agent\n\nYour notes are a way to ask the agent questions in context:\n\n1. Press `c` on a line and write the question.\n2. Ask your agent to read your notes. It runs `hunk session comment list --repo . --type user --json` or `hunk session review --repo . --include-notes --json`.\n3. The agent answers with `--reply-to <note-id>`, and the reply appears under your note.\n\nAgents can remove your notes during cleanup but cannot create or edit them.\n\n## Inspect and clean up\n\n```bash\nhunk session comment list --repo .\nhunk session comment list --repo . --type all\nhunk session comment rm --repo . <comment-id>\nhunk session comment clear --repo . --file README.md --yes\n```\n\n`comment list` shows agent comments by default. `--type` selects other notes:\n\n| `--type`         | Lists                                         |\n| ---------------- | --------------------------------------------- |\n| `live` (default) | Comments added through `hunk session comment` |\n| `user`           | Your notes                                    |\n| `ai`             | Sidecar annotations                           |\n| `agent`          | Agent-authored notes                          |\n| `all`            | Every note                                    |\n\n`comment clear` removes agent comments. Add `--all` (or its synonym `--include-user`) to clear your notes too. Clearing always requires `--yes`.\n\n## Add your own note\n\nIn the TUI, move to a line and press `c`, or click an add-note affordance. Press `Ctrl+S` to save or Escape to cancel. Human and agent notes are labeled by source. Press `{` and `}` to move through annotated hunks across the review stream.\n","website/src/content/docs/docs/agents/live-session-control.md":"---\ntitle: Live session control\ndescription: Inspect, target, navigate, and reload Hunk windows through the local session broker.\n---\n\nEach normal Hunk TUI registers with one loopback daemon. `hunk session ...` finds a registered window and sends it review actions.\n\n## Find the session\n\n```bash\nhunk session list\nhunk session get --repo .\nhunk session context --repo .\n```\n\nUse `--repo <path>` for normal worktrees. Use an explicit session ID when multiple windows share a repository.\n\n## Inspect without overloading context\n\n```bash\nhunk session review --repo . --json\n```\n\nThis returns files and hunks. Add flags only when required:\n\n```bash\nhunk session review --repo . --include-notes --json\nhunk session review --repo . --include-patch --json\n```\n\n## Navigate the visible window\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 . --next-comment\nhunk session navigate --repo . --comment <comment-id>\n```\n\nHunk numbers are 1-based. Absolute navigation needs a file and exactly one hunk, old-line, or new-line target. `--next-comment` and `--prev-comment` move between annotated hunks; `--comment` jumps to one note by the id that `comment list --json` returns.\n\n## Reload the review\n\nAlways place `--` before the nested Hunk command:\n\n```bash\nhunk session reload --repo . -- diff\nhunk session reload --repo . -- show HEAD~1 -- README.md\n```\n\nAdvanced reloads can target the live window by `--session-path` and load from a separate `--source` directory. Prefer `--repo` until those roles genuinely need to differ.\n\n## Security and access\n\nThe daemon listens on `127.0.0.1:47657` and refuses non-loopback addresses. Session commands authenticate with an owner-private credential that Hunk discovers automatically, so only your user account can control your windows; there are no credential flags to pass. The daemon starts with the first Hunk window and shuts down about a minute after the last one closes.\n\n| Variable                         | Effect                                                                                     |\n| -------------------------------- | ------------------------------------------------------------------------------------------ |\n| `HUNK_MCP_HOST`, `HUNK_MCP_PORT` | Move the daemon to another loopback host or port, for example to avoid a port conflict.    |\n| `HUNK_MCP_DISABLE=1`             | Start the TUI without registering a session.                                               |\n| `HUNK_MCP_UNSAFE_ALLOW_REMOTE=1` | Allow a non-loopback host. Anyone who can reach the port can attempt to connect; avoid it. |\n\nIf a visible Hunk window does not appear in `session list`, an agent sandbox may be blocking loopback access. Grant the agent local network access and retry; don't expose the daemon remotely. `hunk daemon serve` starts the daemon in the foreground for debugging.\n\n## After upgrading Hunk\n\nA daemon from the previous build keeps running while any older Hunk window is open. Until it is replaced:\n\n- a window from the new build shows a status-bar notice naming both builds, and\n- every `hunk session` command fails with a `daemon-build-mismatch` error (reported in-band under `--json`).\n\nTo fix it:\n\n1. Run `hunk daemon status` to see the daemon's build and its attached windows.\n2. Run `hunk daemon restart` and confirm. Windows from the new build reattach on their own.\n3. Relaunch any windows from the old build. Their notes are lost.\n\nAgents should report the mismatch and ask before restarting the daemon.\n","website/src/content/docs/docs/agents/review-skill.md":"---\ntitle: Hunk review skill\ndescription: Install the versioned skill that teaches coding agents Hunk's live review commands.\n---\n\nEvery Hunk installation bundles a `hunk-review` skill. It teaches agents how to find a live session, inspect the review efficiently, navigate, reload, and leave notes, and it tells them not to launch the interactive TUI themselves.\n\n## Install it\n\nInstall the skill with the `skills` CLI:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-review\n```\n\n`-g` installs the skill for your user instead of one project. Add `-a <agent>` (for example `-a claude-code`) to install it for a single agent.\n\nThe installed file is a pointer: it tells the agent to run `hunk skill show hunk-review` and follow the output. The instructions therefore come from the Hunk binary on your `PATH` and stay in step with it after every upgrade; you don't need to reinstall the skill.\n\n## Load it without the skills CLI\n\n| Agent setup                             | Command                                               |\n| --------------------------------------- | ----------------------------------------------------- |\n| Loads instructions on demand            | `hunk skill show` prints the skill to stdout.         |\n| Loads or symlinks skill files from disk | `hunk skill path` prints the bundled file's path.     |\n| Can only fetch URLs                     | Use the [published copy](/docs/hunk-review-skill.md). |\n\nThe published copy tracks the newest Hunk release, so prefer `hunk skill show` when your installed Hunk might be older.\n\n## Other bundled skills\n\n`hunk-extensions` teaches agents to build [extensions](/docs/extend/extensions/) against the `hunkdiff/extension` API:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-extensions\nhunk skill show hunk-extensions\n```\n\nFor the human side of the workflow, start with [Review with an agent](/docs/agents/review-with-an-agent/).\n","website/src/content/docs/docs/agents/review-with-an-agent.md":"---\ntitle: Review with an agent\ndescription: Let a coding agent inspect and guide a live Hunk review without giving up the human review UI.\n---\n\nThe Hunk window stays with you. Your agent uses non-interactive `hunk session` commands from another terminal to inspect the same review, navigate it, and leave inline notes.\n\n## Install the review skill\n\nThe `hunk-review` skill teaches your agent the session commands. Install it once with the `skills` CLI:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-review\n```\n\nThe installed skill is a small pointer that runs `hunk skill show`, so the instructions your agent reads always match the Hunk version on your `PATH`. Add `-a <agent>` (for example `-a claude-code`) to install it for a single agent.\n\nIf your agent has no skill system, ask it to run `hunk skill show` and follow the output. See [Hunk review skill](/docs/agents/review-skill/) for other ways to load it.\n\n## Start the review\n\n```bash\nhunk diff\n```\n\nKeep that window open. Normal Hunk sessions register with a local loopback daemon so the session CLI can find them.\n\n## Ask the agent to review\n\nIn your agent, ask for the review:\n\n```text\nUse the hunk-review skill to walk me through the changes in my open Hunk session.\n```\n\nThe skill tells agents not to launch the interactive TUI themselves. A typical agent flow is:\n\n```bash\nhunk session list\nhunk session get --repo .\nhunk session review --repo . --json\nhunk session navigate --repo . --file src/App.tsx --hunk 2\nhunk session comment add --repo . --file src/App.tsx --new-line 42 --summary \"Check this boundary\" --focus\n```\n\n`review --json` exposes structure without forcing the full patch into agent context. The agent should request `--include-patch` only when it actually needs raw unified diff text.\n\n## See the agent's notes\n\nAgent notes are hidden until you turn them on. Press `a` to show or hide them, or launch with `--agent-notes` (or set `agent_notes = true` in your [config](/docs/configure/configuration/)) to show them from the start. When the agent adds a note with `--focus`, Hunk jumps to it and turns agent notes on for you.\n\n![A Hunk review with agent rationale rendered directly beside the annotated diff hunk](/docs/images/agent-comments.webp)\n\nAgent notes stay attached to the code they explain. Press `{` and `}` to move between annotated hunks while keeping the full changeset visible.\n\n## Keep control\n\nThe agent can move the visible selection and add notes, but you stay in the review stream and can navigate normally. Ask it to summarize when finished, then walk the annotated hunks with `{` and `}`.\n\nTo hand questions back to the agent, leave your own notes with `c`; see [Comments and annotations](/docs/agents/comments-and-annotations/#hand-your-notes-to-the-agent).\n\n## Give the agent the docs\n\nThese docs are published as plain Markdown so an agent can read them without scraping HTML:\n\n- [/llms.txt](https://hunk.dev/llms.txt): an index of every page, for pulling only what is needed.\n- [/llms-small.txt](https://hunk.dev/llms-small.txt): a compact corpus for tight context budgets.\n- [/llms-full.txt](https://hunk.dev/llms-full.txt): every page and the full changelog in one file.\n\nAny docs page URL also returns its Markdown source with `.md` appended, so `https://hunk.dev/docs/reference/cli.md` is the CLI reference as the agent should read it.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# hunk agent notes\n\n## purpose\n\n- Terminal-first diff viewer for understanding coding-agent changesets.\n- Product target is \"modern desktop diff tool in a terminal\", not a pager-style TUI.\n\n## architecture\n\n### workspace map\n\n```text\npackages/hunk/                 published CLI, application, and public extension/OpenTUI facades\npackages/hunk-vcs/             private dependency-bottom VCS helpers\npackages/hunk-{git,jj,sapling}/ private bundled VCS providers\npackages/hunk-gh/              private bundled GitHub review extension\npackages/session-broker-core/  low-level broker protocol and state\npackages/session-broker/       runtime-neutral broker, daemon, auth, and connection lifecycle\npackages/session-broker-{bun,node}/ runtime listener adapters\npackages/term-video/           private terminal capture tooling\nwebsite/                       product/documentation site, not the browser review client\n```\n\n### application flow\n\n```text\nmain.tsx -> app/startup.ts -> app/cli.ts\n  -> headless command/history plan returned to main.tsx, or\n  -> app plan: extensionBootstrap.ts -> sessionBootstrap.ts -> normalized Changeset / DiffFile[]\n     -> main.tsx lazy-loads runInteractiveApp.tsx -> HunkSessionHost -> AppHost -> App\n     -> pane/diff planning -> Pierre-backed terminal rows\n```\n\n### shared review seam\n\nTerminal, agent/session, broker/protocol, extension, and HTTP/SSE consumers share review semantics.\nThe browser client and UI remain planned. Do not recreate semantic review behavior in a surface:\n\n```text\nDiffFile[] -> projectReviewDocument -> ReviewDocumentV1 -> ReviewStore\nReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer -> surface projection\n```\n\n- **Model:** `packages/hunk/src/core/review/{types,document,identity}.ts` owns the ordered, JSON-safe document.\n  File order is review/sidebar order; use `key` (referenced as `fileKey` elsewhere),\n  `contentIdentity`, and `sourceIdentity` (cached source text additionally requires\n  `sourceAttested`) — not runtime IDs or indexes — across reloads/surfaces.\n- **Shared derivations:** `geometry.ts`, `expansion.ts`, `anchors.ts`, `stml.ts`, and\n  `contentManifest.ts` own ranges, gaps, source splitting, note targets/ownership, tag roles, and\n  parity manifests. Consume them; never re-derive those facts in a renderer.\n- **State:** `state.ts` is semantic state; `actions.ts` transitions; `reducer.ts` pure/no-I/O;\n  `selectors.ts` shared policies; `store.ts` synchronous observable storage. New cross-surface\n  operations start as intents. Callers supply mutable-note IDs/timestamps; core derives identities.\n- **Surfaces/publishers:** `useTerminalReview.ts` is the TUI adapter and\n  `reviewNoteMapping.ts` is terminal-only. Rows, measurement, scrolling, layout, themes, DOM\n  mechanics, and source I/O stay local. `useHunkSessionBridge.ts` publishes the current terminal\n  session export; `registration.ts` builds its metadata/initial snapshot and `bridge.ts` receives\n  agent commands. This broker export is not a full `ReviewState` mirror.\n- **Other consumers:** Web/API consumers reuse the model, derivations, state, intents, and the\n  producer/protocol tier. Never build a parallel protocol. Keep presentation/client-local state\n  local; host/extension commands need explicit remote capabilities. See\n  `docs/browser-review-rebuild.md` for the rollout and current boundaries.\n- **Conformance:** `test/review-conformance/` has hand-authored semantic fixtures covering every\n  registered core, terminal, producer, broker, protocol, and extension projection. Every new\n  semantic consumer registers its real projection and runs the whole corpus.\n  `scripts/quality/source-boundaries.test.ts` keeps the seam\n  renderer/platform-free; its Node-debt list is shrink-only and tombstone lists append-only. A\n  repaid seam finding deletes copies, adds a file or banned-symbol tombstone and adversarial\n  fixture, registers consumers, and updates `docs/browser-review-seam-audit.md`.\n\n- Bundled VCS implementations live in the private `packages/hunk-{git,jj,sapling}` workspaces and consume\n  the public `hunkdiff/extension` contract plus explicit `@hunk/vcs/*` implementation leaves;\n  `packages/hunk/src/app` composes their registrations into the\n  provider-neutral core VCS catalog. Do not add provider commands, spawning, or source readers under `packages/hunk/src/core`.\n- `hunk daemon serve` is the one loopback daemon for all live sessions; sessions auto-start and\n  register with it rather than opening per-TUI ports. Reuse `classifyReviewPublication` and\n  `ReviewChunkAssembler` for publication ordering and bounded, digest-verified resources. Browser\n  review stays same-origin with no CORS; each session mints its capability and gives the daemon only\n  its digest. Transport semantics come from the browser-safe review protocol modules and the\n  existing intent path. See `docs/browser-review-rebuild.md` and the relevant module headers.\n- User extensions, bundled VCS providers, bundled core commands, and bundled UI share one public\n  registration API and registry model. VCS and UI use dedicated registries; bundled core commands\n  load first into each session-owned extension registry so delegated resources share its lifecycle. `ExtensionSession` owns the\n  user registry across routed surfaces. Keep `packages/hunk/src/extension-api/types.ts` import-free,\n  bundled VCS renderer-free, repo-local extensions trust-gated, and bundled extensions active under\n  `--no-extensions`. See `docs/extension-architecture.md`, `docs/extensions.md`, and\n  `packages/hunk/skills/hunk-extensions/SKILL.md`.\n- Sidecar file order is intentional sidebar and review-stream order.\n- **Session wire:** the daemon and every window exchange `HUNK_SESSION_DAEMON_VERSION`\n  (`packages/hunk/src/session/protocol.ts`) in the signed hello and require an exact match. Any\n  change to what a session registers or snapshots — under `packages/hunk/src/session/**`,\n  `packages/hunk/src/core/reviewDescriptor.ts`, `packages/hunk/src/app/session/registration.ts`, or\n  anything else that appears in `packages/hunk/src/session/broker/fixtures/session-wire.v<N>.json`\n  — requires a bump. `wire.snapshot.test.ts` enforces it; bump, run\n  `bun run generate:session-wire`, and delete the previous fixture in the same change. The\n  cross-revision admin scope (`status`/`stop`) is frozen separately and never grows in place.\n- Derive shared rendering, navigation, scrolling, and note behavior from one planning layer. Make\n  shared geometry explicit, and remove obsolete paths instead of retaining parallel implementations.\n\n## architectural rules\n\n- `bun run deps:check` enforces both the `packages/*` workspace graph and\n  `packages/hunk/src/*` tiers; `.dependency-cruiser.cjs` is authoritative and\n  `docs/module-boundaries.md` explains the model. Standalone packages do not import Hunk internals.\n  Bundled providers use only provider-local modules, platform built-ins, `hunkdiff/extension`, and\n  explicit `@hunk/vcs/*` leaves. The known-violations baseline is shrink-only: fix an edge, rerun\n  `bun run deps:baseline`, and never add to it.\n- Keep the app review-first: the main pane is a single top-to-bottom stream of all visible file diffs.\n- The sidebar is for navigation. Selecting a file jumps to that file in the main review stream; it should not collapse the main pane to one file.\n- Keep Pierre as the diff engine and renderer foundation. Do not switch the main renderer back to OpenTUI's built-in `<diff>` widget.\n- Keep split and unified views terminal-native and driven from the same normalized diff model.\n- Preserve mouse + keyboard parity for primary actions.\n- Keep the chrome restrained: top menu bar, minimal borders, no redundant metadata headers.\n\n## component guidance\n\n- `HunkSessionHost` owns history/review routing in one React root. `AppHost` owns reload\n  serialization, extension adoption, and broker/content/React commit ordering. `App` owns review\n  interaction, navigation, layout, theme, filtering, and pane coordination. Pane and diff modules\n  own rendering and geometry.\n- `InteractiveSessionInitialization` in `packages/hunk/src/core/session/initialization.ts` carries\n  finalized launch inputs whose lifetime spans routed surfaces. Add a concern there only when\n  `HunkSessionHost` owns that cross-surface lifetime; keep surface-local bootstrap data out.\n- Confirmation prompts with a small set of choices should reuse `ConfirmDialog` (body rows plus a clickable key-legend action row) instead of composing `ModalFrame` with a hand-rolled footer; keyboard handling for its actions stays in `useAppKeyboardShortcuts`.\n- Extend existing components or add focused components rather than growing `App` into a monolith.\n- Shared formatting, ids, and small derivations belong in helpers, not repeated inline.\n- When refactoring logic that spans helpers and UI components, add tests at the level where the user-visible behavior actually lives, not only at the lowest helper layer.\n\n## theme guidance\n\n- Built-in theme ids and source metadata live in `packages/hunk/src/core/theme/catalog.ts`; `packages/hunk/src/ui/themes.ts`\n  derives Hunk's semantic `AppTheme` values.\n- When adding or renaming a built-in theme, update validation, public exports, docs/examples, the\n  appropriate Changeset, and tests. Keep source palette tokens separate from semantic mappings and\n  cover non-trivial derived colors.\n- `BUNDLED_SHIKI_THEME_DIFF_COLORS` in `packages/hunk/src/core/theme/catalog.ts` is generated. Edit the sourcing policy in `scripts/generate/generate-theme-diff-colors.ts`, then run `bun run generate:theme-colors`.\n\n## testing\n\n- Colocate unit tests with the code they cover (`packages/hunk/src/core/foo.ts` + `packages/hunk/src/core/foo.test.ts`, `packages/hunk/src/ui/AppHost.*.test.tsx`, `packages/hunk/src/ui/lib/*.test.ts`).\n- Put shared unit-test helpers in `test/helpers/`.\n- Name test helpers so they explicitly include `Test` and are clearly test-only (`createTestDiffFile`).\n- Use repo-level `test/` directories by intent:\n  - `test/cli/` for black-box CLI contract coverage.\n  - `test/session/` for daemon/session integration and end-to-end flows.\n  - `test/pty/` for PTY-backed live UI integration tests.\n  - `test/review-conformance/` for the shared review model's golden fixtures and per-consumer conformance suites.\n  - `test/session-broker-node/` for real Node listener/adapter conformance.\n  - `test/session-broker-runtime/` for shared Bun/Node broker fixtures.\n  - `test/smoke/` for opt-in terminal transcript smoke coverage.\n- `bun run test` does not include review conformance, PTY, TTY smoke, or real-Node adapter\n  conformance under `test/session-broker-node/`. Run the dedicated command documented in\n  `test/README.md` when changing those areas.\n\n## source formatting\n\n- Separate functions, class methods, nested helpers, and test declarations with a blank line; keep JSDoc and test-specific comments attached to their declaration.\n- Use blank lines between logical phases inside functions. Keep related statements grouped rather than packing an entire workflow into one dense block.\n\n## code comments\n\n- Add short JSDoc-style comments to functions and helpers.\n- Write header comments in active voice: the first sentence says what the module or function\n  does (\"Applies one action to the review state and returns the next state.\"), followed by its\n  invariants. Avoid passive or self-important framing (\"The one place where…\", \"the single\n  source of truth for…\") — name the behavior, not the architecture's opinion of itself.\n- For orchestration and controller modules, explain the product workflow before the mechanics:\n  name the user-visible triggers that converge there, state what the module owns, identify the\n  neighboring authority it deliberately leaves elsewhere, and call out preservation or\n  non-reloadable invariants. Prefer concrete flows (\"watch changes and manual refresh both rebuild\n  the mounted review\") over abstract labels (\"handles refresh\").\n- Add inline comments for intent, invariants, ownership, or non-obvious tradeoffs. Do not narrate\n  syntax, praise the implementation, address the reader conversationally, or record temporary\n  implementation history that belongs in Git or maintainer docs.\n\n## naming\n\n- Prefer names that match the role the code plays in the product and architecture.\n- Use `layout` for structural placement or arrangement data.\n- Use `geometry` for aggregate spatial data used by rendering, scrolling, or interaction.\n- Use `bounds` for one concrete visible extent within a larger structure.\n\n## review behavior\n\n- Default behavior is a multi-file review stream in sidebar order.\n- Layout modes are `auto`, `split`, and `unified`. `auto` chooses split on wide terminals and unified\n  on narrow ones; explicit modes override it.\n- `[` and `]` navigate hunks across the full review stream. Do not reintroduce `j`/`k` hunk navigation unless the user asks.\n- Agent context belongs beside the code, not hidden in a separate mode or workflow.\n- Agent notes are hunk-specific: show notes for the selected hunk, render them in the diff flow near the annotated row, and keep a clear spatial relationship to the code they explain.\n- Keep note behavior explicit. If the UI intentionally prioritizes one note, one selection, or one active target, encode that as a named policy rather than scattering array-index assumptions through the codebase.\n- STML markup notes (experimental) live in `packages/hunk/src/ui/lib/stml/`. The layout engine is deliberately a deterministic line layout, not OpenTUI flexbox: the row-windowed review stream needs exact note heights before mount, so `(markup, width)` must always produce the same lines. Colors stay symbolic until render time so measurement never needs a theme. Do not \"simplify\" this into flexbox renderables, and keep note-card geometry in `agentNoteGeometry` as the single source for rendering, measurement, and agent-facing width reporting.\n- Keep temporary sidecars concise and review-oriented. Their file order is intentional, while the\n  visible note UI remains hunk-note driven rather than showing generic explainer cards.\n- Agents review via `packages/hunk/skills/hunk-review/SKILL.md` using `hunk session *` commands; do not run interactive TUI commands directly.\n- `packages/hunk/skills/hunk-review/SKILL.md` is generated. Edit `packages/hunk/src/hunk-review/skillDocument.ts`, `packages/hunk/src/session/agent/surface.ts`, or `packages/hunk/src/session/agent/errors.ts`, then run `bun run generate:skill`; never hand-edit the skill file.\n\n## binary notes\n\n- Installed `hunk` is a compiled snapshot, not linked to source.\n- After source changes, rebuild/reinstall with `bun run install:bin`.\n- For rendering verification, prefer a real TTY smoke run over redirected stdout capture.\n\n## verification\n\n- For rendering changes: run `bun run typecheck`, `bun run test`, `bun run test:integration`,\n  `bun run test:tty-smoke`, and do one real TTY smoke run on an actual diff.\n- For interaction, layout, scrolling, navigation, windowing, or other terminal-native behavior: add or update PTY integration coverage in `test/pty/*-integration.test.ts` and run it with `bun run test:integration`.\n- For broker/runtime changes, run `bun run test:session-broker-node` in addition to the relevant\n  Bun tests.\n- For CLI, config, or pager work: make sure the relevant source invocation still works (`diff`, `show`, `patch`, or `pager`).\n- Preserve current interaction model unless the user asks to change it explicitly.\n\n## cross-platform support\n\n- Hunk should work on macOS, Linux, and Windows. Keep tests and CI portable unless a case is explicitly Unix-only (PTY/TTY smoke coverage is Unix-only).\n- In tests, avoid hard-coded POSIX paths, separators, shell syntax, and filenames invalid on Windows; use Node path helpers for real filesystem paths while preserving user-provided/protocol paths when pass-through is intentional.\n- If Windows-only Bun behavior appears around timers, sockets, or line endings, prefer a small compatibility fix or a narrowly scoped skip with a comment over broadening Unix assumptions.\n\n## releases\n\n- User-visible changes require a Changeset; maintenance-only changes require an empty Changeset.\n  Follow `.changeset/README.md` and do not edit `CHANGELOG.md` directly.\n- For release preparation, publishing, backports, and post-release verification, read `skills/hunk-release/SKILL.md`.\n- Never push a release tag or trigger publishing without explicit user confirmation.\n- `hunk.dev/changelog` is generated from `CHANGELOG.md` by `bun run generate:changelog`; hand-author only `website/releases/notes.json`, and never edit its output. `docs/changelog-on-hunk-dev.md` explains how release dates and the pre-tag window work.\n\n## repo notes\n\n- Local review artifacts are ignored on purpose. Leave them alone unless the user explicitly wants them updated, and do not commit them.\n- Before committing or preparing a PR, follow `CONTRIBUTING.md`.\n","isInternal":false,"tokens":3771,"sizeBytes":17006},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"AGENTS.md","isInternal":false,"tokens":3,"sizeBytes":9},{"name":"SKILL.md","path":".agents/skills/hunk-extensions/SKILL.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/.agents/skills/hunk-extensions/SKILL.md","title":"hunk-extensions","category":"anthropic-skill","format":"markdown","content":"---\nname: hunk-extensions\ndescription: Maps the `hunkdiff/extension` authoring surface for Hunk, the terminal diff viewer — hiding or reordering reviewed files, docked panes, alternate file views, commands and key bindings, dialogs, workspace writes, themes, syntax languages, VCS backends, lifecycle events. Use when writing, debugging, or installing a Hunk extension, or when a request asks Hunk itself to behave differently. Not for reviewing a diff in a live session — that is hunk-review.\n---\n\n<!-- generated by `bun run generate:skill` -->\n\n# hunk-extensions\n\nThis file is a pointer. The instructions live in the installed `hunk` CLI so they always match its version.\n\nBefore anything else, load them and follow them:\n\n```bash\nhunk skill show hunk-extensions\n```\n\nIf `hunk` is not on PATH, ask the user to install Hunk first: https://hunk.dev/docs/start/install/\n","frontmatter":{"name":"hunk-extensions","description":"Maps the `hunkdiff/extension` authoring surface for Hunk, the terminal diff viewer — hiding or reordering reviewed files, docked panes, alternate file views, commands and key bindings, dialogs, workspace writes, themes, syntax languages, VCS backends, lifecycle events. Use when writing, debugging, or installing a Hunk extension, or when a request asks Hunk itself to behave differently. Not for reviewing a diff in a live session — that is hunk-review."},"isInternal":false,"tokens":206,"sizeBytes":873},{"name":"SKILL.md","path":".agents/skills/hunk-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/.agents/skills/hunk-review/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, hunks, and exact lines, reloads session contents, adds inline review comments, and paints attention marks on character ranges. Use when the user has a Hunk session running or wants to review diffs interactively.\n---\n\n<!-- generated by `bun run generate:skill` -->\n\n# hunk-review\n\nThis file is a pointer. The instructions live in the installed `hunk` CLI so they always match its version.\n\nBefore anything else, load them and follow them:\n\n```bash\nhunk skill show hunk-review\n```\n\nIf `hunk` is not on PATH, ask the user to install Hunk first: https://hunk.dev/docs/start/install/\n","frontmatter":{"name":"hunk-review","description":"Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files, hunks, and exact lines, reloads session contents, adds inline review comments, and paints attention marks on character ranges. Use when the user has a Hunk session running or wants to review diffs interactively."},"isInternal":false,"tokens":166,"sizeBytes":709},{"name":"SKILL.md","path":"packages/hunk/skills/hunk-extensions/SKILL.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/hunk/skills/hunk-extensions/SKILL.md","title":"hunk-extensions","category":"anthropic-skill","format":"markdown","content":"---\nname: hunk-extensions\ndescription: Maps the `hunkdiff/extension` authoring surface for Hunk, the terminal diff viewer — hiding or reordering reviewed files, docked panes, alternate file views, commands and key bindings, dialogs, workspace writes, themes, syntax languages, VCS backends, lifecycle events. Use when writing, debugging, or installing a Hunk extension, or when a request asks Hunk itself to behave differently. Not for reviewing a diff in a live session — that is hunk-review.\n---\n\n# Building Hunk extensions\n\nA Hunk extension is **one TypeScript (or JSX/JS) file that default-exports a\nfactory**. Hunk imports it at startup and hands it an API object. No build step,\nno manifest required.\n\n```ts\n// ~/.config/hunk/extensions/hello.ts\nimport type { HunkExtensionAPI } from \"hunkdiff/extension\";\n\nexport default function (hunk: HunkExtensionAPI) {\n  hunk.on(\"startup\", (_event, ctx) => ctx.notify(\"Hello\"));\n}\n```\n\nThis skill is a map of the touchpoints, not a recipe. Decide what to build from\nthe user's request; use the table below to find the call, then read the linked\nmaterial before writing code.\n\n## Sources of truth — read before writing\n\n| Source                                     | What it answers                                              |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `docs/extensions.md`                       | The authoring guide. Every call, every rule. Start here.     |\n| `packages/hunk/src/extension-api/types.ts` | The contract — exact field names, optionality, doc comments. |\n| `examples/extensions/*`                    | Working extensions. Copy these patterns rather than invent.  |\n| `docs/extension-architecture.md`           | Hunk's internals. Needed only when changing the host.        |\n| `docs/keybindings.md`, `docs/themes.md`    | Chord grammar and theme token rules that extensions inherit. |\n\nOutside a Hunk checkout the guide is split across\n<https://hunk.dev/docs/extend/extensions/> (discovery, trust, config) and its\ncompanion pages — extension-api, file-previews, vcs-adapters, custom-panes —\nand the contract ships as `node_modules/hunkdiff/dist/npm/extension/index.d.ts`.\n\nThe examples, by what they demonstrate:\n\n- `review-triage/` — pane + commands + all three dialog shapes + lifecycle\n  events + the extension event bus + a `useSyncExternalStore` bridge.\n- `inline-edit/` — an interactive file-view `mode` driving `ctx.workspace` writes;\n  its README explains the async lifetime rules better than anything else in tree.\n- `rendered-markdown/` — a file view producing host-rendered rows from parsed\n  Markdown, and a folder extension with an npm dependency.\n- `code-document-file-view/` — API-v28 host-owned syntax paint over complete old/new\n  code documents, including semantic gutters and partial UTF-16 ranges.\n- `jsx-file-view/`, `jsx-file-view-gallery/` — the experimental fixed-height JSX\n  row component contract.\n\n## Where extensions live\n\n| Source                                     | Trust            |\n| ------------------------------------------ | ---------------- |\n| `--extension <path>` (repeatable)          | runs immediately |\n| `[extensions] paths` in user config        | runs immediately |\n| `~/.config/hunk/extensions/` (XDG-aware)   | runs immediately |\n| `.hunk/extensions/` or repo-config `paths` | **trust prompt** |\n\nOnly the repo-local group is gated. Everything else — including `--extension`,\neven when its path points inside the repository under review — is read as\nexplicit user intent and executes with full user permissions, no prompt. Never\npass or suggest a path you have not read, including one copied from a\nrepository's own README.\n\nA directory matches `*.ts`/`*.tsx`/`*.js`/`*.jsx`/`*.mjs` at its top level, plus\none level of folder extensions. A folder is an extension if it has a\n`package.json` with `{\"hunk\": {\"extensions\": [\"./index.ts\"]}}`, or an\n`index.{ts,tsx,js,jsx,mjs}`. Reach for a folder only when you need npm\ndependencies, helper modules, or a README; a single file keeps the install to one\n`cp`. A `.hunk/extensions/` folder extension's `node_modules` has to exist on\nevery machine that loads it — keep a repo-shared extension dependency-free.\n\nShared extensions install from git with `hunk extension install <source>`\n(`owner/repo[@ref]`, `git:host/path[@ref]`, a git URL, or a local path) into\n`~/.config/hunk/extensions/installed/<repo-name>/`, where they load with global\norigin; `list`, `update`, and `remove` manage them. Declared `dependencies` are\n`bun install`ed at install time. The manifest may state\n`{\"hunk\": {\"apiVersion\": N}}` — the minimum extension API version — and an older\nHunk refuses the extension with a startup notice instead of failing mid-factory.\nTo publish, push the folder-extension layout to a git repository's root with\nreal `name`/`version`/`description`, tag releases for `@ref` pins, and add the\n`hunk-extension` GitHub topic so it appears at\n<https://github.com/topics/hunk-extension>.\n\nThe **id** is the file stem, or the folder name for a folder extension — unless\nits manifest declares several entries, in which case each entry is its own\nextension named by its own stem (numeric suffix on collision). The id is the\nnamespace it owns: commands are `<id>.<commandId>`, panes and keyboard modes\nare `<id>:<localId>`, config `[extension.<id>]`. Ids match\n`/^[A-Za-z0-9][A-Za-z0-9_-]*$/`; `hunk`, `git`, `jj`, and `sl` are reserved. A\nbad or duplicate id is skipped with a startup notice. `[extensions] disabled = [\"<id>\"]`\nfilters a user extension before import or trust prompting. `--disable-extension <id>` and\n`--enable-extension <id>` are ordered one-run overrides; `--no-extensions` remains the hard switch\nfor all user extensions. Bundled capabilities use separate host-owned selection ids such as\n`hunk.gh`, even when their public registrations remain under the `hunk` namespace.\n\n## Pick the touchpoint\n\n| To do this                                               | Call                                                          |\n| -------------------------------------------------------- | ------------------------------------------------------------- |\n| Keep demo/training view settings temporary               | `hunk.configureSession(options)`                              |\n| Add a selectable color theme                             | `hunk.registerTheme(theme)`                                   |\n| Highlight an extension, exact filename, or filename glob | `hunk.registerFileLanguage(matcher, lang)`                    |\n| Support another VCS (`git`/`jj`/`sl` are reserved)       | `hunk.registerVcsAdapter(adapter)`                            |\n| Report read-only workspace and sibling status            | Optional `registerVcsAdapter({ status })` capability (API 31) |\n| Add a navigation/list/status pane beside the review      | `hunk.registerPane(pane)`                                     |\n| Present a file as something other than a raw diff        | `hunk.registerFileView(view)` (experimental)                  |\n| Mark character ranges inside diff lines                  | `hunk.registerLineHighlighter(highlighter)`                   |\n| Interpret review keys as a temporary global mode         | `hunk.registerKeyboardMode(mode)`                             |\n| Add a generic top-level CLI command tree                 | `hunk.registerCliCommand(command, handler)`                   |\n| Bind a key / add an Extensions-menu entry                | `hunk.registerCommand(command, handler)`                      |\n| Show persistent text on the bottom status row            | `ctx.statusLine.set(item)` in a handler                       |\n| Ask for one line of text inline, `less`-style            | `ctx.prompts.line(options)` in a command                      |\n| Hide, reorder, retitle files before review               | `hunk.transformChangeset(fn)`                                 |\n| React to loads, selection, view movement, notes, reloads | `hunk.on(event, handler)`                                     |\n| Coordinate with another loaded extension                 | `hunk.events.emit` / `hunk.events.on`                         |\n| Reload after an external agent changes reviewed inputs   | `ctx.review.requestReload()` in an event                      |\n| Read user-supplied settings                              | `hunk.config` (`[extension.<id>]` table)                      |\n| Snapshot stable files and every saved review note        | `ctx.review.snapshot()` in a command                          |\n| Branch on the API generation (currently `32`)            | `hunk.apiVersion`                                             |\n\nRegistration is only valid while the factory runs — Hunk seals the API object\nafterwards.\n\nPromise-returning VCS `watchSignature` hooks and watch cancellation require API\nversion 25. Declare `{\"hunk\": {\"apiVersion\": 25}}` in the manifest, or branch on\n`hunk.apiVersion` and return signatures synchronously on older hosts. Use async\nI/O and honor `ctx.signal` on API 25; existing synchronous hooks remain supported.\n\n### Workspace status providers (API 31)\n\nThe optional `registerVcsAdapter({ status })` capability offers cancellable, read-only\n`read`, `readSiblings`, and `planReview` operations, plus optional async `watchPlan`.\nRead [Workspace status capability](../../../../docs/extensions.md#workspace-status-capability)\nand the `ExtensionVcsStatusCapability` contract before implementing it.\n\n- Action ids are supported semantic ids, not opaque strings: `staged` reviews the index;\n  `unstaged` reviews the working copy. Offer each at most once with provider-owned labels.\n  A no-index provider offers only `unstaged` and omits `paths[].index`.\n- Return `{ cwd, input: ExtensionVcsStatusReviewInput }` from `planReview`. Preserve the\n  inspected target and opaque token validation. This exported type excludes pathspecs while\n  preserving exclusive `range` / `rangeEndpoints` comparisons. Any `pathspecs` field, even\n  `[]` or `undefined`, rejects; `input.staged` must match the offered action id.\n- Sibling ids and paths must each be unique and share the current `repositoryId`. If a row\n  matches the current worktree's id or path, both must match. Embedded ready sibling facts\n  and `readSiblings` results follow the same checks; unavailable/error rows never mean clean.\n- Bound collections before returning: 20,000 changed paths, 100 sibling rows, five operations,\n  256 watch targets, four sources per target, 20,000 watch roots/entries per target, and\n  40,000 aggregate collection entries per result (including nested and outer arrays).\n  Defensive action-array validation caps at 32, but only two unique ids are supported.\n  Oversized results reject before per-element schema traversal. Mark bounded sibling scans\n  `truncated`; honor `context.signal`, bound I/O, and reap subprocesses.\n\n### Generic CLI handlers\n\nRegister one lowercase-kebab top-level token; the handler owns every raw token\nbelow it. Built-ins and aliases cannot be shadowed, and discovery order makes\nthe first extension claim win. During development, place the explicit path\nbefore the extension command:\n\n```bash\nhunk --extension ./my-ext.ts my-command sync --help\n```\n\nThe handler receives frozen args plus `ctx.cwd`, `ctx.signal`, streaming\n`ctx.stdin`, and leased `ctx.stdout`/`ctx.stderr` writers. `summary` and\n`usage` are listed when a token reaches discovery unclaimed, so write them as\none short line each. Return `{ kind:\n\"exit\", code? }` or `{ kind: \"delegate\", argv: [\"diff\", ...] }`. Delegation is\nbuilt-in-only and one-time: do not write stdout or read stdin before delegating;\nuse stderr for progress. Reading stdin is an exit-only workflow. Respect cancellation promptly.\nRepo-local providers remain trust-gated; `--no-extensions` performs no discovery\nor import, while a leading explicit `--extension` path is immediate consent.\n\nUse `packages/hunk-gh/src/` as the reference for a complete bundled CLI preprocessor: direct\nauthenticated HTTP with cancellation, temporary artifacts with platform-accurate permission claims\nretained through delegated startup, cleanup on `shutdown`, and a one-time handoff to built-in\n`patch` without touching stdin or stdout.\n\n## What handlers receive\n\nEvery event, bus, command, and file-view mode handler — plus every changeset\ntransform — gets `ctx.cwd` and `ctx.notify(message, type?)`. A file view's\n`matches` and `layout` get no context at all. Beyond that:\n\n- **Event and bus handlers** also get `ctx.panes` (open/close/toggle/isOpen on\n  any pane), live `ctx.navigation`, attributed `ctx.dialogs`, `ctx.statusLine`\n  (set/clear this extension's status-row items), review reloads through\n  `ctx.review.requestReload()`, and\n  `ctx.events.emit`. `ctx.sidebars` is a deprecated alias for `ctx.panes`.\n- **Command handlers** get `ctx.panes`, `ctx.fileViews` (select/toggle/isActive/\n  refresh/enterMode/exitMode), `ctx.highlights` (refresh prepared line marks,\n  whole or `{ fileId }`-scoped), `ctx.selection` (a snapshot of file, hunk index,\n  nullable current `{ side, line }` source address, and `files`, the visible files\n  in review order), `ctx.navigation` (live,\n  guarded `selectFile`/`selectHunk`/`revealLine`, the\n  last landing one exact `(side, line)` near the viewport top), `ctx.commands`\n  (`isEnabled`/`execute` for public semantic `hunk.*` commands),\n  `ctx.keyboardModes` (enter/exit/probe this extension's session modes), `ctx.review`\n  (deeply immutable snapshots of stable files and complete saved store notes),\n  `ctx.dialogs` (`confirm`/`select`/`input`, queued and attributed),\n  `ctx.statusLine` (set/clear persistent status-row items), `ctx.prompts`\n  (`line`: an inline status-row input resolving the text or `null`, queued and\n  attributed like dialogs), and\n  `ctx.workspace` (`readDocument`, `canWriteDocument`, `writeDocument` with consent).\n- **Pane components** get frozen `files`, selection, placement, exact dimensions,\n  nullable immutable delegated-source `review` metadata, optional `currentLine` paint\n  (with `{ side, line }` when opted in), semantic `theme`, resolved `keybindings`, and\n  guarded navigation/notification `actions`. Availability callbacks receive the same\n  `review` value, so a pane can consume no geometry for ordinary reviews.\n- **File-view `layout`** gets `file`, `width`, `signal`, `changes`, and a lazy\n  `readDocument(side)`.\n- **File-view `mode` handlers** get `ctx.file` and `ctx.fileViews`. `onKey`,\n  `onEnter`, and `onExit` must answer **synchronously** — `onKey`'s return value\n  (`\"handled\"`/`\"pass\"`/`\"exit\"`) is the routing decision, so kick off async work\n  and report it later through `notify` or `refresh`. A passed key reaches any\n  active session keyboard mode before ordinary Hunk routing. Escape is host-owned\n  and never reaches `onKey`.\n- **Session keyboard-mode handlers** get only `ctx.commands`, `ctx.highlights`,\n  `ctx.statusLine`, and activation-scoped `ctx.keyboardModes` beyond the standard\n  context. A prompt-shaped interaction is a command plus `ctx.prompts.line()`,\n  not a mode. Those controls become inert on\n  exit, and lifecycle callbacks cannot change keyboard ownership. Keys are frozen\n  snapshots; dialogs, focused inputs, and file-view modes outrank them. When the\n  session mode owns input, Escape exits it; the status badge and Extensions menu\n  are unconditional host-owned exits.\n\nEvent payloads, pane props, and a command's selection all hand you frozen\n`ExtensionDiffFile` / `ExtensionDiffHunk` views. A changeset transform is the\nexception: it receives the live changeset and is expected to return a new one.\n`metadata` is unfrozen either way — it is the renderer's parsed diff, so pass it\nthrough untouched.\n\n## Rules that bite\n\nMost extension bugs are one of these:\n\n- **Registering a surface does not show it.** Panes need `defaultOpen`,\n  `replaces: \"hunk:files\"`, or a command that opens them. File views remain raw\n  until selected from the **View** menu.\n- **A rejected file-view layout silently becomes raw diff.** `hunkRows` needs one\n  in-bounds, inclusive entry per parsed hunk at the same array index, and\n  `sourceRanges` may not overlap on a side; invalid, oversized, cancelled, and\n  throwing layouts warn once and fall back.\n- **Syntax paint stays declarative.** File views declare complete `codeDocuments` and\n  map exact symbolic spans with `syntax`; Hunk owns tokenization, theme colors,\n  visible-window demand, resource limits, and plain fallback. Keep gutters and\n  separators in non-syntax spans, and use `sourceRanges` separately for notes/navigation.\n- **Never bundle or vendor React.** Hunk serves its own `react` and `@opentui/*`\n  to extension files; a second copy means a second hooks dispatcher and the\n  component fails to render. Import them normally. OpenTUI intrinsics (`box`,\n  `text`, `scrollbox`) need no import.\n- **`layout` is a pure derivation of `(file, width)`.** A stateful view keeps\n  painting its first answer until `ctx.fileViews.refresh(viewId)` — scope it with\n  `{ fileId }` when the state belongs to one file.\n- **Handler state must live outside the component.** Panes unmount when closed;\n  bridge module-level state into React with `useSyncExternalStore` and immutable\n  snapshots (`review-triage/index.tsx` is the working version).\n- **Use `ctx.review.snapshot()` for complete saved-note state.** `note_created` and\n  `note_edited` are incremental UI events, not an authoritative collection. Snapshots\n  include stale and orphaned saved notes, exclude drafts and static sidecar annotations,\n  and should be re-read before irreversible async work; compare both generation and revision.\n  `review-note-navigator` shows how to join stable note ids and file keys back to guarded\n  navigation after awaiting a selector; file filters can still refuse hidden targets.\n- **Retained review controls expire on reload.** An old handler cannot control\n  replacement content: pane/navigation calls become inert, dialogs cancel, and\n  workspace reads or not-yet-started writes return `null`/`unavailable`. A\n  consented write already in progress reports its real outcome, holds graceful\n  exit until it settles, and reconciles the active review on success. `shutdown`\n  runs after revocation, so use it only\n  to release extension-owned resources.\n- **A reload keeps your factory and renames the files.** Factories re-run only\n  after a trust grant or a cwd change, so module state survives — but a file's\n  `id` encodes its position in the changeset, so a reload that adds or drops a\n  file renumbers the rest. Key durable per-file state by `path`, or reconcile it\n  on `changeset_loaded`. Pick one deliberately.\n- **Transforms must preserve `metadata`** (spreading a file does), keep ids\n  unique, and return a real changeset — otherwise the transform is skipped with a\n  warning and the previous changeset carries forward.\n- **Chords are defaults.** Users remap by command id in `[keybindings]`; built-ins\n  win conflicts, refused one chord at a time. Bind the character shift produces\n  (`\"!\"`, not `\"shift+1\"`).\n- **Keyboard modes are grammar, not behavior.** Keep pending sequences and\n  numeric prefixes in the extension, then call one public `ctx.commands.execute`\n  after resolving an action. `vim-navigation` demonstrates counts, Ctrl chords,\n  and a `:` key passed to a registered command whose host input dialog temporarily\n  outranks the still-active mode.\n- **`ctx.commands` invokes Hunk, not other extensions.** Probe with\n  `isEnabled(\"hunk.review.nextHunk\")`, then call `execute(id, { count })` for an\n  explicitly public built-in. Counts are positive whole numbers up to 10,000,\n  applied atomically to movement; one-shot actions run once. Unknown, disabled,\n  private, extension-owned, or stale commands return `false`.\n- **Repo config can set `[extension.<id>]` for a globally installed extension.**\n  Treat `hunk.config` as untrusted for anything exec-adjacent (binary paths,\n  shell commands, module loading).\n- **`ctx.workspace` writes only apply to reloadable, unstaged working-tree\n  reviews**, by reviewed file id, inside the review root, with consent. Everything\n  else returns `{ ok: false, reason }` — check `canWriteDocument` first.\n- **File-view note placement is all-or-raw per file**: an unbound or range-less\n  visible note makes Hunk render the complete raw diff instead of guessing.\n- **Failures are contained, not sandboxed.** A throwing factory is rolled back to\n  zero registrations and a throwing handler is a warning naming the extension —\n  containment against bugs, not against code that should not have been loaded.\n- **The API touches nothing outside the review.** No clipboard, no filesystem, no\n  process surface beyond `ctx.workspace` — an extension is ordinary code, so shell\n  out for the rest. Never write to stdout: the renderer owns it. For the same\n  reason `hunk.log` is collected as diagnostics and printed nowhere; `ctx.notify`\n  is how a user hears from you.\n- **`HunkExtensionUserError`** (detected structurally by `name`) buys the full\n  treatment — message plus `suggestions`, no stack trace — only from a VCS adapter\n  operation, which is where Hunk formats it for the CLI. From a command or event\n  handler only the message survives, as a warning toast.\n\n## Verifying\n\nHunk's TUI needs a real terminal, and the review UI is the user's — **do not\nlaunch `hunk diff`/`hunk show` to test, and do not reach for a pipe.** No\ninvocation applies extensions headlessly: `hunk diff … | cat` still starts the\napp and still takes the keyboard, so it hangs holding the user's terminal.\nPractical checks, in order of cost:\n\n1. **Typecheck.** In a checkout, `bun run typecheck` covers\n   `examples/extensions/**` via the `hunkdiff/extension` path mapping. Standalone,\n   add `hunkdiff` as a dev dependency and run `tsc --noEmit`; for a `.tsx`\n   extension also add `react`, `@types/react` (React ships no declarations of its\n   own), `@opentui/core`, and `@opentui/react` as **dev** dependencies and set\n   `\"jsx\": \"react-jsx\"` with\n   `\"jsxImportSource\": \"@opentui/react\"`, or every `<box>` and `<text>` is an\n   untyped intrinsic. Types only — shipping those packages is the second-React bug.\n2. **Unit-test the logic.** When parsing, matching, or formatting is worth\n   testing, put it in helper modules with plain `bun test` coverage.\n3. **PTY integration.** In a checkout, `test/pty/extensions-integration.test.ts`\n   launches Hunk over a PTY with `--extension <path>` and asserts on rendered\n   snapshots; extend it via `test/pty/harness.ts` and run `bun run test:integration`.\n4. **Hand it to the user** to run: `hunk diff --extension ./my-ext`. `--extension`\n   loads immediately with no trust prompt, so it is the iteration path. Ask them\n   what the footer notices and toasts said.\n5. **Triage with `--no-extensions`** to confirm a symptom belongs to an extension\n   (bundled VCS backends, the built-in files pane, and the `/` content search stay loaded\n   either way).\n\n## If it does not load\n\n- No startup notice at all → a successful load is silent, so either it loaded and\n  nothing opened it, or discovery never saw the file. Check the directory, the\n  entry suffix, or the folder's `package.json` `hunk.extensions` paths.\n- Notice naming the extension → id rejected (reserved, malformed, or already\n  claimed), import failure, missing default export, or a throwing factory.\n- Repo-local extension silently absent → the trust prompt was dismissed or denied;\n  decisions are stored per repo root in `~/.config/hunk/state.json`.\n- Pane closes with a toast → the component threw; a second React copy is\n  the usual cause.\n- Pane or file view never appears → nothing opened it (no `defaultOpen`, no\n  command), `matches` returned false, or the layout was rejected.\n- Command never fires → its chord lost to a built-in or an earlier extension (a\n  warning says so); it is still reachable from the **Extensions** menu and\n  bindable by `<id>.<commandId>`.\n\n## Changing Hunk itself\n\nOnly when the work is in the `hunk` repo rather than in a user extension:\n\n- Shipped VCS backends, `hunk gh`, the built-in files pane, and the `/` content search are\n  **bundled extensions**, registering through the same public API. That dogfooding is deliberate —\n  if the public contract cannot express something, that is a real gap, not a reason for a private\n  path. `default/vcs/` loads from VCS adapter resolution and must stay renderer-free;\n  `default/core/` loads session-owned CLI capabilities from private workspaces such as\n  `packages/hunk-gh`; and `default/ui/` holds process-cached rendering registrations.\n- `packages/hunk/src/extension-api/types.ts` must stay **import-free**; declaration emission\n  publishes whatever it reaches, and `scripts/packaging/check-pack.ts` fails the pack\n  otherwise. Shapes shared with internal code are declared there and re-exported\n  inward.\n- New API surface means updating `docs/extensions.md` (its examples are\n  typechecked as consumer code), the matching hand-written page under\n  `website/src/content/docs/docs/extend/` (only `cli.md` and `config.md` are\n  generated), `docs/extension-architecture.md` if ownership moves, and a changeset.\n- `AGENTS.md` and `docs/extension-architecture.md` own the rest of these rules.\n","frontmatter":{"name":"hunk-extensions","description":"Maps the `hunkdiff/extension` authoring surface for Hunk, the terminal diff viewer — hiding or reordering reviewed files, docked panes, alternate file views, commands and key bindings, dialogs, workspace writes, themes, syntax languages, VCS backends, lifecycle events. Use when writing, debugging, or installing a Hunk extension, or when a request asks Hunk itself to behave differently. Not for reviewing a diff in a live session — that is hunk-review."},"isInternal":false,"tokens":5884,"sizeBytes":25389},{"name":"SKILL.md","path":"packages/hunk/skills/hunk-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/hunk/skills/hunk-review/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, hunks, and exact lines, reloads session contents, adds inline review comments, and paints attention marks on character ranges. 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\n10. hunk session highlight add ...                      # light up the exact range you are explaining\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 (<session-id> | --repo <path>) [--json]\nhunk session context (<session-id> | --repo <path>) [--json]\nhunk session review (<session-id> | --repo <path>) [--include-patch] [--include-notes] [--json]\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- `review --include-notes` also returns the live review notes alongside the file and hunk structure\n\n### Navigate\n\n```bash\nhunk session navigate (<session-id> | --repo <path>) --file <path> (--hunk <n> | --old-line <n> | --new-line <n>) [--json]\nhunk session navigate (<session-id> | --repo <path>) --comment <id> [--json]\nhunk session navigate (<session-id> | --repo <path>) (--next-comment | --prev-comment) [--json]\n```\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\nExact comment navigation uses the `commentId` returned by `hunk session comment list --json` and does not require `--file`:\n\n```bash\nhunk session navigate --repo . --comment comment-1\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- A line target lands the user's viewport on that exact line (falling back to its hunk when the line is inside a collapsed region); `--hunk` lands on the hunk\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 (<session-id> | --repo <path> | --session-path <path>) [--source <path>] [--json] -- diff [ref] [-- <pathspec...>]\nhunk session reload (<session-id> | --repo <path> | --session-path <path>) [--source <path>] [--json] -- show [ref] [-- <pathspec...>]\n```\n\nExamples:\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 (<session-id> | --repo <path>) (--reply-to <note-id> | --file <path> (--old-line <n> | --new-line <n>)) --summary <text> [--rationale <text>] [--author <name>] [--markup <stml>] [--focus] [--json]\nhunk session comment apply (<session-id> | --repo <path>) --stdin [--focus] [--json]\nhunk session comment list (<session-id> | --repo <path>) [--file <path>] [--type <live|all|ai|agent|user>] [--json]\nhunk session comment rm (<session-id> | --repo <path>) <comment-id> [--json]\nhunk session comment clear (<session-id> | --repo <path>) [--file <path>] [--include-user|--all] --yes [--json]\n```\n\nExamples:\n\n```bash\nhunk session comment add --repo . --file README.md --new-line 103 --summary \"Tighten this wording\"\nhunk session comment add --repo . --reply-to user:123 --summary \"Addressed in the latest revision\"\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' | hunk session comment apply --repo . --stdin\n```\n\n- Agent notes start hidden: the user sees them only after pressing `a` or setting `agent_notes = true`. `--focus` jumps to the new note (or the first note in a batch) and turns agent notes on, so pass it on the first note you leave in a session, or tell the user to press `a`\n- `comment list` shows the comments added through `hunk session comment` by default; `--type user` lists notes the user wrote with `c`, `--type ai` lists notes loaded from an `--agent-context` sidecar, `--type agent` lists agent-authored notes, and `--type all` lists every note\n- `comment add` is best for one note; `comment apply` is best when an agent already has several notes ready\n- Root `comment add` notes require `--file`, `--summary`, and exactly one of `--old-line` or `--new-line`; replies use `--reply-to <note-id>` with `--summary` and inherit the parent's anchor\n- `comment apply` items require `summary` plus either `replyTo` by itself or `filePath` with 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- `comment list` and `comment clear` accept optional `--file`\n- Quote `--summary` and `--rationale` defensively in the shell\n\n### Attention marks\n\nHighlights paint character ranges inside the diff lines the user is looking at — use them to light up the exact expression you are explaining while you narrate.\n\n```bash\nhunk session highlight add (<session-id> | --repo <path>) --file <path> (--old-line <n> | --new-line <n>) --start <n> --end <n> [--tone <tone>] [--focus] [--json]\nhunk session highlight clear (<session-id> | --repo <path>) [--file <path>] [--json]\n```\n\nExamples:\n\n```bash\nhunk session highlight add --repo . --file src/App.tsx --new-line 42 --start 6 --end 19\nhunk session highlight add --repo . --file src/App.tsx --new-line 42 --start 6 --end 19 --tone warning --focus\nhunk session highlight clear --repo .\n```\n\n- `highlight add` requires `--file`, exactly one of `--old-line` or `--new-line`, and the `--start` / `--end` offsets\n- `--start` is a 0-based inclusive offset into the line's text and `--end` is exclusive, counted in UTF-16 code units — the same `[start, end)` range extensions use\n- Tones: `match` (default), `info`, `warning`, `error`, `dim`; `current` renders as reverse video and is best reserved for the one range under discussion\n- Pass `--focus` to also land the viewport on the marked line\n- Marks survive scrolling, navigation, and reloads that leave the marked file's content unchanged; a reload that changes that file drops its marks, and `highlight clear` removes them explicitly (optionally per `--file`)\n- Marks are visual only — pair them with a `comment add` when the explanation should persist as a note\n\n### Experimental rich markup notes (STML)\n\nOnly use STML when `hunk session context --json` lists `stml` in `experimentalFeatures`. The user opts into that experience by launching the review with `--experimental`; do not ask a normal session to render markup.\n\nFor an opted-in session, `--markup` (or a `markup` field on apply items) renders the note body as STML — a small HTML-like markup for terminal UI (boxes, rows, gauges, badges, lists, code). Keep `--summary` a real sentence: it is the fallback and the `comment list` text.\n\nBefore writing markup, run `hunk markup guide` once — it has copy-paste patterns and the width rules. The session context also reports `noteMarkupWidth` (the live render width); preview with `hunk markup render - --width <that>`. Comment responses echo `markupWidth` and return `markupNotes` when markup degraded — fix what they flag.\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 `highlight add --focus` to steer the user's eyes to the exact expression while you explain it, and `highlight clear` before moving to the next topic\n- Use `comment apply` for agent-generated batches and `comment add` for one-off notes\n- Use `--focus` on your first note so agent notes become visible, then sparingly when a note 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 diff file matches ...\"** -- the file is not in the loaded review. Check `context`, then `reload` if needed.\n- **\"No active Hunk sessions\"** -- if Hunk is visibly running, localhost may be blocked by the agent sandbox; retry with network/sandbox escalation. Otherwise ask the user to open Hunk.\n- **\"Multiple active sessions match\"** -- pass `<session-id>` explicitly.\n- **\"No active 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 exactly one comment target\"** -- pass `comment add` one of `--old-line` or `--new-line`.\n- **\"Specify exactly one highlight target\"** -- pass `highlight add` one of `--old-line` or `--new-line`.\n- **\"Highlight --end must be greater than --start\"** -- offsets are `[start, end)` UTF-16 code units into the line text; end is exclusive.\n- **\"Specify either --next-comment or --prev-comment, not both.\"** -- choose one comment-navigation direction.\n- **\"The session daemon is ...\"** -- a `daemon-build-mismatch` (the `--json` error carries `daemon`, `cli`, `attachedSessions`, and `recommendedAction`). Tell the user which build is newer and how many windows are attached, then **ask** before running `hunk daemon restart --yes`; never restart unprompted. After the restart, windows that failed to register attach on their own, so re-run `hunk session list` instead of relaunching anything. When `recommendedAction` is `use-newer-hunk`, the daemon is the newer build: use that Hunk instead.\n- **\"Could not read the raw diff for ...\"** -- the session reloaded or closed while `--include-patch` was reading it. Re-run `review`; drop `--include-patch` if you only need file and hunk structure.\n","frontmatter":{"name":"hunk-review","description":"Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files, hunks, and exact lines, reloads session contents, adds inline review comments, and paints attention marks on character ranges. Use when the user has a Hunk session running or wants to review diffs interactively."},"isInternal":false,"tokens":3511,"sizeBytes":14089},{"name":"SKILL.md","path":"skills/hunk-launch-video/SKILL.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/skills/hunk-launch-video/SKILL.md","title":"hunk-launch-video","category":"anthropic-skill","format":"markdown","content":"---\nname: hunk-launch-video\ndescription: Produces Hunk videos by driving the real TUI headlessly in a PTY, compositing captioned 1080p frames in Chromium, and encoding with ffmpeg. Use for feature demos, workflow explainers, announcements, launch videos, and full-release roundups.\nmetadata:\n  internal: true\n---\n\n# Hunk video pipeline\n\nSource-checkout only: the pipeline lives in `scripts/launch-video/`, which never ships to npm.\nIt can be used for pull-request demos as well as maintainer release videos. Unix-only — the\ncapture scripts exec `/bin/bash`.\n\nGenerates product videos where every terminal frame is the real Hunk TUI —\nno screen recording, no mockups. Three stages:\n\n```text\ncapture.ts   (bun)               drive Hunk over a PTY, snap styled keyframes to PNG\ncompose.mjs  (node + Playwright) render each PNG on a 1920x1080 HTML stage in Chromium\nffmpeg                           encode the composited PNGs at 30fps to mp4/webm\n```\n\nPlaywright controls headless Chromium: for each planned frame it loads the\nterminal PNG onto the HTML stage, applies the window chrome, cards, captions,\nand transition state, then screenshots the completed stage back to PNG. ffmpeg\nsequences those composited screenshots into the final videos.\n\nThe generic machinery (PTY driving, keyframe rendering, storyboard planning,\nChromium compositing, the stage template) is the `@hunk/term-video` workspace\npackage in `packages/term-video/`; `scripts/launch-video/` holds only Hunk's\nscenes, captions, and cards on top of it.\n\n## Choosing a recipe\n\nThe pipeline is not release-specific. Choose the editorial scope, then use the\nsame capture → composite → encode stages:\n\n- **Single feature:** a short demonstration of one capability or workflow. Use\n  the single-feature recipe below and capture only the required scene.\n- **Full release:** the social promo for a release, in Hunk's house style\n  (`scripts/launch-video/promo/`). Use the full-release recipe: write one\n  storyboard file for the release; the style kit stays unchanged.\n- **Custom video:** author any set of scenes and `SHOTS` for tutorials,\n  comparisons, announcements, or workflow explainers; follow the scene and\n  storyboard rules below.\n\n## Creating a video\n\nExpect ~3–6 min for capture and ~2–4 min for compose — run both with a long\ntimeout (or in the background); each logs per-snap / per-shot progress.\n`compose.mjs` needs node ≥ 18 on PATH (bun alone is not enough).\n\n```sh\n# 0. dependencies (tuistory is a devDependency; ghostty-opentui arrives transitively)\nbun install    # if a postinstall hook fails in a sandbox, retry with --ignore-scripts\n\n# 1. capture the current storyboard's default scenes. Output defaults to\n#    <repo>/.video-work/ regardless of cwd (pass a path argument to override),\n#    but the PROCESS must run from the repo root — see gotchas.\nbun run scripts/launch-video/capture.ts\n\n# 2. one-time portable compositor setup. Playwright installs a Chromium build\n#    that exactly matches its browser driver (see gotchas to reuse a system or\n#    sandbox browser instead).\nprintf '{\"name\":\"hunk-video-work\",\"private\":true}\\n' > .video-work/package.json\ncd .video-work\nbun add playwright playwright-core\nbunx playwright install chromium\ncd ..\n\n# 3. composite the storyboard\nnode scripts/launch-video/compose.mjs .video-work\n\n# 4. encode\ncd .video-work\nffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n  -c:v libx264 -preset slow -crf 18 -movflags +faststart launch.mp4\nffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n  -c:v libvpx-vp9 -b:v 0 -crf 32 -row-mt 1 launch.webm\n```\n\nOverride the default scene set to iterate on one or more scenes:\n\n```sh\nSCENES=review bun run scripts/launch-video/capture.ts   # comma-separated scene names\n```\n\nScene names are the `wants(\"...\")` guards in `capture.ts`'s `main()`. `SCENES=`\nreplaces the canonical default scene set; `compose.mjs` preflights that every\nframe its `SHOTS` table references exists in `frames/` and fails fast listing\nany missing ones, so a full composite still needs every referenced scene\ncaptured at least once.\n\n## Single-feature recipe\n\nFor a short test, demo, or one-feature announcement, capture and composite only\nthe scene for that feature:\n\n1. Pick one user-visible capability and find its scene name in the\n   `wants(\"...\")` guards. If it does not have a scene, author one using the\n   guidance below. Capture only that scene:\n\n   ```sh\n   SCENES=review bun run scripts/launch-video/capture.ts\n   ```\n\n2. Make a scratch compositor beside the canonical one so its imports and\n   repo-relative paths continue to work:\n\n   ```sh\n   cp scripts/launch-video/compose.mjs scripts/launch-video/compose-one-feature.mjs\n   ```\n\n3. In the scratch copy, trim `SHOTS` to an opening card, only the selected\n   feature's frames, and an outro card. Rewrite those cards and captions for\n   the scoped cut. Sequence lengths must still match the captured frame names.\n4. Composite into the same work directory, then run the normal ffmpeg commands\n   with descriptive output names:\n\n   ```sh\n   node scripts/launch-video/compose-one-feature.mjs .video-work\n   cd .video-work\n   ffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n     -c:v libx264 -preset slow -crf 18 -movflags +faststart hunk-feature-demo.mp4\n   ffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n     -c:v libvpx-vp9 -b:v 0 -crf 32 -row-mt 1 hunk-feature-demo.webm\n   cd ..\n   rm scripts/launch-video/compose-one-feature.mjs\n   ```\n\nKeep the scratch compositor uncommitted. The canonical `compose.mjs` remains\nthe checked-in reference storyboard. If you added a capture scene only to make\nPR evidence, revert that scene after encoding; retain it only when it is useful\nchecked-in demo coverage and belongs to the submitted change.\n\n## Full-release recipe\n\nRelease videos use the promo style kit in `scripts/launch-video/promo/`, so\nevery release looks like part of one series. The kit renders through\n`@hunk/term-video/timeline`:\n\n```text\nstoryboards/<version>.mjs   release content: chapters, captions, montage, credits range\nengine.js + stage.html      the style: sting, countdown hook, chapter cards, terminal\n                            scenes, montage, contributors h, outro, transitions\ncontributors.mjs            git range -> contributors.json + avatars/\nrender.mjs                  storyboard + frames + contributors -> promo-<version>.mp4\n```\n\n1. **Pick the headlines.** Read the release section in `CHANGELOG.md` (or its\n   hand-written **Highlights**) and choose 4–6 chapters ordered by impact: new\n   capabilities first, then visible UI/UX changes, then smaller conveniences.\n   Per-PR entries that are too small for a chapter go in the montage. Check\n   that each claim is accurate: a Patch-level fix can earn a chapter, but name\n   what it does rather than calling it new. Confirm the shortlist with the\n   user before capturing.\n2. **Start the storyboard** by copying the newest one, for example\n   `cp storyboards/0.24.0.mjs storyboards/0.25.0.mjs`. Set `version`,\n   rewrite `chapters` and `montage`, set `montage.fixes.count` from the\n   changelog's patch entries, and point `contributors.from`/`to` at the\n   previous release tag and this release's tag. Leave `aliases` from older\n   releases out unless they still apply.\n3. **Capture** each chapter's keyframes, reusing or adding scenes in\n   `capture.ts` (see \"Authoring scenes\"); every `frames[].img` must name a\n   captured keyframe. `SCENES=a,b bun run scripts/launch-video/capture.ts`.\n4. **Credits.** Fetch tags and history (`git fetch --tags --unshallow` in a\n   shallow clone), then run\n   `node scripts/launch-video/promo/contributors.mjs .video-work`. Review the\n   printed table: everyone who authored or co-authored a commit in the range\n   appears once, bots and AI co-author trailers excluded. Map unresolved\n   people to logins with `contributors.aliases` (email or name to a login, or\n   `{ login, id }` when the GitHub users API is unavailable) and rerun; anyone\n   still unresolved is credited by name with an initials tile. Never guess an\n   account.\n5. **Work dir.** Besides `playwright-core` (see gotchas), the kit needs the\n   brand font: `cd .video-work && bun add @fontsource-variable/jetbrains-mono`.\n6. **Stills first.** `node scripts/launch-video/promo/render.mjs .video-work\n--stills 2.6,8,14,...` renders review frames in seconds; check every\n   chapter's callouts and captions before the full render. `render.mjs`\n   validates the storyboard and lists any missing keyframes up front.\n7. **Render** with `node scripts/launch-video/promo/render.mjs .video-work`\n   (about 3 minutes for a minute of video) and verify the cut (see\n   \"Verification\"). When `hunk-release` invoked this workflow, return the\n   approved MP4 to that skill for versioned naming and embedding; do not upload\n   or edit the public release without its confirmation gate.\n\n### Chapter reference\n\nTimes are seconds from the start of the chapter's terminal scene; camera,\nhighlight, and label coordinates are fractions of the captured frame.\n\n- `label`, `title` (lines; prefix a word with `*` to color it), `sub`, `cmd`\n  (typed into the title card's command chip), `winTitle`.\n- `frames: [{ img, dur, key?, fade? }]` plays keyframes in order. `key` shows\n  that keycap pressing as the frame appears, so one frame per keypress reads\n  as typing; `fade` crossfades from the previous frame (use it for layout\n  swaps). The `steps()` helper in the 0.24.0 storyboard builds key-walk runs.\n- `cameras: [[t, { x, y, scale }, motionSeconds?]]` must start at time 0.\n- `highlights: [{ t0, t1, x, y, w, h }]` and `labels: [{ t0, t1, x, y, text }]`\n  outline and annotate source regions; keep edges in blank rows and labels off\n  terminal text (see \"Camera and callout composition\").\n- `captions: [[t, html]]` with `<b>` for the accent color.\n- `bodyH` gives short shell output a compact window instead of a full screen.\n\n### Extending the style\n\nKeep release copy out of `engine.js`: it reads everything release-specific\nfrom the storyboard, and its defaults hold only brand constants (tagline,\ninstall command, site). When a release needs something the kit cannot show,\nadd a reusable scene type or storyboard field to the engine and document it\nhere rather than special-casing one release. The brand geometry in the engine\nfollows `website/public/brand/`; update both together.\n\nThe caption storyboard below (`compose.mjs` on the shared stage) remains the\ntool for single-feature demos, PR evidence, and custom explainers.\n\n## Per-video editorial surface\n\nThis section covers the caption storyboard used for single-feature, PR, and\ncustom videos; release promos keep their content in `promo/storyboards/`. The\ncapture machinery is reusable, but the storyboard is editorial content for one\nvideo. Rewrite it to match the video's scope. The checked-in reference is the\n0.24 release roundup, not a frozen release artifact:\n\n- `compose.mjs`: the whole `SHOTS` table; opening, feature, and outro cards;\n  every caption; camera targets; and callout rectangles. A `NEW` badge is a\n  claim about the video at hand, so drop or move it as features age.\n- `capture.ts`: `SCENES` selects from a reusable scene library. With no\n  override it captures only the current storyboard's default scene; update\n  that default whenever the canonical storyboard changes. Hunk-side glue\n  (`launchHunk`, `launchHunkShell`, demo repositories, and the keyboard probe)\n  remains reusable.\n\nReusable machinery lives in `@hunk/term-video` (`packages/term-video/`) —\nextend it there, don't fork it into the scripts: `createKeyframer`,\n`launchApp`/`launchShell`, `createCommandWrapper`, `typeCommand`,\n`ensureKeyboardIsLive`, `makeSceneFilter` (`src/capture.ts`); the unit-tested\nstoryboard planner with the caption/timing semantics (`src/plan.mjs`);\n`composeStoryboard` with font/Chromium resolution and the missing-keyframe\npreflight (`src/compose.mjs`); the stage template (`src/stage.html`); and\n`renderTimeline` for free-form motion pages such as the promo kit\n(`src/timeline.mjs`).\n\n## Environment gotchas\n\nSandbox-specific bullets are marked; each cost real debugging time.\n\n- **Run `capture.ts` with bun from the repo root.** tuistory uses subpath\n  self-imports (`tuistory/pty`) that only resolve inside this repo's\n  `node_modules`; running the script from elsewhere resolves tuistory from\n  bun's global cache and crashes. (Output location is unaffected — it defaults\n  to `<repo>/.video-work/` via `import.meta.url`.)\n- **ghostty-opentui is a transitive dep** (via tuistory) with an exports map:\n  import `\"ghostty-opentui/image\"` (not `.../dist/image.js`), resolved\n  relative to tuistory — `@hunk/term-video/capture`'s `createKeyframer` does\n  the `Bun.resolveSync` dance.\n- **Playwright must match the Chromium it drives.** The portable setup above\n  installs `playwright` and `playwright-core` together, then downloads their\n  matching Chromium build. It works on macOS and Linux and is preferred when\n  bandwidth and browser downloads are available; leave `CHROMIUM_PATH` unset\n  so Playwright uses that managed browser. On Linux, if Chromium reports\n  missing system libraries, run `bunx playwright install-deps chromium` (it\n  may require sudo) before retrying.\n\n  To reuse an existing system, CI, or sandbox Chromium instead, install the\n  `playwright-core` version provided by that environment and set its executable\n  explicitly:\n\n  ```sh\n  cd .video-work\n  bun add playwright-core@<matching-version>\n  cd ..\n  CHROMIUM_PATH=/path/to/chromium node scripts/launch-video/compose.mjs .video-work\n  ```\n\n  Common executable locations include `$(command -v chromium)` or\n  `$(command -v google-chrome)` on Linux and\n  `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` on macOS.\n  In an environment with a preinstalled Playwright toolchain, read that\n  toolchain's `package.json` to get the exact driver version. For example, the\n  Anthropic sandbox exposes it through `/opt/pw-browsers/.links/*`:\n\n  ```sh\n  cat \"$(cat /opt/pw-browsers/.links/* | head -1)/package.json\" | grep '\"version\"'\n  # e.g. \"1.56.1\" -> bun add playwright-core@1.56.1\n  ```\n\n  `compose.mjs` picks its browser as `$CHROMIUM_PATH`, then\n  `/opt/pw-browsers/chromium` when present, then Playwright's managed browser.\n  If a dependency update leaves Playwright asking for an uninstalled browser\n  revision, either rerun `bunx playwright install chromium` in `.video-work/`\n  or set `CHROMIUM_PATH` to a known system browser such as `/usr/bin/chromium`.\n\n- **Give `.video-work/` its own `package.json` before `bun add`.** Without\n  one, bun walks up and installs into the repo's `package.json` — revert with\n  `git checkout package.json bun.lock` if that happens.\n- **Chromium needs `--allow-file-access-from-files`** (already in\n  `compose.mjs`): the stage samples each keyframe through a canvas to\n  color-match the window background, and file:// images taint the canvas\n  without it.\n- **mp4 needs an ffmpeg with libx264.** Sandbox: `apt-get install ffmpeg`\n  (run `apt-get update` first if packages 404). macOS: `brew install ffmpeg`.\n  Verify `ffmpeg -encoders | grep -E 'libx264|libvpx-vp9'` shows both before\n  encoding — playwright's bundled `ffmpeg-*/ffmpeg-linux` only does VP8/WebM\n  and cannot produce the mp4.\n- **Caption font**: JetBrains Mono ships inside ghostty-opentui;\n  `findCaptionFont` in `packages/term-video/src/compose.mjs` searches bun's\n  isolated layout (`node_modules/.bun/node_modules/…`) then a hoisted\n  `node_modules/…`. If it still throws `caption font not found`, locate the\n  file with `find node_modules -name jetbrains-mono-nerd.ttf` and pass it as\n  `fontPath` to `composeStoryboard`.\n\n## Authoring scenes (capture.ts)\n\n- Shared geometry is 140x32 cells rendered at fontSize 16 / dpr 2 → 2688x1536\n  PNGs. Keep every scene at this size so all frames fit one window.\n- Keep time-sensitive fixtures recent and timezone-stable. The canonical\n  history scene derives commit dates from the current UTC day and launches\n  Hunk with `TZ=UTC`, so day groups and relative-age labels remain useful on\n  future runs. Preserve that policy in custom history scenes.\n- Helpers: `createDemoRepo()` and `launchHunkShell()` are hunk-side glue in\n  the script (git repo built from `examples/2-mini-app-refactor`; interactive\n  bash with a real `hunk` command on PATH and a clean `❯` prompt); `snap`,\n  `typeCommand`, `launchApp`/`launchShell`, and `createCommandWrapper` come\n  from `@hunk/term-video/capture`.\n- Always `waitForText` on scene-specific content before the first snap, and\n  call `ensureKeyboardIsLive()` before scripted keypresses — the first key\n  after startup can be dropped (real race, the helper toggles `?` to prove\n  keys land).\n- **Animation = one snap per keypress.** Cursor walks and typing effects are\n  just every `j`/`k`/character captured as its own frame and played back at\n  0.2–0.3s per frame. Prefer this over sparse keyframes: three stills read as\n  a slideshow, per-press frames read as motion.\n- `renderTerminalToImage` auto-trims trailing blank rows, so short outputs\n  (CLI scenes) produce short PNGs — the stage handles this by sampling the\n  image's bottom-left pixel and painting the window body to match.\n- `manifest.json` is a capture-side inventory of the _current run_ only;\n  `compose.mjs` ignores it (frames resolve by name from `SHOTS`), and after a\n  `SCENES=` run it is partial while `frames/` stays cumulative.\n- Demo content that must exist: STML notes come from\n  `examples/9-agent-markup-notes` (launch with `--experimental`), extension\n  scenes from `examples/extensions/` loaded via `--extension <path>` (explicit\n  paths skip the repo trust prompt). The pager pipe is `git diff | hunk pager`\n  — bare `hunk` on piped stdin prints help. Sidebar toggle is `s`; comment\n  draft is `c`, save with Ctrl+S (`\\x13`).\n\n## Storyboard model (compose.mjs)\n\n- `SHOTS` is the whole edit: one entry per shot, `dur` in seconds, played as\n  unique frames + per-frame durations in an ffmpeg concat list (holds cost one\n  frame, so runtime is dominated by transitions, not length).\n- `capKey` is caption identity: the caption slides in only when `capKey`\n  changes, and continuation shots that share a `capKey` without restating\n  `caption` keep the previous caption on screen. Sequences (walks, typing) are\n  generated with `Array.from` spreads.\n- **Sequence lengths must match capture loop bounds**: the open capture's\n  four-step tree walk plus its select and expand presses produce\n  `open-tree-0..5`, consumed by a six-entry `Array.from` spread in `SHOTS`.\n  Change one side and the other breaks — the preflight check names any frame\n  that's missing.\n- `enter: true` fades/scales the surface in — use it for cards and the first\n  terminal shot only.\n- Terminal shots may set normalized `camera: { x, y, scale }` targets and\n  `highlight: { x, y, width, height, label? }` rectangles. `motion` controls\n  the transition length in seconds. The planner interpolates camera changes\n  and outline geometry, while the stage keeps the camera inside the captured\n  image and paints an animated glow around the source-aligned region. Set the\n  same `cameraKey` or `highlightKey` on related keyframes when their normalized\n  coordinates share one capture geometry. Cross-image camera changes cut to\n  their target unless `cameraKey` explicitly permits a pan, and outlines never\n  leak across unrelated images.\n- Caption HTML vocabulary: `<span class=\"badge\">NEW</span>` amber pill,\n  `<span class=\"hl\">` amber highlight, `<span class=\"dim\">` muted. Cards use\n  `badge` / `h1`/`h2` / `sub` / `cmds`+`cmd` / `foot` classes from\n  `packages/term-video/src/stage.html`.\n- Target pacing: money shots hold 3–4s, context shots 2–3s, typing/walk frames\n  0.2–0.6s; keep the total near 60s.\n\n## Camera and callout composition\n\nTreat `camera` and `highlight` as one composition. Both use normalized source\ncoordinates, but the stage projects highlights through the active camera. A\nrectangle that fits within the source can still lose an edge after zooming.\n\nUse this framing process:\n\n1. Start with a full-frame establishing shot, then pan or zoom to one readable\n   subject. Use `motion` around 0.7–0.9s for a deliberate move rather than a\n   cut disguised as animation.\n2. Fit the target plus its outline inside the camera viewport. Leave about 1%\n   of the visible width on each side and at least one terminal row above and\n   below the meaningful content. If that does not fit, reduce `camera.scale`.\n3. Put outline edges in actual blank rows. Mathematical padding can move a\n   border onto a neighboring date heading, status row, or metadata line.\n4. Check both sides of the capture. A zoom that preserves left-aligned subjects\n   can still crop hashes, authors, or status text aligned to the right.\n5. Give irregular states explicit rectangles. A growing commit range that\n   crosses day separators should use measured per-keyframe heights instead of\n   assuming every added item occupies the same vertical distance.\n6. Share `cameraKey` or `highlightKey` only when captures use the same source\n   geometry. This allows pans and outline morphs across related captures;\n   unrelated keyframes cut or fade separately.\n\nThe outline should direct attention, not become the subject. Use one callout at\na time, keep labels short, and remove the outline once the viewer has enough\ncontext to follow the interaction unaided.\n\n## Command and output legibility\n\nTerminal commands and their results must be readable at normal playback size.\nNever rely on a full-width terminal prompt or output region as the only way the\nviewer can understand a command scene; text that looks acceptable in a 1080p\nsource frame is often illegible in an embedded player or social feed.\n\nUse this default treatment:\n\n1. **Present the exact command in large type.** The easiest treatment is a\n   `cmd` card or an oversized editorial callout immediately before the real\n   terminal result. A typing animation may remain for motion, but it does not\n   replace the large command treatment.\n2. **Zoom the real output.** Crop or scale the captured terminal frame around\n   the meaningful result so both the command and the important output lines\n   are comfortably readable. A brief full-window establishing shot is fine,\n   but the result's main hold must use the focused view.\n3. **Keep the evidence real.** Never fabricate terminal output. Large command\n   text may reproduce the exact command as an editorial overlay; any enlarged\n   output must come from the captured PTY frame. If reusable crop/zoom controls\n   are missing, add them to `@hunk/term-video` rather than baking one-off image\n   edits into a storyboard.\n4. **Trim before shrinking.** Prefer fewer relevant lines and a tighter crop\n   over fitting a long transcript into the frame. Split a workflow across\n   multiple focused shots when one crop cannot keep every important line\n   legible.\n\nTreat command legibility as a release gate: if the command or its result cannot\nbe read when the 1920x1080 video is displayed at 50% size, revise the shot.\n\n## Content accuracy (learned the hard way)\n\n- **Verify install commands against reality**, not the README: check\n  `npm view hunkdiff dist-tags`. A prerelease needs `npm i -g hunkdiff@beta`;\n  `brew install hunk` only serves stable (homebrew-core Autobump, lags npm) —\n  omit brew on prerelease cards.\n- **Label demo extensions as examples.** The triage board, CSS palette, and\n  semver views are `examples/extensions/`, not shipped features — caption them\n  with a dimmed `example:` prefix. The real features are the APIs (sidebars,\n  file views, commands, dialogs).\n- Window titles are decorative but must not lie: the shell scenes run bash,\n  so keep their titles generic (`shell — …`) rather than naming a shell the\n  capture doesn't launch.\n- STML requires `--experimental`; say so on the outro card.\n- The video is silent — never imply audio in the video or its announcement copy.\n\n## Verification and delivery\n\n- Eyeball keyframes in `.video-work/frames/` (Read renders PNGs) after\n  capture — especially new scenes — before compositing.\n- Inspect every command scene at 50% display size. Confirm the exact command is\n  shown in large type and the meaningful real output is zoomed tightly enough\n  to read; a readable caption does not compensate for unreadable terminal text.\n- Composite and encode the MP4 first. Inspect it before spending time on the\n  slower VP9/WebM encode; produce both final formats only after the visual pass.\n- After encoding, return to the repository root, set `VIDEO` to the produced\n  MP4, and inspect a settled frame for each callout, a mid-animation point,\n  each new scene, and the outro:\n\n  ```sh\n  VIDEO=.video-work/hunk-feature-demo.mp4 # or .video-work/launch.mp4\n  ffmpeg -y -ss 2 -i \"$VIDEO\" -frames:v 1 -update 1 .video-work/check.png\n  ffprobe -v error -show_entries format=duration,size \\\n    -show_entries stream=codec_name,width,height,r_frame_rate \\\n    -of default=noprint_wrappers=1 \"$VIDEO\"\n  ```\n\n  Captions must persist through extracted animation frames. Every callout must\n  show all four borders, keep its label clear of terminal text, leave visible\n  padding around its subject, and avoid clipping right-aligned metadata.\n\n- Compare the storyboard planner's reported duration with `ffprobe`; they\n  should agree within one frame. The concat manifest declares the PNG input\n  rate so 30 fps animation timestamps stay exact. Investigate larger drift\n  before delivery.\n\n- Outputs stay under `.video-work/`: the full-release recipe creates\n  `launch.mp4`/`launch.webm`, while the single-feature recipe above creates\n  `hunk-feature-demo.mp4`/`hunk-feature-demo.webm`. `.video-work/` is\n  gitignored — never commit the video or its frames. For a standalone video\n  request, send both files to the user directly (mp4: social/Slack; webm: web\n  embeds), report duration and file sizes, and flag if the mp4 exceeds ~10 MB\n  (Slack) or ~15 MB (X). When invoked by `hunk-release`, hand both files back\n  to that workflow instead; it owns versioned filenames, GitHub's\n  user-attachment limit, public embedding, and inline-player verification.\n  Copy them elsewhere only if the user names a destination.\n","frontmatter":{"name":"hunk-launch-video","description":"Produces Hunk videos by driving the real TUI headlessly in a PTY, compositing captioned 1080p frames in Chromium, and encoding with ffmpeg. Use for feature demos, workflow explainers, announcements, launch videos, and full-release roundups.","metadata":{"internal":true}},"isInternal":true,"tokens":6552,"sizeBytes":26378},{"name":"SKILL.md","path":"skills/hunk-release/SKILL.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/skills/hunk-release/SKILL.md","title":"hunk-release","category":"anthropic-skill","format":"markdown","content":"---\nname: hunk-release\ndescription: Prepares, publishes, verifies, and curates Hunk releases. Use for release metadata, benchmarks, tags, publishing, release videos, backports, or recovery.\nmetadata:\n  internal: true\n---\n\n# Hunk release workflow\n\nMaintainer-focused and source-checkout only. The tag workflow publishes `hunkdiff` plus five platform packages, attests the binary archives, and creates the GitHub release.\n\n## Safety\n\n- Ask when the version, release branch, previous tag, or channel is ambiguous.\n- Get explicit confirmation before pushing a tag, triggering publication, or editing a public release.\n- Never reuse an npm version or move a tag after publication.\n- Never bypass a benchmark regression without an approved, recorded reason.\n- Never retry a partial publish before inventorying every package and artifact.\n\n## 1. Confirm the release\n\nRecord the version, tag, branch, previous tag, and expected channel:\n\n- newer stable: npm `latest` and GitHub Latest;\n- prerelease: npm `beta`, not GitHub Latest;\n- older-series backport: npm `backport-X.Y`, leaving both latest pointers unchanged.\n\nFor backports, include only commits present between the previous tag and the release tip on that maintenance branch. Inspect `.changeset/pre.json` before changing prerelease state.\n\n## 2. Prepare\n\nStart from a clean, current release branch:\n\n```sh\ngit status --short --branch\ngit fetch origin --tags --prune\ngit tag --sort=-version:refname | head -10\nbun install --frozen-lockfile\nbun run changeset:status\n```\n\nRead the pending Changesets, then generate metadata and the published release notes:\n\n```sh\nbun run release:version\nbun run generate:changelog\ngit diff -- package.json bun.lock packages CHANGELOG.md .changeset website\n```\n\nVerify the intended versions, consumed Changesets, and new changelog section. Do not force a bump by hand-editing generated versions.\n\n`generate:changelog` projects `CHANGELOG.md` into `hunk.dev/changelog`. Never hand-edit its output under `website/src/content/docs/changelog/`, `website/public/changelog/`, `website/releases/dates.json`, or `website/releases/latest.json`. For a minor or major release, add the hand-authored parts to `website/releases/notes.json` under the `major.minor` key and regenerate:\n\n- `summary` — one sentence; replaces the changelog's `### Highlights` lead paragraph on the page.\n- `tagline` — short phrase for the landing-page release ribbon.\n- `links` — docs pages the highlights describe, rendered as \"Related documentation\".\n- `video` — `mp4` plus optional `webm`, `poster`, `duration`, and `title`, filled in at step 5.\n\nThe release tag does not exist yet at this point. A stable version is rendered as `Unreleased` without an install command, while an undated prerelease remains off the site entirely; the tag date is picked up by the next generation. Recorded dates in `website/releases/dates.json` are never recomputed, which is what lets `check:changelog` gate CI without Git tags.\n\nGenerate and compare the committed release benchmark:\n\n```sh\nbun run bench:release\nbun run bench:release:compare\n```\n\nA material regression blocks the release unless the user approves an `acceptedRegressions` entry following `benchmarks/release/README.md`.\n\nRun the validation required by `AGENTS.md`, plus the release packaging checks:\n\n```sh\nbun run check:docs\nbun run check:changelog\nbun run check:pack\nbun run build:prebuilt:npm\nbun run check:prebuilt-pack\nbun run smoke:prebuilt-install\n```\n\nCommit the generated metadata and `benchmarks/release/bench-X.Y.Z.json`, then follow normal review\npolicy. The Firecracker evidence must come from that reviewed release tip, not the pre-generation\ncommit. Push the reviewed tip before using the manual workflow.\n\nRun the full Firecracker install compatibility suite once from the clean reviewed release tip on a\nLinux x64 host with working KVM, either locally or through the manually dispatched\n`install-vm.yml` workflow:\n\n```sh\nset -euo pipefail\nmkdir -p tmp/install-vm/runs\nresult_dir=$(mktemp -d tmp/install-vm/runs/release-XXXXXXXX)\nbun run test:install-vm -- --output \"$result_dir\"\nbun run ./test/cli/install-vm/validate-release-result.ts \"$result_dir/result.json\"\n```\n\nThe explicit output directory prevents a failed invocation from falling back to stale evidence. The\nvalidator requires the complete checked-in scenario manifest, passing statuses, and the current\ncheckout's source identity. A skipped result does not satisfy release validation. For the manual\nworkflow, dispatch the full suite from the reviewed tip, download its `result.json` beneath the\nignored `tmp/install-vm/` directory of a checkout at that exact tip, and run the validator there; a\ngreen job alone is insufficient because unsupported runners may use the intentional skip path.\nFirecracker validates Linux x64 packaging behavior, while the existing native release jobs remain\nresponsible for macOS, Windows, and other architectures. Wait for required CI before continuing.\n\n## 3. Tag and publish\n\nImmediately before tagging, fail unless the reviewed branch tip is clean and matches its remote:\n\n```sh\nset -euo pipefail\nbranch=$(git branch --show-current)\ntest -n \"$branch\"\ngit fetch origin \"$branch\" --tags\ntest \"$(git rev-parse HEAD)\" = \"$(git rev-parse \"origin/$branch\")\"\nif [ -n \"$(git status --porcelain --untracked-files=all)\" ]; then\n  git status --short\n  exit 1\nfi\ngit log -1 --format='%H %s'\n```\n\nPresent the tag, commit, benchmark result, validation, and release highlights. After explicit confirmation:\n\n```sh\nversion=X.Y.Z\ntag=\"v$version\"\ngit tag -a \"$tag\" -m \"$tag\"\ngit push origin \"$tag\"\n```\n\nThe tag push starts `.github/workflows/release-prebuilt-npm.yml`. Find only the run for that tagged commit and propagate failure:\n\n```sh\nrelease_sha=$(git rev-parse \"$tag^{}\")\nrun_id=$(\n  gh run list \\\n    --workflow release-prebuilt-npm.yml \\\n    --event push \\\n    --commit \"$release_sha\" \\\n    --limit 1 \\\n    --json databaseId \\\n    --jq '.[0].databaseId // empty'\n)\ntest -n \"$run_id\"\ngh run view \"$run_id\" --json url,status,conclusion\ngh run watch \"$run_id\" --exit-status\n```\n\nIf lookup is empty, repeat it after GitHub registers the run; never select an unrelated run. Success requires the benchmark, all builds, staging smoke test, npm publish, attestation, and GitHub release jobs.\n\n## 4. Verify publication\n\nCheck the meta-package, platform packages, dist-tags, release body, and five binary archives:\n\n```sh\nnpm view \"hunkdiff@$version\" version\nfor package in \\\n  hunkdiff-darwin-arm64 hunkdiff-darwin-x64 \\\n  hunkdiff-linux-arm64 hunkdiff-linux-x64 \\\n  hunkdiff-windows-x64; do\n  npm view \"$package@$version\" version\ndone\nnpm view hunkdiff dist-tags --json\ngh release view \"$tag\" --json tagName,name,isPrerelease,url,assets,body\n```\n\nStop if versions, channel, tag, archives, or attestations disagree.\n\nThen record the tag date and publish the release notes. The published page is what the update notice and the GitHub release body point at, so it is part of the release, not follow-up work:\n\n```sh\nbun run generate:changelog\nbun run generate:og\ngit diff --stat -- website\n```\n\nThis backfills the new tag's date, publishes its notes, and redraws the social cards whose contents changed. Stable releases also add the install command and move the landing-page ribbon; prereleases publish a clearly labeled series without advancing either stable surface. Commit it to `main` — the diff should only touch `website/releases/`, `website/src/content/docs/changelog/`, `website/public/changelog/rss.xml`, and the redrawn cards under `website/public/changelog/og/`. Run this for backports too: they publish into their older series without advancing the latest stable release.\n\nPrereleases publish to `hunk.dev/changelog` after their tag exists. A beta contributes its series page, exact version anchor, index row, feed item, and social card, while the stable latest marker, landing ribbon, and default install instructions continue to name the newest stable release.\n\n## 5. Add the release video and final notes\n\nOnly after publication verifies, create a detached worktree at the released tag and follow `skills/hunk-launch-video/SKILL.md`'s full-release recipe:\n\n```sh\ngit worktree add --detach \"../hunk-release-video-$version\" \"$tag\"\n```\n\nThat skill owns capture, encoding, and media checks; release videos use its promo style kit, with this release's content in a new `scripts/launch-video/promo/storyboards/<version>.mjs`. Keep generated media out of Git and Git LFS. Commit the release's storyboard file and any capture scenes it needs only with separate approval, so the next release can start from them.\n\nPublish the video so the release page can carry it, then record it in `website/releases/notes.json` under the series' `video` key and regenerate. Host it where the site can serve it rather than committing it: the 1080p master stays out of Git and Git LFS.\n\nDraft the final body from the released changelog and actual branch diff. Replace GitHub's generated PR inventory with editorial notes that help someone decide whether to install the release:\n\n- Open with one short paragraph describing the release's product theme and user impact.\n- Group a small number of meaningful changes under descriptive headings. Explain what users can now do; do not restate commit titles or reproduce the changelog.\n- Include both upgrade and npm installation instructions using the exact release: `hunk update <version>` for npm- and curl-managed installs and `npm install -g hunkdiff@<version>` for npm installs or first-time npm users. Homebrew cannot select an exact version: tell its users to run plain `hunk update` only after the Homebrew readiness gate below passes, or label Homebrew propagation as pending.\n- Add a clearly labeled compatibility section for runtime requirements, changed CLI interpretation, extension API variants, migrations, or other upgrade risks.\n- Add a **Community contributors** section that names every external contributor in the release, links each relevant PR, and briefly describes their contribution. Derive this from the actual release diff; do not limit acknowledgment to first-time contributors and do not bury contributors in an autogenerated list.\n- Preserve a complete PR inventory inside a collapsed GitHub `<details>` block after the editorial sections. Use one concise bullet per merged PR with its author and link, include maintenance work there, and verify the list against the actual previous-tag comparison. The collapsed inventory is for completeness; it does not replace the curated highlights or contributor acknowledgments.\n- Link the comparison from the actual previous tag and point every release at its `hunk.dev` series page, which carries the full notes, video, and docs links.\n- Keep maintenance-only PRs out of the editorial highlights unless they materially affect installation, compatibility, security, or performance.\n\nUse this shape rather than a flat `What's Changed` list:\n\n````md\n## <release name or product theme>\n\n<one short release summary>\n\n```sh\nhunk update <version>\nnpm install -g hunkdiff@<version>\n```\n\nhttps://github.com/user-attachments/assets/<video-id>\n\n### <user-facing theme>\n\n<what changed, why it matters, and links to the defining PRs>\n\n### Compatibility notes\n\n- <upgrade requirement or changed behavior>\n\n### Community contributors\n\n- [@contributor](https://github.com/contributor) <concise contribution>. [#123](PR URL)\n\n<details>\n<summary>All merged pull requests</summary>\n\n- <concise PR title> by @author in [#123](PR URL)\n\n</details>\n\n**Release notes**: https://hunk.dev/changelog/<major.minor>/\n**Full changelog**: https://github.com/modem-dev/hunk/compare/<previous-tag>...<new-tag>\n````\n\nPresent the local MP4 and draft notes for explicit confirmation. Then attach the H.264 MP4 in GitHub's release editor, replace the placeholder with its generated user-attachment URL, and apply the reviewed body:\n\n```sh\ngh release edit \"$tag\" --notes-file /tmp/hunk-release-notes.md\n```\n\nOpen the public release in a browser and verify inline playback, final notes, and the unchanged five immutable binary archives.\n\n## 6. Distribution channels\n\nOnly stable releases that advance `latest` should propagate to Homebrew and mise. Let Homebrew Autobump update `Homebrew/homebrew-core`; use `brew bump-formula-pr` only if maintainers request it or Autobump stalls.\n\nTreat Homebrew availability as an announcement check only. Never delay or fail the tag, npm publication, GitHub release, changelog, release notes, video, or other release completion work because Homebrew is pending. Homebrew detects the published release on its own schedule and merges a separate formula PR. Immediately before an external promotional announcement that names Homebrew or recommends `hunk update` without qualifying the install method, read the live formula API and require its stable version to match:\n\n```sh\nversion=X.Y.Z\nformula_version=$(\n  curl -fsSL https://formulae.brew.sh/api/formula/hunk.json |\n    bun -e 'console.log(JSON.parse(await Bun.stdin.text()).versions.stable)'\n)\nprintf 'Homebrew formula: %s (release: %s)\\n' \"$formula_version\" \"$version\"\ntest \"$formula_version\" = \"$version\"\n```\n\nIf the check fails, still mark the software release complete, but hold the external promotional announcement unless it explicitly says that Homebrew is still propagating and offers npm or the install script meanwhile. Check for an open or merged Autobump PR:\n\n```sh\ngh api --method GET /search/issues \\\n  -f q=\"repo:Homebrew/homebrew-core is:pr in:title \\\"hunk $version\\\"\" \\\n  --jq '.items[] | [.html_url, .state, .title] | @tsv'\n```\n\nDetermine the next expected Autobump scan from Homebrew's live workflow rather than relying on a remembered cadence. This command prints the current cron expression and, while Homebrew retains its present three-hour schedule, the next nominal UTC scan. Scheduled GitHub Actions can start late, so report this as an estimate rather than an availability promise:\n\n```sh\nautobump_workflow=$(\n  gh api repos/Homebrew/homebrew-core/contents/.github/workflows/autobump.yml \\\n    --jq .content | base64 --decode\n)\nautobump_cron=$(\n  printf '%s\\n' \"$autobump_workflow\" |\n    awk -F'\"' '/^[[:space:]]*- cron:/ { print $2; exit }'\n)\nprintf 'Homebrew Autobump schedule: %s\\n' \"$autobump_cron\"\nAUTOBUMP_CRON=\"$autobump_cron\" bun -e '\n  const cron = process.env.AUTOBUMP_CRON;\n  if (cron !== \"45 1-23/3 * * *\") {\n    console.error(\"Homebrew changed its Autobump schedule; inspect the workflow before announcing.\");\n    process.exit(1);\n  }\n  const next = new Date();\n  next.setUTCSeconds(0, 0);\n  do next.setUTCMinutes(next.getUTCMinutes() + 1);\n  while (next.getUTCMinutes() !== 45 || (next.getUTCHours() - 1) % 3 !== 0);\n  console.log(`Next nominal Homebrew Autobump scan: ${next.toISOString()}`);\n'\n```\n\nAfter the formula version matches, run `brew update`, verify `brew info hunk` reports the release, and only then describe Homebrew as available. Verify mise independently against fresh registry data:\n\n```sh\nMISE_AQUA_BAKED_REGISTRY=false mise latest hunk\n```\n\nUse `mise cache clear` only when cached registry data is stale. Do not claim Homebrew or mise support for prereleases or older-series backports.\n\n## Failure invariants\n\n- Before tag push: fix, regenerate, validate, and request confirmation again.\n- After any npm publication: keep the version and tag; inventory all six packages before recovery.\n- Keep a verified software release intact when video or notes fail; retry only the approved media/edit step.\n","frontmatter":{"name":"hunk-release","description":"Prepares, publishes, verifies, and curates Hunk releases. Use for release metadata, benchmarks, tags, publishing, release videos, backports, or recovery.","metadata":{"internal":true}},"isInternal":true,"tokens":3556,"sizeBytes":15512},{"name":"agent-context-and-stml.md","path":"website/src/content/docs/docs/agents/agent-context-and-stml.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/website/src/content/docs/docs/agents/agent-context-and-stml.md","title":"Subagent: agent-context-and-stml","category":"subagent-persona","format":"markdown","content":"---\ntitle: Agent context and STML\ndescription: Load review notes from a JSON sidecar and opt into experimental rich note markup.\n---\n\n[Live agent comments](/docs/agents/comments-and-annotations/) are the recommended workflow. A sidecar file is useful when the notes already exist before Hunk starts, or need to travel with a patch.\n\n## Load a JSON sidecar\n\n```bash\nhunk diff --agent-context notes.json\nhunk patch change.patch --agent-context notes.json\n```\n\nUse `--agent-context -` to read the sidecar from stdin. With `--watch`, Hunk also reloads when the sidecar file changes, so an agent can rewrite the file and the open review updates; a stdin sidecar cannot be watched.\n\nSidecar annotations are agent notes, so they start hidden. Press `a` to show them, or launch with `--agent-notes`.\n\n## Sidecar format\n\n```json\n{\n  \"version\": 1,\n  \"files\": [\n    {\n      \"path\": \"src/search.ts\",\n      \"summary\": \"Scores and sorts matches instead of returning the first substring hit.\",\n      \"annotations\": [\n        {\n          \"newRange\": [15, 35],\n          \"summary\": \"Prefix and exact matches now outrank weaker substring hits.\",\n          \"rationale\": \"The old behavior made every match look equally good.\",\n          \"author\": \"sonnet\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nTop level:\n\n| Field     | Type   | Notes                                                                            |\n| --------- | ------ | -------------------------------------------------------------------------------- |\n| `version` | number | Optional; defaults to `1`.                                                       |\n| `summary` | string | Optional changeset summary. Extensions can read it; the TUI does not display it. |\n| `files`   | array  | One entry per file you want to describe.                                         |\n\nEach `files[]` entry:\n\n| Field         | Type   | Notes                                                                         |\n| ------------- | ------ | ----------------------------------------------------------------------------- |\n| `path`        | string | Required. Matches the file's current path, or its previous path for a rename. |\n| `summary`     | string | Optional. Used by the file filter, not shown as a card.                       |\n| `annotations` | array  | Optional notes for this file.                                                 |\n\nEach `annotations[]` entry:\n\n| Field                                             | Type           | Notes                                                                                                                  |\n| ------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- |\n| `summary`                                         | string         | Required. The note's headline.                                                                                         |\n| `newRange` / `oldRange`                           | `[start, end]` | Optional 1-based, inclusive line range on the new or old side. A note with no range attaches to the file's first hunk. |\n| `rationale`                                       | string         | Optional longer explanation shown under the summary.                                                                   |\n| `author`                                          | string         | Optional label for the card, such as `sonnet note`.                                                                    |\n| `markup`                                          | string         | Optional [STML](#opt-into-stml) body. Ignored unless the session launched with `--experimental`.                       |\n| `id`, `tags`, `confidence`, `source`, `createdAt` | various        | Optional metadata kept with the note. `confidence` is `low`, `medium`, or `high`.                                      |\n\nFiles listed in the sidecar appear first in the review, in sidecar order; other changed files follow in their usual order. Hunk rejects a sidecar with a missing `summary` or `path`, or a range whose end is before its start.\n\nKeep it concise: short file summaries and only rationale that improves the review. A fuller example lives at [`examples/3-agent-review-demo/agent-context.json`](https://github.com/modem-dev/hunk/blob/main/examples/3-agent-review-demo/agent-context.json).\n\n## Opt into STML\n\nSTML is experimental markup for richer note bodies: boxes, rows, badges, lists, and code. It is off by default and only the launch flag turns it on:\n\n```bash\nhunk --experimental diff --agent-context notes.json\n```\n\nA reload cannot turn STML on later, and there is no config key for it. Every note still needs a plain `summary`, which is the fallback whenever markup is unavailable. Without `--experimental`, a sidecar's `markup` is ignored and a live `--markup` comment is rejected.\n\nBefore sending markup, an agent should check support and width:\n\n```bash\nhunk session context --repo . --json\nhunk markup guide\nhunk markup render - --width <reported-noteMarkupWidth>\n```\n\nOnly send `--markup` when `experimentalFeatures` includes `stml`. `hunk markup guide` prints the tag reference and copy-paste patterns. Keep colors symbolic and markup compact so notes stay close to their code.\n","frontmatter":{"title":"Agent context and STML","description":"Load review notes from a JSON sidecar and opt into experimental rich note markup."},"isInternal":false,"tokens":1009,"sizeBytes":5322},{"name":"comments-and-annotations.md","path":"website/src/content/docs/docs/agents/comments-and-annotations.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/website/src/content/docs/docs/agents/comments-and-annotations.md","title":"Subagent: comments-and-annotations","category":"subagent-persona","format":"markdown","content":"---\ntitle: Comments and annotations\ndescription: Attach human or agent review notes to the code and navigate them in context.\n---\n\nNotes are hunk-specific and render beside the rows they explain. Hunk keeps them in the review flow rather than in a separate comments screen.\n\n## Kinds of notes\n\n| Term                   | What it is                                                                                           |\n| ---------------------- | ---------------------------------------------------------------------------------------------------- |\n| **Note**               | Any card shown beside the code.                                                                      |\n| **Agent comment**      | A note an agent adds to a live window with `hunk session comment`. This is the recommended path.     |\n| **Sidecar annotation** | A note loaded at launch from an [`--agent-context` JSON file](/docs/agents/agent-context-and-stml/). |\n| **Your note**          | A note you write in Hunk with `c`.                                                                   |\n| **Highlight**          | A temporary mark on a character range. It draws attention but is not a note.                         |\n\nAgent comments and sidecar annotations are both **agent notes**. They start hidden: press `a` to show or hide them, or launch with `--agent-notes`. Your own notes are always visible.\n\nNotes live in the Hunk window. Closing the window discards them.\n\n## Add one agent comment\n\n```bash\nhunk session comment add \\\n  --repo . \\\n  --file README.md \\\n  --new-line 103 \\\n  --summary \"Tighten this wording\" \\\n  --focus\n```\n\nChoose exactly one `--old-line` or `--new-line` target. `--focus` moves the user's viewport to the new note and turns agent notes on; use it for the first note in a session and when a note should actively steer the review.\n\nOptional fields:\n\n- `--rationale <text>`: a longer explanation shown under the summary.\n- `--author <name>`: the label on the note card, such as `Claude note` instead of `Agent note`.\n- `--markup <stml>`: an experimental rich body; see [Agent context and STML](/docs/agents/agent-context-and-stml/#opt-into-stml).\n\n## Apply a batch\n\n```bash\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' \\\n  | hunk session comment apply --repo . --stdin --focus\n```\n\nEach item needs a `summary` and either:\n\n- `filePath` with exactly one target: `hunk`, `hunkNumber`, `oldLine`, or `newLine`, or\n- `replyTo` by itself, to reply to an existing note.\n\nItems also accept `rationale`, `author`, and `markup`. Hunk validates the complete batch before changing the live session. `--focus` jumps to the first note in the batch.\n\n## Reply to a note\n\nA reply inherits the parent note's file and line:\n\n```bash\nhunk session comment add --repo . --reply-to user:123 --summary \"Addressed in the latest revision\"\n```\n\n## Hand your notes to the agent\n\nYour notes are a way to ask the agent questions in context:\n\n1. Press `c` on a line and write the question.\n2. Ask your agent to read your notes. It runs `hunk session comment list --repo . --type user --json` or `hunk session review --repo . --include-notes --json`.\n3. The agent answers with `--reply-to <note-id>`, and the reply appears under your note.\n\nAgents can remove your notes during cleanup but cannot create or edit them.\n\n## Inspect and clean up\n\n```bash\nhunk session comment list --repo .\nhunk session comment list --repo . --type all\nhunk session comment rm --repo . <comment-id>\nhunk session comment clear --repo . --file README.md --yes\n```\n\n`comment list` shows agent comments by default. `--type` selects other notes:\n\n| `--type`         | Lists                                         |\n| ---------------- | --------------------------------------------- |\n| `live` (default) | Comments added through `hunk session comment` |\n| `user`           | Your notes                                    |\n| `ai`             | Sidecar annotations                           |\n| `agent`          | Agent-authored notes                          |\n| `all`            | Every note                                    |\n\n`comment clear` removes agent comments. Add `--all` (or its synonym `--include-user`) to clear your notes too. Clearing always requires `--yes`.\n\n## Add your own note\n\nIn the TUI, move to a line and press `c`, or click an add-note affordance. Press `Ctrl+S` to save or Escape to cancel. Human and agent notes are labeled by source. Press `{` and `}` to move through annotated hunks across the review stream.\n","frontmatter":{"title":"Comments and annotations","description":"Attach human or agent review notes to the code and navigate them in context."},"isInternal":false,"tokens":1013,"sizeBytes":4553},{"name":"live-session-control.md","path":"website/src/content/docs/docs/agents/live-session-control.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/website/src/content/docs/docs/agents/live-session-control.md","title":"Subagent: live-session-control","category":"subagent-persona","format":"markdown","content":"---\ntitle: Live session control\ndescription: Inspect, target, navigate, and reload Hunk windows through the local session broker.\n---\n\nEach normal Hunk TUI registers with one loopback daemon. `hunk session ...` finds a registered window and sends it review actions.\n\n## Find the session\n\n```bash\nhunk session list\nhunk session get --repo .\nhunk session context --repo .\n```\n\nUse `--repo <path>` for normal worktrees. Use an explicit session ID when multiple windows share a repository.\n\n## Inspect without overloading context\n\n```bash\nhunk session review --repo . --json\n```\n\nThis returns files and hunks. Add flags only when required:\n\n```bash\nhunk session review --repo . --include-notes --json\nhunk session review --repo . --include-patch --json\n```\n\n## Navigate the visible window\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 . --next-comment\nhunk session navigate --repo . --comment <comment-id>\n```\n\nHunk numbers are 1-based. Absolute navigation needs a file and exactly one hunk, old-line, or new-line target. `--next-comment` and `--prev-comment` move between annotated hunks; `--comment` jumps to one note by the id that `comment list --json` returns.\n\n## Reload the review\n\nAlways place `--` before the nested Hunk command:\n\n```bash\nhunk session reload --repo . -- diff\nhunk session reload --repo . -- show HEAD~1 -- README.md\n```\n\nAdvanced reloads can target the live window by `--session-path` and load from a separate `--source` directory. Prefer `--repo` until those roles genuinely need to differ.\n\n## Security and access\n\nThe daemon listens on `127.0.0.1:47657` and refuses non-loopback addresses. Session commands authenticate with an owner-private credential that Hunk discovers automatically, so only your user account can control your windows; there are no credential flags to pass. The daemon starts with the first Hunk window and shuts down about a minute after the last one closes.\n\n| Variable                         | Effect                                                                                     |\n| -------------------------------- | ------------------------------------------------------------------------------------------ |\n| `HUNK_MCP_HOST`, `HUNK_MCP_PORT` | Move the daemon to another loopback host or port, for example to avoid a port conflict.    |\n| `HUNK_MCP_DISABLE=1`             | Start the TUI without registering a session.                                               |\n| `HUNK_MCP_UNSAFE_ALLOW_REMOTE=1` | Allow a non-loopback host. Anyone who can reach the port can attempt to connect; avoid it. |\n\nIf a visible Hunk window does not appear in `session list`, an agent sandbox may be blocking loopback access. Grant the agent local network access and retry; don't expose the daemon remotely. `hunk daemon serve` starts the daemon in the foreground for debugging.\n\n## After upgrading Hunk\n\nA daemon from the previous build keeps running while any older Hunk window is open. Until it is replaced:\n\n- a window from the new build shows a status-bar notice naming both builds, and\n- every `hunk session` command fails with a `daemon-build-mismatch` error (reported in-band under `--json`).\n\nTo fix it:\n\n1. Run `hunk daemon status` to see the daemon's build and its attached windows.\n2. Run `hunk daemon restart` and confirm. Windows from the new build reattach on their own.\n3. Relaunch any windows from the old build. Their notes are lost.\n\nAgents should report the mismatch and ask before restarting the daemon.\n","frontmatter":{"title":"Live session control","description":"Inspect, target, navigate, and reload Hunk windows through the local session broker."},"isInternal":false,"tokens":796,"sizeBytes":3583},{"name":"review-skill.md","path":"website/src/content/docs/docs/agents/review-skill.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/website/src/content/docs/docs/agents/review-skill.md","title":"Subagent: review-skill","category":"subagent-persona","format":"markdown","content":"---\ntitle: Hunk review skill\ndescription: Install the versioned skill that teaches coding agents Hunk's live review commands.\n---\n\nEvery Hunk installation bundles a `hunk-review` skill. It teaches agents how to find a live session, inspect the review efficiently, navigate, reload, and leave notes, and it tells them not to launch the interactive TUI themselves.\n\n## Install it\n\nInstall the skill with the `skills` CLI:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-review\n```\n\n`-g` installs the skill for your user instead of one project. Add `-a <agent>` (for example `-a claude-code`) to install it for a single agent.\n\nThe installed file is a pointer: it tells the agent to run `hunk skill show hunk-review` and follow the output. The instructions therefore come from the Hunk binary on your `PATH` and stay in step with it after every upgrade; you don't need to reinstall the skill.\n\n## Load it without the skills CLI\n\n| Agent setup                             | Command                                               |\n| --------------------------------------- | ----------------------------------------------------- |\n| Loads instructions on demand            | `hunk skill show` prints the skill to stdout.         |\n| Loads or symlinks skill files from disk | `hunk skill path` prints the bundled file's path.     |\n| Can only fetch URLs                     | Use the [published copy](/docs/hunk-review-skill.md). |\n\nThe published copy tracks the newest Hunk release, so prefer `hunk skill show` when your installed Hunk might be older.\n\n## Other bundled skills\n\n`hunk-extensions` teaches agents to build [extensions](/docs/extend/extensions/) against the `hunkdiff/extension` API:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-extensions\nhunk skill show hunk-extensions\n```\n\nFor the human side of the workflow, start with [Review with an agent](/docs/agents/review-with-an-agent/).\n","frontmatter":{"title":"Hunk review skill","description":"Install the versioned skill that teaches coding agents Hunk's live review commands."},"isInternal":false,"tokens":422,"sizeBytes":1909},{"name":"review-with-an-agent.md","path":"website/src/content/docs/docs/agents/review-with-an-agent.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/website/src/content/docs/docs/agents/review-with-an-agent.md","title":"Subagent: review-with-an-agent","category":"subagent-persona","format":"markdown","content":"---\ntitle: Review with an agent\ndescription: Let a coding agent inspect and guide a live Hunk review without giving up the human review UI.\n---\n\nThe Hunk window stays with you. Your agent uses non-interactive `hunk session` commands from another terminal to inspect the same review, navigate it, and leave inline notes.\n\n## Install the review skill\n\nThe `hunk-review` skill teaches your agent the session commands. Install it once with the `skills` CLI:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-review\n```\n\nThe installed skill is a small pointer that runs `hunk skill show`, so the instructions your agent reads always match the Hunk version on your `PATH`. Add `-a <agent>` (for example `-a claude-code`) to install it for a single agent.\n\nIf your agent has no skill system, ask it to run `hunk skill show` and follow the output. See [Hunk review skill](/docs/agents/review-skill/) for other ways to load it.\n\n## Start the review\n\n```bash\nhunk diff\n```\n\nKeep that window open. Normal Hunk sessions register with a local loopback daemon so the session CLI can find them.\n\n## Ask the agent to review\n\nIn your agent, ask for the review:\n\n```text\nUse the hunk-review skill to walk me through the changes in my open Hunk session.\n```\n\nThe skill tells agents not to launch the interactive TUI themselves. A typical agent flow is:\n\n```bash\nhunk session list\nhunk session get --repo .\nhunk session review --repo . --json\nhunk session navigate --repo . --file src/App.tsx --hunk 2\nhunk session comment add --repo . --file src/App.tsx --new-line 42 --summary \"Check this boundary\" --focus\n```\n\n`review --json` exposes structure without forcing the full patch into agent context. The agent should request `--include-patch` only when it actually needs raw unified diff text.\n\n## See the agent's notes\n\nAgent notes are hidden until you turn them on. Press `a` to show or hide them, or launch with `--agent-notes` (or set `agent_notes = true` in your [config](/docs/configure/configuration/)) to show them from the start. When the agent adds a note with `--focus`, Hunk jumps to it and turns agent notes on for you.\n\n![A Hunk review with agent rationale rendered directly beside the annotated diff hunk](/docs/images/agent-comments.webp)\n\nAgent notes stay attached to the code they explain. Press `{` and `}` to move between annotated hunks while keeping the full changeset visible.\n\n## Keep control\n\nThe agent can move the visible selection and add notes, but you stay in the review stream and can navigate normally. Ask it to summarize when finished, then walk the annotated hunks with `{` and `}`.\n\nTo hand questions back to the agent, leave your own notes with `c`; see [Comments and annotations](/docs/agents/comments-and-annotations/#hand-your-notes-to-the-agent).\n\n## Give the agent the docs\n\nThese docs are published as plain Markdown so an agent can read them without scraping HTML:\n\n- [/llms.txt](https://hunk.dev/llms.txt): an index of every page, for pulling only what is needed.\n- [/llms-small.txt](https://hunk.dev/llms-small.txt): a compact corpus for tight context budgets.\n- [/llms-full.txt](https://hunk.dev/llms-full.txt): every page and the full changelog in one file.\n\nAny docs page URL also returns its Markdown source with `.md` appended, so `https://hunk.dev/docs/reference/cli.md` is the CLI reference as the agent should read it.\n","frontmatter":{"title":"Review with an agent","description":"Let a coding agent inspect and guide a live Hunk review without giving up the human review UI."},"isInternal":false,"tokens":802,"sizeBytes":3357},{"name":"README.md","path":"examples/extensions/cli-tools/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/cli-tools/README.md","title":"cli-tools Documentation","category":"plugin-manifest","format":"markdown","content":"# CLI tools extension\n\nDemonstrates a generic top-level command tree. The handler owns every token below `cli-tools`, can stream headless output, receives cooperative cancellation, and can hand terminal ownership to one built-in Hunk command.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx --extension ./examples/extensions/cli-tools cli-tools status\nbun run packages/hunk/src/main.tsx --extension ./examples/extensions/cli-tools cli-tools review\n```\n\n`status` writes to stdout and exits. `review` performs signal-aware asynchronous preprocessing, writes progress to stderr, then delegates to `hunk diff`. A delegating handler must not write stdout or read stdin because the built-in command or TUI takes ownership of both.\n\nThe example parses its raw argument tokens directly. Extensions may use their own parser, make network requests, spawn processes, or access the filesystem under Hunk's ordinary extension trust model.\n","isInternal":false,"tokens":193,"sizeBytes":959},{"name":"README.md","path":"examples/extensions/code-document-file-view/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/code-document-file-view/README.md","title":"code-document-file-view Documentation","category":"plugin-manifest","format":"markdown","content":"# Code-document file view\n\nThis API-v28 example presents one old/new code pair per diff hunk. It declares the complete source documents so Hunk can tokenize with multiline lexical context, while each displayed span references either a complete old-side line or a partial new-side UTF-16 slice. Gutters and the unhighlighted prefix stay ordinary symbolic spans.\n\nHunk owns the language, active syntax theme, workers, caching, terminal safety, and plain fallback. The extension never supplies token colors.\n\nRun it from this checkout against two JavaScript or TypeScript files:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/code-document-file-view \\\n  path/to/before.ts path/to/after.ts\n```\n\nChoose **View → File presentation → Code documents: old / new**. The example intentionally omits `sourceRanges`: syntax references paint code but do not claim note/navigation ownership. A visible note therefore makes Hunk use the raw diff, as required by the all-or-raw note policy.\n","isInternal":false,"tokens":212,"sizeBytes":1021},{"name":"README.md","path":"examples/extensions/inline-edit/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/inline-edit/README.md","title":"inline-edit Documentation","category":"plugin-manifest","format":"markdown","content":"# Inline edit extension\n\nA miniature line editor for the file under review. Press `Ctrl-E`, type into the diff, press `Ctrl-S`, and Hunk writes the file back to your working tree after asking you first.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\nIt exists to demonstrate that Hunk's interactive extension surfaces compose, so it uses them all at once:\n\n| Capability                              | Where this extension uses it                                                                                                                                                                                                                                           |\n| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `ctx.workspace`                         | `canWriteDocument` gates the affordance, `readDocument(\"new\")` fills the buffer, `writeDocument` performs the consented write and reloads the review.                                                                                                                  |\n| `mode` on a registered file view        | `onKey` claims arrows, editable characters, Backspace, Enter, and `Ctrl-S`, while `]`, `?`, and `q` pass through so navigation, help, and quit keep working. `Ctrl-S` is recognized with `matchesKey`, so the bare control byte terminals send for it matches too.     |\n| `fileViews.enterMode(viewId)`           | One call makes the view the file's presentation _and_ gives its mode the keyboard, so `Ctrl-E` opens the editor in a single press.                                                                                                                                     |\n| `fileViews.refresh(viewId, { fileId })` | Every buffer or caret change re-derives the layout. A view's layout is a pure function of `(file, width)`, so this is the only way a stateful presentation redraws — and the buffer belongs to one file, so the refresh is scoped to it and no other file re-lays out. |\n\n## Try it from this checkout\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/inline-edit\n```\n\n## Install it globally\n\nCopy the whole folder — it has no dependencies:\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/inline-edit ~/.config/hunk/extensions/\n```\n\nHunk discovers the folder automatically on later launches. Open **View** and choose **File presentation: Inline edit**, or press `Ctrl-E`. The command is named `inline-edit.edit` for `[keybindings]` customization.\n\n## Keys\n\n| Key                           | Does                                                                          |\n| ----------------------------- | ----------------------------------------------------------------------------- |\n| `Ctrl-E`                      | Starts editing the selected file, showing the view if it was not already.     |\n| `↑` `↓` `←` `→`               | Move the caret. `←`/`→` wrap across line ends.                                |\n| any other printable character | Types at the caret — including characters bound to Hunk commands, like `z`.   |\n| `Backspace`                   | Deletes back one character, joining with the previous line at column 0.       |\n| `Enter`                       | Splits the line at the caret.                                                 |\n| `Ctrl-S`                      | Asks Hunk to write the buffer. Hunk confirms, writes, and reloads the review. |\n| `Esc`                         | Leaves the editor and **discards** everything typed since the last write.     |\n| everything else               | Still Hunk's: `]` moves to the next hunk, `?` opens help, `q` quits.          |\n\nThe header row reads `EDITING — Esc exits · ctrl+s writes`, plus a `MODIFIED` marker whenever the buffer differs from the text on disk.\n\n## How the pieces fit\n\n`Ctrl-E` is one press. The command gates on `canWriteDocument`, starts reading the document, and calls `fileViews.enterMode` before awaiting that read. Entry therefore uses the same selected file the command captured; a selection change cannot attach a late buffer to another file. `enterMode` makes the view that file's presentation and gives its mode the keyboard together, then the completed read builds the buffer and refreshes the view. When entry refuses (no `mode`, a file Hunk keeps on raw diff, a view that does not match), it warns by name and returns `false` without installing a buffer.\n\nThe editor slot is claimed synchronously, before that command's first `await`. Reading the document suspends the handler, so a guard that only checked \"is a session live?\" would let a second `Ctrl-E` through the window in between, and one of the two handlers would then be parked forever on a session nothing could end. The claim is released on every way out — an unwritable review, an unreadable document, a refused `enterMode` — and once a session is live it is the live session that answers the next press.\n\n`onKey` has to answer synchronously — its return value _is_ the routing decision — and the mode context carries only `file` and `fileViews`. So a keystroke can never write anything itself. What `Ctrl-S` does instead is post a request into the edit session, and the command handler that entered the mode is still awaiting that session: `ctx.workspace` is valid for the whole life of a command handler's promise, so the handler is the mode's async runtime.\n\nThat loop ends the same way from every direction, because `onExit` runs on every exit path:\n\n- **Saved.** A successful `writeDocument` reloads the session, the reload exits the mode, `onExit` ends the loop.\n- **Escaped.** Escape is host-owned, so it exits the mode without ever reaching `onKey`, and the loop ends the same way.\n- **Cancelled write.** `{ ok: false, reason: \"cancelled\" }` is the user answering, not a failure. The editor keeps running.\n- **Failed write.** The `detail` sentence is shown as a warning and the editor keeps running, so nothing typed is lost to a full disk.\n\n## Limitations\n\nThis is a demonstration, not an editor:\n\n- **No wrapping.** Lines are truncated at the pane width with `…`. A file-view layout must be deterministic for `(file, width)`, and wrapping would be a second layout problem on top of the one this example is about.\n- **Whole-line truncation.** The caret can move past the visible edge; the row does not scroll horizontally to follow it.\n- **One buffer, no undo.** Escape discards everything since the last write, with no confirmation beyond the notice it leaves behind.\n- **Writes are working-tree only**, which is a property of `ctx.workspace`: `hunk show`, `hunk patch`, a staged diff, and a file-pair diff have no working-tree document to replace, so the command refuses instead of opening an editor that could not save.\n- **Agent notes describe the document as it was loaded.** They stay on the lines they were written about, but nothing re-reads the changeset while you type.\n\n## Source bindings and provenance\n\nRows carry `sourceRanges` so Hunk can place its own inline notes inside this presentation, and a row may only bind a line it honestly still _is_. Row position cannot answer that once you split or join a line, so the edit session keeps **provenance**: for each buffer line, the document line it came from, or nothing at all.\n\n| Edit               | Provenance                                                               |\n| ------------------ | ------------------------------------------------------------------------ |\n| typing in a line   | kept — an edited line is still the line it came from                     |\n| `Enter` (split)    | the first line keeps it; the new tail gets none                          |\n| `Backspace` (join) | the merged line presents both source lines so either note stays attached |\n\nSo a line you inserted binds nothing, the lines below a split keep the numbers they had, and a joined row keeps every source line it now presents. The hunk extents in `hunkRows` are derived from the same provenance, so the hunk highlight follows the rows still holding a hunk's lines instead of drifting down by however many lines you added above them. A join across two distinct hunks is refused because one bound row cannot belong to two hunk extents.\n\nOne thing this shows that is easy to get wrong: Hunk accepts a binding only on a row exactly one `hunkRows` extent owns, and rejects the whole layout otherwise — so a last pass drops the bindings no extent owns, and context lines outside every hunk are presented without one. The [rendered Markdown example](../rendered-markdown/) ends its layout with the same pass, for the same reason.\n\n## Where this is documented\n\n- [`docs/extensions.md` → Interactive file views](../../../docs/extensions.md#interactive-file-views)\n- [`docs/extensions.md` → Reading and writing a reviewed file](../../../docs/extensions.md#reading-and-writing-a-reviewed-file)\n- [`docs/extensions.md` → `hunk.registerFileView(view)`](../../../docs/extensions.md#hunkregisterfileviewview-experimental)\n","isInternal":false,"tokens":1867,"sizeBytes":9313},{"name":"README.md","path":"examples/extensions/jsx-file-view-gallery/mixed-review/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/jsx-file-view-gallery/mixed-review/README.md","title":"mixed-review Documentation","category":"plugin-manifest","format":"markdown","content":"# Mixed preview review\n\nThis launches one real five-file Git working-tree review that is intentionally taller than a terminal viewport:\n\n- `README.md` — raw Markdown diff;\n- `package.json` — dependency version-segment highlights;\n- `scripts/deploy.py` — raw Python diff;\n- `src/invoice.ts` — responsive change-atlas cards;\n- `styles/theme.css` — exact-source color swatches.\n\nThe launcher creates a temporary repository, commits the checked-in `before` fixtures, copies the `after` fixtures into its working tree, and starts Hunk from this checkout. The repository is removed when Hunk exits.\n\nFrom the Hunk repository root:\n\n```bash\nbun run ./examples/extensions/jsx-file-view-gallery/mixed-review/run.ts\n```\n\nRaw diff is deliberately the default. To build the mixed stream:\n\n1. Click `package.json` in the sidebar and press **F8**.\n2. Click `src/invoice.ts` and press **F8**.\n3. Click `styles/theme.css` and press **F8**.\n4. Click `README.md` to return to the top, then scroll through the main pane.\n\nSelecting files only jumps the main review stream; it does not collapse other files. The three preview selections therefore remain active together, interleaved with the two raw Pierre diffs. Use `[` and `]` while scrolling to verify that hunk navigation crosses raw and custom sections using the same host-owned geometry.\n","isInternal":false,"tokens":301,"sizeBytes":1335},{"name":"README.md","path":"examples/extensions/jsx-file-view-gallery/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/jsx-file-view-gallery/README.md","title":"jsx-file-view-gallery Documentation","category":"plugin-manifest","format":"markdown","content":"# JSX file-view gallery\n\nThree opt-in presentations exercise the constrained React/OpenTUI row contract against checked-in, realistic file pairs. Run one command from the repository root, then press **F8** or choose **Extensions → Toggle JSX demo for current file**. Press F8 again to restore Pierre's raw diff.\n\n## 1. Change atlas\n\nNested boxes, responsive meters, semantic color, and selected-hunk styling summarize a multi-hunk TypeScript refactor. It uses no source parser and works from public hunk/change metadata alone.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/before.ts \\\n  examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/after.ts\n```\n\n## 2. CSS palette delta\n\nThe extension lazily reads both exact documents, associates changed opaque three- or six-digit hexadecimal custom properties with each real diff hunk, and paints old/new terminal color swatches inside deterministic two-row rectangles.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/css-palette/before.css \\\n  examples/extensions/jsx-file-view-gallery/fixtures/css-palette/after.css\n```\n\n## 3. Dependency delta\n\nA conservative package-file parser highlights only the changed semantic-version segment: patch-only changes emphasize the patch number, minor upgrades emphasize the minor number, and major upgrades emphasize the full old/new strings. It retains positional bounds for every parsed hunk; invalid JSON or unavailable source falls back to raw diff.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/before/package.json \\\n  examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/after/package.json\n```\n\n## Mixed five-file review\n\nTo see all three preview types retained together between ordinary raw diffs—and enough content to exercise stream scrolling—run:\n\n```bash\nbun run ./examples/extensions/jsx-file-view-gallery/mixed-review/run.ts\n```\n\nFollow the short activation sequence in [`mixed-review/README.md`](./mixed-review/README.md).\n\n## Contract illustrated\n\n- Every painter stays inside a declared fixed-height row; geometry, scrolling, windowing, and hunk navigation remain host-owned.\n- Every row keeps useful symbolic spans for row-local error fallback, clipped to the same fixed rectangle.\n- Semantic data is captured in closures during `layout`; painters receive only bounded paint props and use Hunk's live paint-only semantic theme palette.\n- The demos intentionally have no pointer handlers. Registered commands are the supported interaction path.\n- Layouts return `null` when exact source is unavailable or no supported semantic row can be attributed. Mixed diffs may retain neutral summary rows for non-semantic hunks so navigation stays positional.\n- Rows bind conservatively attributed exact-source ranges. Hunk renders a note inside the alternate view only when its preferred-side anchor resolves uniquely; otherwise the complete file falls back to raw diff.\n\nThis gallery is experimental and is not loaded unless you explicitly pass or install its folder. See [`docs/file-view-jsx-poc.md`](../../../docs/file-view-jsx-poc.md) for the full contract.\n","isInternal":false,"tokens":713,"sizeBytes":3538},{"name":"README.md","path":"examples/extensions/jsx-file-view/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/jsx-file-view/README.md","title":"jsx-file-view Documentation","category":"plugin-manifest","format":"markdown","content":"# JSX file-view POC extension\n\nAn opt-in proof of concept for fixed-height React/OpenTUI rows in alternate file presentations. It appears only for files with at least two parsed hunks and creates two custom rows per hunk, with stable row IDs and explicit hunk bounds.\n\nRun it from this checkout against a multi-hunk working-tree change:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/jsx-file-view\n```\n\nChoose **Extensions → Toggle JSX hunk cards (POC)**. The row component uses a React state hook and OpenTUI `box`/`text` elements. Its registered F8 command/menu item is the supported keyboard path. A cooperatively delivered, un-dragged left-button mouse-up toggles local detail and stops propagation; wheel, drag, and unhandled input remain host-owned. The row is a non-focusable paint surface, with no portal, renderer, focus, or input-delivery guarantee. Each component is a closure over the hunk summary; Hunk passes it only bounded paint props, including a live semantic theme palette that does not participate in layout. The `spans` on every row are the host-rendered fallback, clipped to the same declared fixed height if the component fails. Hook state survives selected-hunk updates while mounted, but is intentionally lost when windowing unmounts the row or a new layout generation replaces it.\n\nThis example is deliberately opt-in and experimental. See [`docs/file-view-jsx-poc.md`](../../../docs/file-view-jsx-poc.md) for constraints.\n","isInternal":false,"tokens":327,"sizeBytes":1490},{"name":"README.md","path":"examples/extensions/pane-layout/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/pane-layout/README.md","title":"pane-layout Documentation","category":"plugin-manifest","format":"markdown","content":"# Pane layout extension\n\nRegisters a resizable right pane and fixed two-row top and bottom panes.\n\nRun it from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/pane-layout\n```\n\nPress `ctrl+p` or use the **Extensions** menu. Drag the right divider to resize.\n\nCopy this directory to your Hunk extensions directory to install it.\n","isInternal":false,"tokens":85,"sizeBytes":380},{"name":"README.md","path":"examples/extensions/rendered-markdown/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/rendered-markdown/README.md","title":"rendered-markdown Documentation","category":"plugin-manifest","format":"markdown","content":"# Rendered Markdown extension\n\nAn optional Markdown preview for Hunk's experimental file-view API. It parses Markdown with [Marked](https://marked.js.org/) and presents headings, inline formatting, links, lists, quotes, tables, and fenced code as host-owned symbolic rows.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\n## Try it from this checkout\n\nThe repository's root install supplies the example's development dependency:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/rendered-markdown \\\n  before.md after.md\n```\n\n## Install it globally\n\nCopy the whole folder, then install its local dependency:\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/rendered-markdown ~/.config/hunk/extensions/\ncd ~/.config/hunk/extensions/rendered-markdown\nbun install\n```\n\nHunk discovers the folder automatically on later launches. Open **View** and choose **File presentation: Rendered Markdown**, or press `F8`. The command is named `rendered-markdown.toggle-rendered-markdown` for `[keybindings]` customization.\n\nRaw Pierre diff remains the default and fallback. The preview reads exact text through `input.readDocument`, returns hunk row bounds in source-hunk order, binds rendered blocks to exact new-side ranges, and retains hunk navigation and selection highlighting. Hunk inserts inline note cards at uniquely bound rows; if any visible note cannot be resolved, the complete file temporarily returns to raw rendering so review data is never hidden.\n\n## Why it returns rows instead of an OpenTUI Markdown component\n\nOpenTUI includes a capable `MarkdownRenderable`, but mounting it would make the extension own opaque layout geometry. Hunk needs exact rows before mount for review-stream measurement, windowing, scrolling, hunk navigation, and fallback. This example therefore uses the same Marked parser OpenTUI uses, then translates its tokens into the file-view contract's generic tones and text attributes.\n","isInternal":false,"tokens":419,"sizeBytes":2012},{"name":"README.md","path":"examples/extensions/review-note-navigator/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/review-note-navigator/README.md","title":"review-note-navigator Documentation","category":"plugin-manifest","format":"markdown","content":"# Review note navigator extension\n\nLists every note currently saved in Hunk's shared ReviewStore, then navigates to a selected visible note's authoritative source anchor. The example shows how `ctx.review.snapshot()` complements dialogs and live navigation: selection and note events alone cannot recover notes that already existed before the extension began listening.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-note-navigator\n```\n\nSave one or more review notes, then run **Extensions → Navigate saved review note…** (`F8`). Each choice includes its reconciliation status, file, preferred line, side, and summary.\n\n- Active notes reveal their current source line.\n- Stale notes reveal the last authoritative anchor Hunk retained.\n- Orphaned notes remain visible in the inventory but produce a warning because they have no current review location.\n- A note whose file is hidden by Hunk's current file filter remains in the inventory, but guarded navigation refuses the hidden target with a warning; clear the filter and retry.\n- Drafts and static sidecar annotations that never entered ReviewStore are intentionally absent.\n\nThe command captures the complete note inventory before opening its selector. After the user chooses, it reads the authoritative snapshot again and resolves the selected note by stable note id, so an edit made by another review surface cannot make it navigate using an obsolete anchor. A reload cancels the dialog and retires the command's review controls.\n","isInternal":false,"tokens":301,"sizeBytes":1573},{"name":"README.md","path":"examples/extensions/review-snapshot-export/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/review-snapshot-export/README.md","title":"review-snapshot-export Documentation","category":"plugin-manifest","format":"markdown","content":"# Review snapshot export extension\n\nExports Hunk's authoritative saved review state as JSON. The example shows why `ctx.review.snapshot()` is more useful than accumulating `note_created` events: one command receives every note currently retained by the shared ReviewStore, including stale and orphaned notes an exporter must handle explicitly.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-snapshot-export\n```\n\nAdd one or more review notes, then run **Extensions → Export review snapshot…** (`F9`) and choose a new output path. Relative paths resolve from the review's working directory; the example refuses to overwrite an existing file.\n\nThe JSON includes:\n\n- the opaque producer generation and ReviewStore revision\n- every reviewed file's stable `fileKey`, runtime navigation id, content identity, status, and path\n- all saved live and reviewer notes with their resolved old/new anchors and reconciliation status\n\nDraft notes and static sidecar annotations that never entered ReviewStore are intentionally absent. Saved notes appear in live-arrival order followed by reviewer-creation order.\n\nThe command captures a snapshot before opening its path dialog, then reads the controls again before writing. If either the generation or revision changed, it refuses the stale export and asks the user to rerun the command. Publishers can use the same check before an irreversible network request.\n\n## Trust\n\nThis example uses `node:fs` directly to write the user-selected export path. Hunk extensions run with the user's full permissions; this is distinct from `ctx.workspace`, which mediates writes to reviewed files and asks for consent. Install and run only extensions you trust.\n","isInternal":false,"tokens":341,"sizeBytes":1768},{"name":"README.md","path":"examples/extensions/review-triage/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/review-triage/README.md","title":"review-triage Documentation","category":"plugin-manifest","format":"markdown","content":"# Review triage extension\n\nA session-local hunk review board for Hunk. It records which hunks you have visited and lets you mark the selected hunk **approved**, **investigate**, or **blocked** with an optional rationale.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-triage\n```\n\nOr copy the directory to your Hunk extensions directory and keep its `package.json`; its manifest makes the folder a single `review-triage` extension.\n\n## Use\n\nOpen **Extensions → Toggle review triage** (`Y`). The right pane lists each visible file's hunks; click a hunk to navigate the review stream. Use **Extensions → Mark selected hunk…** (`x`) to choose a status and enter an optional rationale. **Center current review line**, **Set review focus…**, and **Clear triage decisions** are menu-only commands.\n\nThe board intentionally keeps state only for the running Hunk session. Reloading reconciles decisions against the newly parsed hunks and drops entries that no longer match, rather than silently transferring a decision to changed code.\n\n## API surface exercised\n\n- `registerPane` renders public file/hunk summaries and navigates with pane actions.\n- `registerCommand` supplies the Extensions-menu items and user-remappable defaults; `ctx.commands.execute` delegates the dedicated centering action to Hunk's public semantic command.\n- `dialogs.select`, `dialogs.input`, and `dialogs.confirm` implement the review decision and clear flows.\n- Lifecycle handlers track changeset loads, reloads, selection, viewed hunks, Hunk notes, filters, and pending watch reloads through a `useSyncExternalStore` bridge.\n- `hunk.events` publishes decisions and listens for `review-triage:open`, so another extension can reveal the board without importing its state.\n","isInternal":false,"tokens":389,"sizeBytes":1831},{"name":"README.md","path":"examples/extensions/vim-navigation/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/examples/extensions/vim-navigation/README.md","title":"vim-navigation Documentation","category":"plugin-manifest","format":"markdown","content":"# Vim navigation extension\n\nA small Vim-style normal mode for Hunk's whole review stream. It demonstrates session keyboard modes and public semantic command execution without accessing scroll boxes, renderer objects, or viewport coordinates.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\n## Try it from this checkout\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/vim-navigation\n```\n\nPress `F6` or choose **Extensions → Toggle Vim navigation**. The persistent status badge shows when the mode owns review-level keys; click the badge, choose the host-owned exit menu item, or press `Esc` to leave.\n\n## Install it globally\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/vim-navigation ~/.config/hunk/extensions/\n```\n\n## Keys\n\n| Key                 | Action                                                      |\n| ------------------- | ----------------------------------------------------------- |\n| `j` / `k`           | Move the current review line down/up                        |\n| `[` / `]`           | Move to the previous/next hunk                              |\n| `gg` / `G`          | Jump to the start/end of the review                         |\n| `zt` / `zz` / `zb`  | Align the current line at the top/center/bottom             |\n| `Ctrl-D` / `Ctrl-U` | Move down/up by half pages                                  |\n| positive digits     | Prefix the next relative motion, for example `5j` or `3]`   |\n| `:`                 | Open the host-rendered Vim command line                     |\n| `Esc`               | Exit the mode (host-owned; the extension never receives it) |\n| everything else     | Pass through to normal Hunk routing                         |\n\nCounts are parsed by the extension and capped at 10,000. Once a normal-mode sequence resolves, the extension calls `ctx.commands.execute(id, { count })` exactly once, so Hunk applies movement atomically. A bare `0` passes to Hunk's normal layout shortcut; `0` can extend a count that already began with `1`–`9`.\n\nPressing `:` passes the key to the example's registered command, which opens `ctx.prompts.line()` on the status row with a `:` prefix. That focused host input captures typed keys ahead of the still-active session mode until Enter submits, or Escape clears the buffer and a second Escape cancels. The deliberately small Ex-style command set is:\n\n| Command   | Action                          |\n| --------- | ------------------------------- |\n| `:top`    | Jump to the start of the review |\n| `:bottom` | Jump to the end of the review   |\n\nUnsupported commands produce an attributed warning. Absolute source-line commands such as Vim's `:100` are intentionally absent because Hunk does not expose source-line targeting as a public semantic command; relative counted movement such as `100j` remains available in normal mode.\n\nThe example enables Hunk's host-owned current-line marker on entry so the `z*` alignment commands have a target. It resets all pending prefix/count state on entry and exit. Invalid continuations clear pending state and pass the current key back to Hunk.\n","isInternal":false,"tokens":666,"sizeBytes":3172},{"name":"README.md","path":"packages/hunk-git/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/hunk-git/README.md","title":"hunk-git Documentation","category":"plugin-manifest","format":"markdown","content":"# @hunk/git\n\nPrivate, statically bundled Git provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-tree, revision, and stash\nreviews; history; exact old/new source reads; staged changes; untracked and large-file handling;\nand filesystem-assisted watch plans.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-git` and `bun run deps:check` after changes.\n","isInternal":false,"tokens":205,"sizeBytes":843},{"name":"README.md","path":"packages/hunk-jj/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/hunk-jj/README.md","title":"hunk-jj Documentation","category":"plugin-manifest","format":"markdown","content":"# @hunk/jj\n\nPrivate, statically bundled Jujutsu provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-copy and revision reviews,\nhistory, exact source reads where the revision shape permits them, and signature-based refresh.\nStaged and stash reviews are not supported.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-jj` and `bun run deps:check` after changes.\n","isInternal":false,"tokens":207,"sizeBytes":852},{"name":"README.md","path":"packages/hunk-sapling/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/hunk-sapling/README.md","title":"hunk-sapling Documentation","category":"plugin-manifest","format":"markdown","content":"# @hunk/sapling\n\nPrivate, statically bundled Sapling provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-copy and revision reviews,\nuntracked-file discovery, and signature-based refresh. History, staged and stash reviews, exact\nsource reads, and filesystem watch plans are not supported.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-sapling` and `bun run deps:check` after changes.\n","isInternal":false,"tokens":211,"sizeBytes":878},{"name":"README.md","path":"packages/hunk-vcs/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/hunk-vcs/README.md","title":"hunk-vcs Documentation","category":"plugin-manifest","format":"markdown","content":"# @hunk/vcs\n\nPrivate implementation helpers shared by Hunk's bundled VCS providers. This `0.0.0` workspace is\nnot a public or versioned SDK.\n\nThe package has no root export. Providers import only the leaf they need:\n\n- `@hunk/vcs/async-process`\n- `@hunk/vcs/diff-target`\n- `@hunk/vcs/large-file`\n- `@hunk/vcs/path`\n- `@hunk/vcs/source`\n\n`@hunk/vcs` sits at the bottom of the workspace graph and does not import other workspace packages.\nProvider implementations combine these helpers with the public `hunkdiff/extension` contract; Hunk\nregisters them in `packages/hunk/src/extensions/default/vcs/index.ts` and composes the catalog in\n`packages/hunk/src/app/vcsCatalog.ts`.\n\nKeep unit tests beside the source. Run `bun test packages/hunk-vcs` and `bun run deps:check` after\nchanging this package.\n","isInternal":false,"tokens":214,"sizeBytes":796},{"name":"README.md","path":"packages/hunk/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/hunk/README.md","title":"hunk Documentation","category":"plugin-manifest","format":"markdown","content":"# hunk\n\nHunk is an interactive terminal diff viewer for reviewing code changes, built on [OpenTUI](https://github.com/anomalyco/opentui) and [Pierre diffs](https://www.npmjs.com/package/@pierre/diffs).\n\n**[hunk.dev](https://hunk.dev)** · [Documentation](https://hunk.dev/docs/)\n\n[![CI status](https://img.shields.io/github/actions/workflow/status/modem-dev/hunk/ci.yml?branch=main&style=for-the-badge&label=CI)](https://github.com/modem-dev/hunk/actions/workflows/ci.yml?branch=main)\n[![Latest release](https://img.shields.io/github/v/release/modem-dev/hunk?style=for-the-badge)](https://github.com/modem-dev/hunk/releases)\n[![MIT License](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE)\n[![Join the Discord community](https://img.shields.io/badge/Discord-Join%20community-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/WZFjaP6Gt8)\n\n- multi-file review stream with sidebar navigation\n- inline AI and agent annotations beside the code\n- split, unified, and responsive auto layouts\n- watch mode for auto-reloading file and Git-backed reviews\n- keyboard, mouse, pager, and Git difftool support\n\n<table>\n <tr>\n   <td width=\"60%\" align=\"center\">\n     <img width=\"845\" alt=\"image\" src=\"https://raw.githubusercontent.com/modem-dev/hunk/refs/heads/main/website/public/docs/images/review-stream.webp\" />\n     <br />\n     <sub>Split view with sidebar and inline AI notes</sub>\n   </td>\n   <td width=\"40%\" align=\"center\">\n     <img width=\"507\"alt=\"image\" src=\"https://raw.githubusercontent.com/modem-dev/hunk/refs/heads/main/website/public/docs/images/agent-comments.webp\" />\n     <br />\n     <sub>Unified view and mouse-selectable menus</sub>\n   </td>\n </tr>\n</table>\n\n## Install\n\nOn macOS and Linux, install the standalone binary:\n\n```bash\ncurl -fsSL https://hunk.dev/install.sh | sh\n```\n\nWindows users can install with npm or mise. Other installation methods are also available:\n\n```bash\nnpm i -g hunkdiff                    # macOS, Linux, or Windows; requires Node.js 22+\nbrew install hunk                    # macOS or Linux\nmise use -g hunk                     # macOS, Linux, or Windows\n```\n\n> [!NOTE]\n> If you previously installed hunk via `modem-dev/tap`, be sure to uninstall it first with `brew uninstall modem-dev/tap/hunk`.\n\nWindows requires mise 2026.8.6 or newer. Nix users can use the `default` package exported in `flake.nix`; see [the Nix guide](https://github.com/modem-dev/hunk/blob/main/nix/README.md) for details. Hunk also ships as a default tool in [Omarchy](https://omarchy.org), installed through mise.\n\nRequirements:\n\n- macOS (arm64, x64), Linux (arm64, x64; glibc), or Windows (x64); prebuilt binaries are not published for Windows on arm64 or musl-based Linux such as Alpine\n- On x86-64, a CPU with SSE4.2 (Intel Nehalem 2008+, AMD Bulldozer 2011+); arm64 has no CPU feature floor\n- Node.js 22+ for the npm install; the install script, Homebrew, mise, and Nix ship a standalone binary that does not require Node.js\n- Git recommended for most workflows\n\n### Update Hunk\n\nFor npm, Homebrew, and default install-script installs (Hunk 0.20+):\n\n```bash\nhunk update          # install the newest release\nhunk update --check  # check without installing\nhunk update 0.20.0   # select an exact npm or default install-script release\n```\n\nOn an older release, update once with the installer or package manager that installed Hunk, then use `hunk update` going forward. Custom `HUNK_INSTALL_DIR` installs must re-run the installer with the same directory; mise, Nix, and source installs use their owning tools instead.\n\n## Quick start\n\n```bash\nhunk           # show help\nhunk --version # print the installed version\n```\n\n### Working with Git\n\nUse familiar Git commands to open changes in Hunk:\n\n```bash\nhunk diff                      # review current repo changes, including untracked files\nhunk diff --watch              # auto-reload as the working tree changes\nhunk show                      # review the latest commit\nhunk show HEAD~1               # review an earlier commit\nhunk log                       # browse history on a terminal; print when redirected\nhunk log --static              # force static output, paging when needed\nhunk status                    # view workspace status and sibling worktrees (Git)\nhunk status --static           # print one read-only workspace snapshot\nhunk status --json             # emit one versioned workspace snapshot\n```\n\n`hunk status` is read-only: it does not fetch or change the repository. See [workspace status](docs/status.md) for details.\n\n`hunk log` opens an interactive history browser in a terminal and prints text when piped or redirected. Use `--oneline` for compact output. In the browser, enable **Graph view** from the View menu to see commit branches, or press `v` to select a commit range. Quit a commit review to return to history.\n\n### Working with GitHub\n\nHunk bundles `hunk gh`, so pull requests, commits, and comparisons open directly without the\nGitHub CLI or a separate extension:\n\n```bash\nhunk gh pr                         # infer the open PR for the current branch\nhunk gh pr 123                     # infer the repository from origin\nhunk gh pr 'modem-dev/hunk#123'    # quote this form for shells that treat # as a comment\nhunk gh commit a1b2c3d --repo modem-dev/hunk\nhunk gh compare main...feature --repo modem-dev/hunk\n```\n\nPublic repositories work anonymously. Set `GH_TOKEN`, or `GITHUB_TOKEN` when it is absent, for\nprivate repositories and higher API limits. GitHub Enterprise is not currently supported.\n\n### Working with Jujutsu and Sapling\n\nHunk auto-detects Jujutsu and Sapling checkouts, so `hunk diff [revset]` and `hunk show [revset]` use native revsets inside jj or Sapling workspaces. `hunk log --vcs jj` also reads JJ history directly, including in a non-colocated workspace. To override VCS detection, set `vcs = \"git\"` or `vcs = \"jj\"` or `vcs = \"sl\"` in [config](#config).\n\n### Viewing complete files and browsing directories\n\n```bash\nhunk open                 # browse the current directory\nhunk open README.md       # view a complete, syntax-highlighted document\nhunk open path/to/project # browse another directory without requiring a repository\n```\n\nUse arrows and Enter to navigate, Tab to switch focus, `i` to show hidden/Git-ignored entries, and `e` to edit the displayed file in `$EDITOR`. See [the open workflow](docs/open.md) for details and file limits.\n\n### Working with raw files and patches\n\n```bash\nhunk diff --files before.ts after.ts        # compare two files directly\nhunk diff --files before.ts after.ts --watch # auto-reload when either file changes\ngit diff --no-color | hunk patch -          # review a patch from stdin\n```\n\n### Working with agents\n\n1. Install the review skill once: `npx skills add modem-dev/hunk -g --skill hunk-review`.\n2. Open Hunk in another terminal with `hunk diff` or `hunk show`.\n3. Ask your agent to use the hunk-review skill against the live Hunk session.\n4. Press `a` in Hunk to show agent notes; they start hidden.\n\nIf your agent has no skill system, ask it to run `hunk skill show` and follow the output.\n\nSee [Review with an agent](https://hunk.dev/docs/agents/review-with-an-agent/) for live sessions, `--agent-context`, and experimental rich notes.\n\n## Feature comparison\n\n| Capability                           | [hunk](https://github.com/modem-dev/hunk) | [lumen](https://github.com/jnsahaj/lumen) | [difftastic](https://github.com/Wilfred/difftastic) | [delta](https://github.com/dandavison/delta) | [diff-so-fancy](https://github.com/so-fancy/diff-so-fancy) | [diff](https://www.gnu.org/software/diffutils/) |\n| ------------------------------------ | ----------------------------------------- | ----------------------------------------- | --------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |\n| Review-first interactive UI          | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Multi-file review stream + sidebar   | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Inline agent / AI annotations        | ✅                                        | ❌                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Responsive auto split/unified layout | ✅                                        | ❌                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Mouse support inside the viewer      | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Runtime view toggles                 | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Syntax highlighting                  | ✅                                        | ✅                                        | ✅                                                  | ✅                                           | ❌                                                         | ❌                                              |\n| Structural diffing                   | ❌                                        | ❌                                        | ✅                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Pager-compatible mode                | ✅                                        | ❌                                        | ✅                                                  | ✅                                           | ✅                                                         | ✅                                              |\n\n## Advanced\n\n### Config\n\nYou can persist preferences to a config file:\n\n- `$XDG_CONFIG_HOME/hunk/config.toml` (default `~/.config/hunk/config.toml`)\n- `.hunk/config.toml` in a repository\n\nPersistent state uses `$XDG_STATE_HOME/hunk/state.json` when the variable is\nabsolute, or `~/.local/state/hunk/state.json` otherwise. Hunk preserves existing\nstate by copying the old config-directory file on first use, keeping the original\nand falling back to it if migration fails. Global extension paths are unchanged.\n\nFor example:\n\n```toml\ntheme = \"github-dark-default\"\nmode = \"auto\"               # auto, split, unified\nauto_split_min_width = 160  # minimum available diff columns for split in auto mode\nline_numbers = true\nwrap_lines = false\n```\n\nSee the [configuration guide](https://hunk.dev/docs/configure/configuration/) for command overrides and examples, the [full reference](https://hunk.dev/docs/reference/config/) for every option, or the [theme guide](https://hunk.dev/docs/configure/themes/) to customize colors.\n\n### Keybindings\n\nRemap shortcuts with a `[keybindings]` table in your user config. See [the keybinding guide](https://hunk.dev/docs/configure/keybindings/) for available commands, default keys, and examples.\n\n### Git integration\n\nSet Hunk as your Git pager so `git diff` and `git show` open in Hunk automatically:\n\n> [!NOTE]\n> Untracked files are auto-included only for Hunk's own `hunk diff` working-tree loader. If you open `git diff` through `hunk pager`, Git still decides the patch contents, so untracked files will not appear there.\n\n```bash\ngit config --global core.pager \"hunk pager\"\n```\n\nOr in your Git config:\n\n```ini\n[core]\n    pager = hunk pager\n```\n\nIf you want to keep Git's default pager and add opt-in aliases instead:\n\n```bash\ngit config --global alias.hdiff \"-c core.pager=\\\"hunk pager\\\" diff\"\ngit config --global alias.hshow \"-c core.pager=\\\"hunk pager\\\" show\"\n```\n\n### Jujutsu pager integration\n\nTo use Hunk as jj's pager, run `jj config edit --user` and update:\n\n```toml\n[ui]\npager = [\"hunk\", \"pager\"]\ndiff-formatter = \":git\"\n```\n\n### Sapling pager integration\n\nTo use Hunk as Sapling's pager, run `sl config -u` and update:\n\n```ini\n[pager]\npager = hunk pager\n```\n\n### Extensions (experimental)\n\nThe extension API is experimental and may change in breaking ways between\nminor releases while it stabilizes; breaking changes are called out in\nrelease notes.\n\nHunk loads plain TypeScript extensions from `~/.config/hunk/extensions/`, from a\nrepository's `.hunk/extensions/` (after you explicitly trust that repository),\nand from `--extension <path>` for development. `--no-extensions` turns those off\nfor one run; Hunk's own bundled backends (Git, Jujutsu, and Sapling) stay loaded.\n\nExtensions can add commands, themes, VCS backends, custom sidebars, and more. For example, filter lockfiles out of a review:\n\n```ts\n// ~/.config/hunk/extensions/collapse-lockfiles.ts\nimport type { HunkExtensionAPI } from \"hunkdiff/extension\";\n\nexport default function (hunk: HunkExtensionAPI) {\n  hunk.transformChangeset((changeset, ctx) => {\n    const files = changeset.files.filter((file) => !file.path.endsWith(\".lock\"));\n    ctx.notify(`Collapsed ${changeset.files.length - files.length} lockfiles`);\n    return { ...changeset, files };\n  });\n}\n```\n\nExtensions shared as git repositories install straight from their host, and a\n`hunk-extension` GitHub topic marks community ones:\n\n```bash\nhunk extension install acme/hunk-word-diff@v1.2.0   # or git:host/path, a URL, a local path\nhunk extension list                                 # then update [name] / remove <name>\n```\n\nBrowse community extensions at\n[github.com/topics/hunk-extension](https://github.com/topics/hunk-extension);\npublish yours by pushing the extension to a repository root and adding that\ntopic.\n\nSee [the extension guide](https://hunk.dev/docs/extend/extensions/) for the API, configuration, and publishing instructions.\nInstallable examples include\n[review triage](https://github.com/modem-dev/hunk/tree/main/examples/extensions/review-triage),\n[review snapshot export](https://github.com/modem-dev/hunk/tree/main/examples/extensions/review-snapshot-export), an optional\n[rendered Markdown file view](https://github.com/modem-dev/hunk/tree/main/examples/extensions/rendered-markdown), and a\n[Vim navigation mode](https://github.com/modem-dev/hunk/tree/main/examples/extensions/vim-navigation) built from public semantic commands.\n\n### OpenTUI component\n\nHunk also publishes `HunkDiffView` and lower-level primitives from `hunkdiff/opentui` for embedding the same diff renderer in your own OpenTUI app.\n\nSee [the OpenTUI component guide](https://hunk.dev/docs/reference/opentui-components/) for install, API, and runnable examples.\n\n## Examples\n\nReady-to-run demo diffs live in [the examples directory](https://github.com/modem-dev/hunk/tree/main/examples).\n\nEach example includes the exact command to run from the repository root.\n\n## Contributing\n\n💬 _Chat with users/contributors on the [Modem Discord server](https://discord.gg/WZFjaP6Gt8)_\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution guidelines.\n\n## Sponsor\n\nSponsored by [Modem](https://modem.dev?utm_source=github&utm_medium=oss&utm_campaign=oss_hunk&utm_content=readme_footer).\n\n<a href=\"https://modem.dev?utm_source=github&utm_medium=oss&utm_campaign=oss_hunk&utm_content=readme_footer\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://modem.dev/images/logo/svg/modem-combined-white.svg\">\n    <source media=\"(prefers-color-scheme: light)\" srcset=\"https://modem.dev/images/logo/svg/modem-combined-black.svg\">\n    <img src=\"https://modem.dev/images/logo/svg/modem-combined-black.svg\" alt=\"Modem\" width=\"220\">\n  </picture>\n</a>\n\n## License\n\n[MIT](https://github.com/modem-dev/hunk/blob/main/LICENSE)\n","isInternal":false,"tokens":3667,"sizeBytes":16920},{"name":"README.md","path":"packages/session-broker-bun/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/session-broker-bun/README.md","title":"session-broker-bun Documentation","category":"plugin-manifest","format":"markdown","content":"# @hunk/session-broker-bun\n\nPrivate Bun HTTP and websocket adapter for `@hunk/session-broker`.\n\nUse this workspace to serve a runtime-neutral `SessionBrokerDaemon` through `Bun.serve(...)`.\nIt is not a published, versioned SDK package. See the main broker README for current internal API\nconstruction and the [session broker SDK contract](../../docs/session-broker-sdk.md) for the future\npublic package.\n\n## What it does\n\n- binds a broker daemon to a Bun HTTP server\n- upgrades websocket requests on the daemon socket path\n- forwards websocket messages and close events into the daemon\n- exposes a `stopped` promise compatible with Hunk's daemon lifecycle\n- lets callers override or add custom HTTP routes before the daemon's built-in routes\n\n## Usage\n\nCreate the daemon with steps 1–2 in\n[`@hunk/session-broker`](../session-broker/README.md), then bind it to Bun:\n\n```ts\nimport type { SessionBrokerDaemon } from \"@hunk/session-broker\";\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-bun\";\n\ndeclare const daemon: SessionBrokerDaemon;\n\nconst server = serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n## Custom routes\n\nYou can override or extend request handling with `handleRequest`.\n\n```ts\nconst server = serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n  handleRequest: async (request) => {\n    const url = new URL(request.url);\n    if (url.pathname === \"/health\") {\n      return Response.json({ ok: true, overridden: true });\n    }\n\n    return undefined;\n  },\n});\n```\n\nReturn `undefined` to fall through to the daemon's built-in routes. The raw `/broker` HTTP API is available only when the daemon was created with `exposeHttpApi: true`.\n\n## License\n\nMIT\n","isInternal":false,"tokens":417,"sizeBytes":1740},{"name":"README.md","path":"packages/session-broker-core/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/session-broker-core/README.md","title":"session-broker-core Documentation","category":"plugin-manifest","format":"markdown","content":"# @hunk/session-broker-core\n\nLow-level shared primitives for the session broker packages.\n\nThis package is an **internal foundation layer**, not the main entrypoint you should build against in most cases.\n\n## Use this package when\n\n- you are working on broker internals\n- you need the shared envelope types and parsers directly\n- you are implementing a higher-level broker package on top of it\n\n## Prefer these packages for normal use\n\n- `@hunk/session-broker` — main runtime-neutral broker API\n- `@hunk/session-broker-bun` — Bun runtime adapter\n- `@hunk/session-broker-node` — Node runtime adapter\n\n## What this package includes\n\n- shared session envelope types\n- registration and snapshot wire parsing helpers\n- low-level in-memory `SessionBrokerState`\n- selector helpers for `sessionId`, `sessionPath`, and `repoRoot`\n- generic terminal metadata capture\n\n## What this package does not include\n\n- daemon behavior\n- session-side websocket lifecycle helpers\n- Bun or Node listener setup\n- app-specific command semantics or projections\n\nThose higher-level concerns live in the packages above.\n\n## Package boundary\n\nThe intended split is:\n\n- **`@hunk/session-broker-core`** — low-level primitives\n- **`@hunk/session-broker`** — main broker API\n- **runtime adapters** — Bun and Node listener bindings\n\n## Quick example\n\n```ts\nimport {\n  brokerWireParsers,\n  parseSessionRegistrationEnvelope,\n  parseSessionSnapshotEnvelope,\n  SessionBrokerState,\n} from \"@hunk/session-broker-core\";\n```\n\nIf you find yourself reaching for this package directly in app code, double-check whether `@hunk/session-broker` would be the better fit.\n\n## License\n\nMIT\n","isInternal":false,"tokens":364,"sizeBytes":1651},{"name":"README.md","path":"packages/session-broker-node/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/session-broker-node/README.md","title":"session-broker-node Documentation","category":"plugin-manifest","format":"markdown","content":"# @hunk/session-broker-node\n\nPrivate Node HTTP and websocket adapter for `@hunk/session-broker`.\n\nUse this workspace to serve the broker daemon under Node instead of Bun. It is not a published,\nversioned SDK package. See the main broker README for current internal API construction and the\n[session broker SDK contract](../../docs/session-broker-sdk.md) for the future public package.\n\n## What it does\n\n- serves a runtime-neutral `SessionBrokerDaemon` through Node HTTP\n- upgrades websocket requests with `ws`\n- forwards websocket messages and close events into the daemon\n- exposes async startup and shutdown helpers\n- keeps the runtime-specific listener code out of `@hunk/session-broker`\n\n## Usage\n\nCreate the daemon with steps 1–2 in\n[`@hunk/session-broker`](../session-broker/README.md), then bind it to Node:\n\n```ts\nimport type { SessionBrokerDaemon } from \"@hunk/session-broker\";\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-node\";\n\ndeclare const daemon: SessionBrokerDaemon;\n\nconst server = await serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n## Why this package exists\n\nThis package runs the shared broker API against Node listener and websocket primitives.\n\nIf the Node adapter needs an abstraction the shared package does not provide, the fix should happen in `@hunk/session-broker`, not as Node-only glue.\n\n## License\n\nMIT\n","isInternal":false,"tokens":315,"sizeBytes":1395},{"name":"README.md","path":"packages/session-broker/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/session-broker/README.md","title":"session-broker Documentation","category":"plugin-manifest","format":"markdown","content":"# @hunk/session-broker\n\nRuntime-neutral session broker daemon and connection helpers.\n\nThe [session broker SDK contract](https://github.com/modem-dev/hunk/blob/main/docs/session-broker-sdk.md)\ndescribes the future supported public package. These private `0.0.0` source workspaces predate that\ncontract and do not yet satisfy all of its security, compatibility, supervision, packaging, or\nrelease gates.\n\nThe current package provides signed producer/caller hello, short-lived caller sessions, replay\nadmission, and default-deny raw HTTP authorization primitives. Hunk discovers app-scoped\ncredentials, starts session producers, and authenticates callers in its own runtime adapters. The\nlegacy Hunk WebSocket and custom session routes remain internal-only; no bearer fallback is\navailable. Browser-review capabilities are separate from broker caller credentials.\n\nThis is the **main broker package** in the workspace. It owns the reusable broker behavior without committing to Bun or Node server APIs.\n\nUse this package when you want to:\n\n- track live sessions\n- register and update session snapshots\n- route commands to one live session\n- expose broker health and optional raw list/get/dispatch APIs\n- manage session-side websocket connection state\n\n## Current workspace roles\n\nThe source tree is currently split into four private workspaces:\n\n- `@hunk/session-broker-core` — low-level shared primitives and envelope parsing\n- `@hunk/session-broker` — **main runtime-neutral broker API**\n- `@hunk/session-broker-bun` — Bun HTTP/websocket adapter\n- `@hunk/session-broker-node` — Node HTTP/websocket adapter\n\nThe public SDK contract consolidates these into one `@hunk/session-broker` package whose root\nselects Node or Bun automatically. Until that migration lands, these names and examples describe\ninternal workspace usage only.\n\n## What this package owns\n\n- `SessionBroker` raw session registry\n- `SessionBrokerDaemon` runtime-neutral daemon engine\n- `SessionBrokerConnection` runtime-neutral session-side websocket helper\n- raw broker HTTP request types\n- health handling and optional capabilities API handling\n- stale-session pruning and idle shutdown\n\n## What this package does not own\n\n- Bun `Bun.serve(...)`\n- Node `http` / `ws` listener setup\n- app-specific command semantics\n- app-specific projections like Hunk review exports, comments, or selected hunks\n- daemon process launch policy\n\n## Current internal quick start\n\n### 1. Create a broker\n\n```ts\nimport {\n  SessionBroker,\n  brokerWireParsers,\n  createSessionBrokerProtocolParsers,\n  parseSessionRegistrationEnvelope,\n  parseSessionSnapshotEnvelope,\n} from \"@hunk/session-broker\";\n\ninterface SessionInfo {\n  title: string;\n}\n\ninterface SessionState {\n  selectedIndex: number;\n}\n\nfunction parseInfo(value: unknown): SessionInfo | null {\n  const record = brokerWireParsers.asRecord(value);\n  if (!record) {\n    return null;\n  }\n\n  const title = brokerWireParsers.parseRequiredString(record.title);\n  return title === null ? null : { title };\n}\n\nfunction parseState(value: unknown): SessionState | null {\n  const record = brokerWireParsers.asRecord(value);\n  if (!record) {\n    return null;\n  }\n\n  const selectedIndex = brokerWireParsers.parseNonNegativeInt(record.selectedIndex);\n  return selectedIndex === null ? null : { selectedIndex };\n}\n\nconst protocolParsers = createSessionBrokerProtocolParsers({\n  appRevision: 1,\n  features: [],\n  parseRegistration: (value) => parseSessionRegistrationEnvelope(value, parseInfo),\n  parseSnapshot: (value) => parseSessionSnapshotEnvelope(value, parseState),\n  commands: [\n    {\n      command: \"select\",\n      version: 1,\n      parseInput: (value) => (brokerWireParsers.parseNonNegativeInt(value) === null ? null : value),\n      parseResult: (value) => (value === true ? true : null),\n    },\n  ],\n});\n\nconst broker = new SessionBroker({ protocolParsers });\n```\n\n### 2. Create a daemon engine\n\n```ts\nimport { createSessionBrokerDaemon } from \"@hunk/session-broker\";\n\nconst daemon = createSessionBrokerDaemon({\n  broker,\n  capabilities: {\n    version: 1,\n    name: \"example-broker\",\n  },\n});\n```\n\nAt this point the daemon can:\n\n- handle health requests\n- process websocket register/snapshot/heartbeat/result messages\n- prune stale sessions and request idle shutdown\n\nThe raw HTTP broker API is opt-in and fails closed: `exposeHttpApi: true` exposes no control route\nunless an explicit immutable `appId`, a singleton `appRevision`, a `callerAuthenticator`, and an app\n`authorizer` are supplied. The included\n`SessionBrokerAuthenticator` implements Ed25519 challenge/proof and signed requests; applications\ninject app-scoped grants, public verifiers, daemon signing identity, revocation policy, and their\nown default-deny authorization hook. It performs no filesystem, environment, coordinator, or Hunk\ncredential discovery.\n\n### 3. Serve it through a runtime adapter\n\n#### Bun\n\n```ts\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-bun\";\n\nconst server = serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n#### Node\n\n```ts\nimport { serveSessionBrokerDaemon } from \"@hunk/session-broker-node\";\n\nconst server = await serveSessionBrokerDaemon({\n  daemon,\n  hostname: \"127.0.0.1\",\n  port: 47657,\n});\n```\n\n## Session-side connection helper\n\nUse `SessionBrokerConnection` when an app window or live process needs to stay registered with the broker.\n\n```ts\nimport {\n  createNativeSessionBrokerLifecycleClock,\n  createSessionBrokerConnection,\n  type SessionBrokerSocketLike,\n} from \"@hunk/session-broker\";\n\nconst lifecycleClock = createNativeSessionBrokerLifecycleClock();\nconst connection = createSessionBrokerConnection({\n  url: \"ws://127.0.0.1:47657/session\",\n  createSocket: (url) => new WebSocket(url) as unknown as SessionBrokerSocketLike,\n  registration,\n  snapshot,\n  protocolParsers,\n  lifecycleClock,\n  bridge: {\n    dispatchCommand: async (message) => {\n      if (message.command !== \"select\") throw new Error(\"Unsupported command.\");\n      selectFile(message.input);\n      return true;\n    },\n  },\n});\n\nconnection.start();\n```\n\n`lifecycleClock` may be supplied in the connection options when an application needs to share\nlifecycle timing with its launcher or make producer scheduling deterministic. The ordinary\n`SessionBrokerLifecycleClock` contract provides current time, one-shot scheduling, delayed-first\nfixed-rate interval scheduling, and awaitable delay. Scheduled callbacks return idempotent\ndisposers. `createNativeSessionBrokerLifecycleClock()` uses unref'd native timers so pending\nhandshake, heartbeat, reconnect, and polling work does not retain the process.\n\n`createSocket` must return a fresh socket object for every generation because the helper installs\nproperty callbacks that cannot distinguish queued events after object reuse. Reconnect and close\ncallbacks receive a frozen opaque generation token; after foreign work settles, use\n`connection.isGenerationCurrent(token)` before committing app-owned state. These checks fence\ncommits only: they do not cancel foreign promises, cryptography, probes, or other work already in\nprogress.\n\nUnexpected defects in connection-owned native callbacks, scheduled work, or fire-and-forget work\nterminally retire that connection. Applications may supply `onDefect` to observe the event. The\ncallback receives only `SESSION_BROKER_LIFECYCLE_DEFECT_MESSAGE`; thrown values and lifecycle data\nare never forwarded, the callback runs at most once, and callback failures are contained. This\nruntime-neutral package does not write defect reports to process output.\n\nThe helper owns:\n\n- initial `register`\n- later `snapshot` updates\n- heartbeats\n- `command-result` replies\n- queued broker commands until the bridge is ready\n- reconnect scheduling\n\n## Raw broker API\n\nThe daemon always serves `GET /health`. Its raw capability/control API is intentionally small and\ndisabled by default. When `exposeHttpApi: true` is set together with an explicit `appId`, singleton\n`appRevision`, caller authenticator, and authorizer, it additionally serves:\n\n- `GET /broker/capabilities`\n- `POST /broker`\n\nRequest body shapes:\n\n```ts\n{ action: \"list\" }\n{ action: \"get\", selector: { sessionId: \"...\" } }\n{ action: \"dispatch\", selector: { sessionId: \"...\" }, command: \"...\", commandVersion: 1, input: {...} }\n```\n\nAn omitted `commandVersion` is validated and deliberately defaults to revision `1` for current\ninternal callers. Authentication covers the exact bounded HTTP body bytes before strict UTF-8 and\nJSON decoding. Authenticated responses use `{ body, authentication }`; the signed authentication\nrecord binds daemon generation, broker revision, target application contract when applicable,\nrequest ID, HTTP status, and the canonical structured-body digest.\n\n## Hunk-specific layering\n\nHunk uses this package for generic broker lifecycle and keeps product behavior in `packages/hunk`:\n\n- credential discovery: `packages/hunk/src/session/broker/credentials.ts`\n- daemon and Hunk-specific routes: `packages/hunk/src/session/broker/brokerServer.ts`\n- authenticated agent caller: `packages/hunk/src/session/agent/cliClient.ts`\n- session commands and review projections: `packages/hunk/src/session/`\n\nThe broker owns transport and lifecycle; Hunk owns session meaning and policy.\n\n## License\n\nMIT\n","isInternal":false,"tokens":2026,"sizeBytes":9289},{"name":"README.md","path":"packages/term-video/README.md","rawUrl":"https://raw.githubusercontent.com/modem-dev/hunk/HEAD/packages/term-video/README.md","title":"term-video Documentation","category":"plugin-manifest","format":"markdown","content":"# @hunk/term-video\n\nKeyframe-based terminal product videos. Instead of screen-recording in real\ntime, a script drives the real TUI over a PTY and snaps discrete styled\nkeyframes; a storyboard then declares how long each state holds and which\ncaption accompanies it; Chromium composites each frame onto a stage (window\nchrome, captions, title cards); ffmpeg encodes the result. Pacing is\ndeterministic, a minute of video is ~300 unique frames, and every frame is an\ninspectable PNG before anything is encoded.\n\n## Entry points\n\n- `@hunk/term-video/capture` (Bun) — PTY driving and keyframe snapping:\n  `createKeyframer`, `launchApp`, `launchShell`, `createCommandWrapper`,\n  `typeCommand`, `ensureKeyboardIsLive`, `makeSceneFilter`.\n- `@hunk/term-video/plan` (Bun or Node) — pure storyboard→frame-plan\n  expansion: `planFrames`, `requiredKeyframes`. Unit-tested; owns caption\n  carry-forward plus camera-pan, zoom, and source-aligned highlight animation.\n- `@hunk/term-video/compose` (Node ≥ 22) — `composeStoryboard` renders the\n  plan in headless Chromium against `src/stage.html` (or a custom stage) and\n  writes PNG frames plus an ffmpeg concat list. `playwright-core` is resolved\n  from the caller's work directory so its version can match the driven\n  Chromium build.\n\n- `@hunk/term-video/timeline` (Node ≥ 22) — `renderTimeline` steps a free-form\n  motion page frame by frame instead of planning stage states. The page owns\n  all of its motion and exposes `timelineReady`, `TIMELINE_DURATION`, and a\n  pure `renderAt(t)`, so any frame renders on its own for review stills.\n  `frameTimes` and `encodeArgs` are the unit-tested sampling and encoding\n  pieces.\n\n## Consumers\n\n`scripts/launch-video/` holds Hunk's current storyboard (scenes, captions,\ncards) on top of this package, and `scripts/launch-video/promo/` holds Hunk's\nrelease-promo style kit rendered through `timeline`; `skills/hunk-launch-video/SKILL.md` documents the\nend-to-end workflow and recipes for single-feature, custom, and full-release\nvideos, environment gotchas included.\n","isInternal":false,"tokens":515,"sizeBytes":2063}],"systemPromptSnippet":"<yakaai_skills repo=\"modem-dev/hunk\">\n<!-- File: AGENTS.md (Tokens: ~3771 | Category: root-instruction) -->\n# hunk agent notes\n\n## purpose\n\n- Terminal-first diff viewer for understanding coding-agent changesets.\n- Product target is \"modern desktop diff tool in a terminal\", not a pager-style TUI.\n\n## architecture\n\n### workspace map\n\n```text\npackages/hunk/                 published CLI, application, and public extension/OpenTUI facades\npackages/hunk-vcs/             private dependency-bottom VCS helpers\npackages/hunk-{git,jj,sapling}/ private bundled VCS providers\npackages/hunk-gh/              private bundled GitHub review extension\npackages/session-broker-core/  low-level broker protocol and state\npackages/session-broker/       runtime-neutral broker, daemon, auth, and connection lifecycle\npackages/session-broker-{bun,node}/ runtime listener adapters\npackages/term-video/           private terminal capture tooling\nwebsite/                       product/documentation site, not the browser review client\n```\n\n### application flow\n\n```text\nmain.tsx -> app/startup.ts -> app/cli.ts\n  -> headless command/history plan returned to main.tsx, or\n  -> app plan: extensionBootstrap.ts -> sessionBootstrap.ts -> normalized Changeset / DiffFile[]\n     -> main.tsx lazy-loads runInteractiveApp.tsx -> HunkSessionHost -> AppHost -> App\n     -> pane/diff planning -> Pierre-backed terminal rows\n```\n\n### shared review seam\n\nTerminal, agent/session, broker/protocol, extension, and HTTP/SSE consumers share review semantics.\nThe browser client and UI remain planned. Do not recreate semantic review behavior in a surface:\n\n```text\nDiffFile[] -> projectReviewDocument -> ReviewDocumentV1 -> ReviewStore\nReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer -> surface projection\n```\n\n- **Model:** `packages/hunk/src/core/review/{types,document,identity}.ts` owns the ordered, JSON-safe document.\n  File order is review/sidebar order; use `key` (referenced as `fileKey` elsewhere),\n  `contentIdentity`, and `sourceIdentity` (cached source text additionally requires\n  `sourceAttested`) — not runtime IDs or indexes — across reloads/surfaces.\n- **Shared derivations:** `geometry.ts`, `expansion.ts`, `anchors.ts`, `stml.ts`, and\n  `contentManifest.ts` own ranges, gaps, source splitting, note targets/ownership, tag roles, and\n  parity manifests. Consume them; never re-derive those facts in a renderer.\n- **State:** `state.ts` is semantic state; `actions.ts` transitions; `reducer.ts` pure/no-I/O;\n  `selectors.ts` shared policies; `store.ts` synchronous observable storage. New cross-surface\n  operations start as intents. Callers supply mutable-note IDs/timestamps; core derives identities.\n- **Surfaces/publishers:** `useTerminalReview.ts` is the TUI adapter and\n  `reviewNoteMapping.ts` is terminal-only. Rows, measurement, scrolling, layout, themes, DOM\n  mechanics, and source I/O stay local. `useHunkSessionBridge.ts` publishes the current terminal\n  session export; `registration.ts` builds its metadata/initial snapshot and `bridge.ts` receives\n  agent commands. This broker export is not a full `ReviewState` mirror.\n- **Other consumers:** Web/API consumers reuse the model, derivations, state, intents, and the\n  producer/protocol tier. Never build a parallel protocol. Keep presentation/client-local state\n  local; host/extension commands need explicit remote capabilities. See\n  `docs/browser-review-rebuild.md` for the rollout and current boundaries.\n- **Conformance:** `test/review-conformance/` has hand-authored semantic fixtures covering every\n  registered core, terminal, producer, broker, protocol, and extension projection. Every new\n  semantic consumer registers its real projection and runs the whole corpus.\n  `scripts/quality/source-boundaries.test.ts` keeps the seam\n  renderer/platform-free; its Node-debt list is shrink-only and tombstone lists append-only. A\n  repaid seam finding deletes copies, adds a file or banned-symbol tombstone and adversarial\n  fixture, registers consumers, and updates `docs/browser-review-seam-audit.md`.\n\n- Bundled VCS implementations live in the private `packages/hunk-{git,jj,sapling}` workspaces and consume\n  the public `hunkdiff/extension` contract plus explicit `@hunk/vcs/*` implementation leaves;\n  `packages/hunk/src/app` composes their registrations into the\n  provider-neutral core VCS catalog. Do not add provider commands, spawning, or source readers under `packages/hunk/src/core`.\n- `hunk daemon serve` is the one loopback daemon for all live sessions; sessions auto-start and\n  register with it rather than opening per-TUI ports. Reuse `classifyReviewPublication` and\n  `ReviewChunkAssembler` for publication ordering and bounded, digest-verified resources. Browser\n  review stays same-origin with no CORS; each session mints its capability and gives the daemon only\n  its digest. Transport semantics come from the browser-safe review protocol modules and the\n  existing intent path. See `docs/browser-review-rebuild.md` and the relevant module headers.\n- User extensions, bundled VCS providers, bundled core commands, and bundled UI share one public\n  registration API and registry model. VCS and UI use dedicated registries; bundled core commands\n  load first into each session-owned extension registry so delegated resources share its lifecycle. `ExtensionSession` owns the\n  user registry across routed surfaces. Keep `packages/hunk/src/extension-api/types.ts` import-free,\n  bundled VCS renderer-free, repo-local extensions trust-gated, and bundled extensions active under\n  `--no-extensions`. See `docs/extension-architecture.md`, `docs/extensions.md`, and\n  `packages/hunk/skills/hunk-extensions/SKILL.md`.\n- Sidecar file order is intentional sidebar and review-stream order.\n- **Session wire:** the daemon and every window exchange `HUNK_SESSION_DAEMON_VERSION`\n  (`packages/hunk/src/session/protocol.ts`) in the signed hello and require an exact match. Any\n  change to what a session registers or snapshots — under `packages/hunk/src/session/**`,\n  `packages/hunk/src/core/reviewDescriptor.ts`, `packages/hunk/src/app/session/registration.ts`, or\n  anything else that appears in `packages/hunk/src/session/broker/fixtures/session-wire.v<N>.json`\n  — requires a bump. `wire.snapshot.test.ts` enforces it; bump, run\n  `bun run generate:session-wire`, and delete the previous fixture in the same change. The\n  cross-revision admin scope (`status`/`stop`) is frozen separately and never grows in place.\n- Derive shared rendering, navigation, scrolling, and note behavior from one planning layer. Make\n  shared geometry explicit, and remove obsolete paths instead of retaining parallel implementations.\n\n## architectural rules\n\n- `bun run deps:check` enforces both the `packages/*` workspace graph and\n  `packages/hunk/src/*` tiers; `.dependency-cruiser.cjs` is authoritative and\n  `docs/module-boundaries.md` explains the model. Standalone packages do not import Hunk internals.\n  Bundled providers use only provider-local modules, platform built-ins, `hunkdiff/extension`, and\n  explicit `@hunk/vcs/*` leaves. The known-violations baseline is shrink-only: fix an edge, rerun\n  `bun run deps:baseline`, and never add to it.\n- Keep the app review-first: the main pane is a single top-to-bottom stream of all visible file diffs.\n- The sidebar is for navigation. Selecting a file jumps to that file in the main review stream; it should not collapse the main pane to one file.\n- Keep Pierre as the diff engine and renderer foundation. Do not switch the main renderer back to OpenTUI's built-in `<diff>` widget.\n- Keep split and unified views terminal-native and driven from the same normalized diff model.\n- Preserve mouse + keyboard parity for primary actions.\n- Keep the chrome restrained: top menu bar, minimal borders, no redundant metadata headers.\n\n## component guidance\n\n- `HunkSessionHost` owns history/review routing in one React root. `AppHost` owns reload\n  serialization, extension adoption, and broker/content/React commit ordering. `App` owns review\n  interaction, navigation, layout, theme, filtering, and pane coordination. Pane and diff modules\n  own rendering and geometry.\n- `InteractiveSessionInitialization` in `packages/hunk/src/core/session/initialization.ts` carries\n  finalized launch inputs whose lifetime spans routed surfaces. Add a concern there only when\n  `HunkSessionHost` owns that cross-surface lifetime; keep surface-local bootstrap data out.\n- Confirmation prompts with a small set of choices should reuse `ConfirmDialog` (body rows plus a clickable key-legend action row) instead of composing `ModalFrame` with a hand-rolled footer; keyboard handling for its actions stays in `useAppKeyboardShortcuts`.\n- Extend existing components or add focused components rather than growing `App` into a monolith.\n- Shared formatting, ids, and small derivations belong in helpers, not repeated inline.\n- When refactoring logic that spans helpers and UI components, add tests at the level where the user-visible behavior actually lives, not only at the lowest helper layer.\n\n## theme guidance\n\n- Built-in theme ids and source metadata live in `packages/hunk/src/core/theme/catalog.ts`; `packages/hunk/src/ui/themes.ts`\n  derives Hunk's semantic `AppTheme` values.\n- When adding or renaming a built-in theme, update validation, public exports, docs/examples, the\n  appropriate Changeset, and tests. Keep source palette tokens separate from semantic mappings and\n  cover non-trivial derived colors.\n- `BUNDLED_SHIKI_THEME_DIFF_COLORS` in `packages/hunk/src/core/theme/catalog.ts` is generated. Edit the sourcing policy in `scripts/generate/generate-theme-diff-colors.ts`, then run `bun run generate:theme-colors`.\n\n## testing\n\n- Colocate unit tests with the code they cover (`packages/hunk/src/core/foo.ts` + `packages/hunk/src/core/foo.test.ts`, `packages/hunk/src/ui/AppHost.*.test.tsx`, `packages/hunk/src/ui/lib/*.test.ts`).\n- Put shared unit-test helpers in `test/helpers/`.\n- Name test helpers so they explicitly include `Test` and are clearly test-only (`createTestDiffFile`).\n- Use repo-level `test/` directories by intent:\n  - `test/cli/` for black-box CLI contract coverage.\n  - `test/session/` for daemon/session integration and end-to-end flows.\n  - `test/pty/` for PTY-backed live UI integration tests.\n  - `test/review-conformance/` for the shared review model's golden fixtures and per-consumer conformance suites.\n  - `test/session-broker-node/` for real Node listener/adapter conformance.\n  - `test/session-broker-runtime/` for shared Bun/Node broker fixtures.\n  - `test/smoke/` for opt-in terminal transcript smoke coverage.\n- `bun run test` does not include review conformance, PTY, TTY smoke, or real-Node adapter\n  conformance under `test/session-broker-node/`. Run the dedicated command documented in\n  `test/README.md` when changing those areas.\n\n## source formatting\n\n- Separate functions, class methods, nested helpers, and test declarations with a blank line; keep JSDoc and test-specific comments attached to their declaration.\n- Use blank lines between logical phases inside functions. Keep related statements grouped rather than packing an entire workflow into one dense block.\n\n## code comments\n\n- Add short JSDoc-style comments to functions and helpers.\n- Write header comments in active voice: the first sentence says what the module or function\n  does (\"Applies one action to the review state and returns the next state.\"), followed by its\n  invariants. Avoid passive or self-important framing (\"The one place where…\", \"the single\n  source of truth for…\") — name the behavior, not the architecture's opinion of itself.\n- For orchestration and controller modules, explain the product workflow before the mechanics:\n  name the user-visible triggers that converge there, state what the module owns, identify the\n  neighboring authority it deliberately leaves elsewhere, and call out preservation or\n  non-reloadable invariants. Prefer concrete flows (\"watch changes and manual refresh both rebuild\n  the mounted review\") over abstract labels (\"handles refresh\").\n- Add inline comments for intent, invariants, ownership, or non-obvious tradeoffs. Do not narrate\n  syntax, praise the implementation, address the reader conversationally, or record temporary\n  implementation history that belongs in Git or maintainer docs.\n\n## naming\n\n- Prefer names that match the role the code plays in the product and architecture.\n- Use `layout` for structural placement or arrangement data.\n- Use `geometry` for aggregate spatial data used by rendering, scrolling, or interaction.\n- Use `bounds` for one concrete visible extent within a larger structure.\n\n## review behavior\n\n- Default behavior is a multi-file review stream in sidebar order.\n- Layout modes are `auto`, `split`, and `unified`. `auto` chooses split on wide terminals and unified\n  on narrow ones; explicit modes override it.\n- `[` and `]` navigate hunks across the full review stream. Do not reintroduce `j`/`k` hunk navigation unless the user asks.\n- Agent context belongs beside the code, not hidden in a separate mode or workflow.\n- Agent notes are hunk-specific: show notes for the selected hunk, render them in the diff flow near the annotated row, and keep a clear spatial relationship to the code they explain.\n- Keep note behavior explicit. If the UI intentionally prioritizes one note, one selection, or one active target, encode that as a named policy rather than scattering array-index assumptions through the codebase.\n- STML markup notes (experimental) live in `packages/hunk/src/ui/lib/stml/`. The layout engine is deliberately a deterministic line layout, not OpenTUI flexbox: the row-windowed review stream needs exact note heights before mount, so `(markup, width)` must always produce the same lines. Colors stay symbolic until render time so measurement never needs a theme. Do not \"simplify\" this into flexbox renderables, and keep note-card geometry in `agentNoteGeometry` as the single source for rendering, measurement, and agent-facing width reporting.\n- Keep temporary sidecars concise and review-oriented. Their file order is intentional, while the\n  visible note UI remains hunk-note driven rather than showing generic explainer cards.\n- Agents review via `packages/hunk/skills/hunk-review/SKILL.md` using `hunk session *` commands; do not run interactive TUI commands directly.\n- `packages/hunk/skills/hunk-review/SKILL.md` is generated. Edit `packages/hunk/src/hunk-review/skillDocument.ts`, `packages/hunk/src/session/agent/surface.ts`, or `packages/hunk/src/session/agent/errors.ts`, then run `bun run generate:skill`; never hand-edit the skill file.\n\n## binary notes\n\n- Installed `hunk` is a compiled snapshot, not linked to source.\n- After source changes, rebuild/reinstall with `bun run install:bin`.\n- For rendering verification, prefer a real TTY smoke run over redirected stdout capture.\n\n## verification\n\n- For rendering changes: run `bun run typecheck`, `bun run test`, `bun run test:integration`,\n  `bun run test:tty-smoke`, and do one real TTY smoke run on an actual diff.\n- For interaction, layout, scrolling, navigation, windowing, or other terminal-native behavior: add or update PTY integration coverage in `test/pty/*-integration.test.ts` and run it with `bun run test:integration`.\n- For broker/runtime changes, run `bun run test:session-broker-node` in addition to the relevant\n  Bun tests.\n- For CLI, config, or pager work: make sure the relevant source invocation still works (`diff`, `show`, `patch`, or `pager`).\n- Preserve current interaction model unless the user asks to change it explicitly.\n\n## cross-platform support\n\n- Hunk should work on macOS, Linux, and Windows. Keep tests and CI portable unless a case is explicitly Unix-only (PTY/TTY smoke coverage is Unix-only).\n- In tests, avoid hard-coded POSIX paths, separators, shell syntax, and filenames invalid on Windows; use Node path helpers for real filesystem paths while preserving user-provided/protocol paths when pass-through is intentional.\n- If Windows-only Bun behavior appears around timers, sockets, or line endings, prefer a small compatibility fix or a narrowly scoped skip with a comment over broadening Unix assumptions.\n\n## releases\n\n- User-visible changes require a Changeset; maintenance-only changes require an empty Changeset.\n  Follow `.changeset/README.md` and do not edit `CHANGELOG.md` directly.\n- For release preparation, publishing, backports, and post-release verification, read `skills/hunk-release/SKILL.md`.\n- Never push a release tag or trigger publishing without explicit user confirmation.\n- `hunk.dev/changelog` is generated from `CHANGELOG.md` by `bun run generate:changelog`; hand-author only `website/releases/notes.json`, and never edit its output. `docs/changelog-on-hunk-dev.md` explains how release dates and the pre-tag window work.\n\n## repo notes\n\n- Local review artifacts are ignored on purpose. Leave them alone unless the user explicitly wants them updated, and do not commit them.\n- Before committing or preparing a PR, follow `CONTRIBUTING.md`.\n\n<!-- File: CLAUDE.md (Tokens: ~3 | Category: claude-rule) -->\nAGENTS.md\n\n<!-- File: .agents/skills/hunk-extensions/SKILL.md (Tokens: ~206 | Category: anthropic-skill) -->\n---\nname: hunk-extensions\ndescription: Maps the `hunkdiff/extension` authoring surface for Hunk, the terminal diff viewer — hiding or reordering reviewed files, docked panes, alternate file views, commands and key bindings, dialogs, workspace writes, themes, syntax languages, VCS backends, lifecycle events. Use when writing, debugging, or installing a Hunk extension, or when a request asks Hunk itself to behave differently. Not for reviewing a diff in a live session — that is hunk-review.\n---\n\n<!-- generated by `bun run generate:skill` -->\n\n# hunk-extensions\n\nThis file is a pointer. The instructions live in the installed `hunk` CLI so they always match its version.\n\nBefore anything else, load them and follow them:\n\n```bash\nhunk skill show hunk-extensions\n```\n\nIf `hunk` is not on PATH, ask the user to install Hunk first: https://hunk.dev/docs/start/install/\n\n<!-- File: .agents/skills/hunk-review/SKILL.md (Tokens: ~166 | Category: anthropic-skill) -->\n---\nname: hunk-review\ndescription: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files, hunks, and exact lines, reloads session contents, adds inline review comments, and paints attention marks on character ranges. Use when the user has a Hunk session running or wants to review diffs interactively.\n---\n\n<!-- generated by `bun run generate:skill` -->\n\n# hunk-review\n\nThis file is a pointer. The instructions live in the installed `hunk` CLI so they always match its version.\n\nBefore anything else, load them and follow them:\n\n```bash\nhunk skill show hunk-review\n```\n\nIf `hunk` is not on PATH, ask the user to install Hunk first: https://hunk.dev/docs/start/install/\n\n<!-- File: packages/hunk/skills/hunk-extensions/SKILL.md (Tokens: ~5884 | Category: anthropic-skill) -->\n---\nname: hunk-extensions\ndescription: Maps the `hunkdiff/extension` authoring surface for Hunk, the terminal diff viewer — hiding or reordering reviewed files, docked panes, alternate file views, commands and key bindings, dialogs, workspace writes, themes, syntax languages, VCS backends, lifecycle events. Use when writing, debugging, or installing a Hunk extension, or when a request asks Hunk itself to behave differently. Not for reviewing a diff in a live session — that is hunk-review.\n---\n\n# Building Hunk extensions\n\nA Hunk extension is **one TypeScript (or JSX/JS) file that default-exports a\nfactory**. Hunk imports it at startup and hands it an API object. No build step,\nno manifest required.\n\n```ts\n// ~/.config/hunk/extensions/hello.ts\nimport type { HunkExtensionAPI } from \"hunkdiff/extension\";\n\nexport default function (hunk: HunkExtensionAPI) {\n  hunk.on(\"startup\", (_event, ctx) => ctx.notify(\"Hello\"));\n}\n```\n\nThis skill is a map of the touchpoints, not a recipe. Decide what to build from\nthe user's request; use the table below to find the call, then read the linked\nmaterial before writing code.\n\n## Sources of truth — read before writing\n\n| Source                                     | What it answers                                              |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `docs/extensions.md`                       | The authoring guide. Every call, every rule. Start here.     |\n| `packages/hunk/src/extension-api/types.ts` | The contract — exact field names, optionality, doc comments. |\n| `examples/extensions/*`                    | Working extensions. Copy these patterns rather than invent.  |\n| `docs/extension-architecture.md`           | Hunk's internals. Needed only when changing the host.        |\n| `docs/keybindings.md`, `docs/themes.md`    | Chord grammar and theme token rules that extensions inherit. |\n\nOutside a Hunk checkout the guide is split across\n<https://hunk.dev/docs/extend/extensions/> (discovery, trust, config) and its\ncompanion pages — extension-api, file-previews, vcs-adapters, custom-panes —\nand the contract ships as `node_modules/hunkdiff/dist/npm/extension/index.d.ts`.\n\nThe examples, by what they demonstrate:\n\n- `review-triage/` — pane + commands + all three dialog shapes + lifecycle\n  events + the extension event bus + a `useSyncExternalStore` bridge.\n- `inline-edit/` — an interactive file-view `mode` driving `ctx.workspace` writes;\n  its README explains the async lifetime rules better than anything else in tree.\n- `rendered-markdown/` — a file view producing host-rendered rows from parsed\n  Markdown, and a folder extension with an npm dependency.\n- `code-document-file-view/` — API-v28 host-owned syntax paint over complete old/new\n  code documents, including semantic gutters and partial UTF-16 ranges.\n- `jsx-file-view/`, `jsx-file-view-gallery/` — the experimental fixed-height JSX\n  row component contract.\n\n## Where extensions live\n\n| Source                                     | Trust            |\n| ------------------------------------------ | ---------------- |\n| `--extension <path>` (repeatable)          | runs immediately |\n| `[extensions] paths` in user config        | runs immediately |\n| `~/.config/hunk/extensions/` (XDG-aware)   | runs immediately |\n| `.hunk/extensions/` or repo-config `paths` | **trust prompt** |\n\nOnly the repo-local group is gated. Everything else — including `--extension`,\neven when its path points inside the repository under review — is read as\nexplicit user intent and executes with full user permissions, no prompt. Never\npass or suggest a path you have not read, including one copied from a\nrepository's own README.\n\nA directory matches `*.ts`/`*.tsx`/`*.js`/`*.jsx`/`*.mjs` at its top level, plus\none level of folder extensions. A folder is an extension if it has a\n`package.json` with `{\"hunk\": {\"extensions\": [\"./index.ts\"]}}`, or an\n`index.{ts,tsx,js,jsx,mjs}`. Reach for a folder only when you need npm\ndependencies, helper modules, or a README; a single file keeps the install to one\n`cp`. A `.hunk/extensions/` folder extension's `node_modules` has to exist on\nevery machine that loads it — keep a repo-shared extension dependency-free.\n\nShared extensions install from git with `hunk extension install <source>`\n(`owner/repo[@ref]`, `git:host/path[@ref]`, a git URL, or a local path) into\n`~/.config/hunk/extensions/installed/<repo-name>/`, where they load with global\norigin; `list`, `update`, and `remove` manage them. Declared `dependencies` are\n`bun install`ed at install time. The manifest may state\n`{\"hunk\": {\"apiVersion\": N}}` — the minimum extension API version — and an older\nHunk refuses the extension with a startup notice instead of failing mid-factory.\nTo publish, push the folder-extension layout to a git repository's root with\nreal `name`/`version`/`description`, tag releases for `@ref` pins, and add the\n`hunk-extension` GitHub topic so it appears at\n<https://github.com/topics/hunk-extension>.\n\nThe **id** is the file stem, or the folder name for a folder extension — unless\nits manifest declares several entries, in which case each entry is its own\nextension named by its own stem (numeric suffix on collision). The id is the\nnamespace it owns: commands are `<id>.<commandId>`, panes and keyboard modes\nare `<id>:<localId>`, config `[extension.<id>]`. Ids match\n`/^[A-Za-z0-9][A-Za-z0-9_-]*$/`; `hunk`, `git`, `jj`, and `sl` are reserved. A\nbad or duplicate id is skipped with a startup notice. `[extensions] disabled = [\"<id>\"]`\nfilters a user extension before import or trust prompting. `--disable-extension <id>` and\n`--enable-extension <id>` are ordered one-run overrides; `--no-extensions` remains the hard switch\nfor all user extensions. Bundled capabilities use separate host-owned selection ids such as\n`hunk.gh`, even when their public registrations remain under the `hunk` namespace.\n\n## Pick the touchpoint\n\n| To do this                                               | Call                                                          |\n| -------------------------------------------------------- | ------------------------------------------------------------- |\n| Keep demo/training view settings temporary               | `hunk.configureSession(options)`                              |\n| Add a selectable color theme                             | `hunk.registerTheme(theme)`                                   |\n| Highlight an extension, exact filename, or filename glob | `hunk.registerFileLanguage(matcher, lang)`                    |\n| Support another VCS (`git`/`jj`/`sl` are reserved)       | `hunk.registerVcsAdapter(adapter)`                            |\n| Report read-only workspace and sibling status            | Optional `registerVcsAdapter({ status })` capability (API 31) |\n| Add a navigation/list/status pane beside the review      | `hunk.registerPane(pane)`                                     |\n| Present a file as something other than a raw diff        | `hunk.registerFileView(view)` (experimental)                  |\n| Mark character ranges inside diff lines                  | `hunk.registerLineHighlighter(highlighter)`                   |\n| Interpret review keys as a temporary global mode         | `hunk.registerKeyboardMode(mode)`                             |\n| Add a generic top-level CLI command tree                 | `hunk.registerCliCommand(command, handler)`                   |\n| Bind a key / add an Extensions-menu entry                | `hunk.registerCommand(command, handler)`                      |\n| Show persistent text on the bottom status row            | `ctx.statusLine.set(item)` in a handler                       |\n| Ask for one line of text inline, `less`-style            | `ctx.prompts.line(options)` in a command                      |\n| Hide, reorder, retitle files before review               | `hunk.transformChangeset(fn)`                                 |\n| React to loads, selection, view movement, notes, reloads | `hunk.on(event, handler)`                                     |\n| Coordinate with another loaded extension                 | `hunk.events.emit` / `hunk.events.on`                         |\n| Reload after an external agent changes reviewed inputs   | `ctx.review.requestReload()` in an event                      |\n| Read user-supplied settings                              | `hunk.config` (`[extension.<id>]` table)                      |\n| Snapshot stable files and every saved review note        | `ctx.review.snapshot()` in a command                          |\n| Branch on the API generation (currently `32`)            | `hunk.apiVersion`                                             |\n\nRegistration is only valid while the factory runs — Hunk seals the API object\nafterwards.\n\nPromise-returning VCS `watchSignature` hooks and watch cancellation require API\nversion 25. Declare `{\"hunk\": {\"apiVersion\": 25}}` in the manifest, or branch on\n`hunk.apiVersion` and return signatures synchronously on older hosts. Use async\nI/O and honor `ctx.signal` on API 25; existing synchronous hooks remain supported.\n\n### Workspace status providers (API 31)\n\nThe optional `registerVcsAdapter({ status })` capability offers cancellable, read-only\n`read`, `readSiblings`, and `planReview` operations, plus optional async `watchPlan`.\nRead [Workspace status capability](../../../../docs/extensions.md#workspace-status-capability)\nand the `ExtensionVcsStatusCapability` contract before implementing it.\n\n- Action ids are supported semantic ids, not opaque strings: `staged` reviews the index;\n  `unstaged` reviews the working copy. Offer each at most once with provider-owned labels.\n  A no-index provider offers only `unstaged` and omits `paths[].index`.\n- Return `{ cwd, input: ExtensionVcsStatusReviewInput }` from `planReview`. Preserve the\n  inspected target and opaque token validation. This exported type excludes pathspecs while\n  preserving exclusive `range` / `rangeEndpoints` comparisons. Any `pathspecs` field, even\n  `[]` or `undefined`, rejects; `input.staged` must match the offered action id.\n- Sibling ids and paths must each be unique and share the current `repositoryId`. If a row\n  matches the current worktree's id or path, both must match. Embedded ready sibling facts\n  and `readSiblings` results follow the same checks; unavailable/error rows never mean clean.\n- Bound collections before returning: 20,000 changed paths, 100 sibling rows, five operations,\n  256 watch targets, four sources per target, 20,000 watch roots/entries per target, and\n  40,000 aggregate collection entries per result (including nested and outer arrays).\n  Defensive action-array validation caps at 32, but only two unique ids are supported.\n  Oversized results reject before per-element schema traversal. Mark bounded sibling scans\n  `truncated`; honor `context.signal`, bound I/O, and reap subprocesses.\n\n### Generic CLI handlers\n\nRegister one lowercase-kebab top-level token; the handler owns every raw token\nbelow it. Built-ins and aliases cannot be shadowed, and discovery order makes\nthe first extension claim win. During development, place the explicit path\nbefore the extension command:\n\n```bash\nhunk --extension ./my-ext.ts my-command sync --help\n```\n\nThe handler receives frozen args plus `ctx.cwd`, `ctx.signal`, streaming\n`ctx.stdin`, and leased `ctx.stdout`/`ctx.stderr` writers. `summary` and\n`usage` are listed when a token reaches discovery unclaimed, so write them as\none short line each. Return `{ kind:\n\"exit\", code? }` or `{ kind: \"delegate\", argv: [\"diff\", ...] }`. Delegation is\nbuilt-in-only and one-time: do not write stdout or read stdin before delegating;\nuse stderr for progress. Reading stdin is an exit-only workflow. Respect cancellation promptly.\nRepo-local providers remain trust-gated; `--no-extensions` performs no discovery\nor import, while a leading explicit `--extension` path is immediate consent.\n\nUse `packages/hunk-gh/src/` as the reference for a complete bundled CLI preprocessor: direct\nauthenticated HTTP with cancellation, temporary artifacts with platform-accurate permission claims\nretained through delegated startup, cleanup on `shutdown`, and a one-time handoff to built-in\n`patch` without touching stdin or stdout.\n\n## What handlers receive\n\nEvery event, bus, command, and file-view mode handler — plus every changeset\ntransform — gets `ctx.cwd` and `ctx.notify(message, type?)`. A file view's\n`matches` and `layout` get no context at all. Beyond that:\n\n- **Event and bus handlers** also get `ctx.panes` (open/close/toggle/isOpen on\n  any pane), live `ctx.navigation`, attributed `ctx.dialogs`, `ctx.statusLine`\n  (set/clear this extension's status-row items), review reloads through\n  `ctx.review.requestReload()`, and\n  `ctx.events.emit`. `ctx.sidebars` is a deprecated alias for `ctx.panes`.\n- **Command handlers** get `ctx.panes`, `ctx.fileViews` (select/toggle/isActive/\n  refresh/enterMode/exitMode), `ctx.highlights` (refresh prepared line marks,\n  whole or `{ fileId }`-scoped), `ctx.selection` (a snapshot of file, hunk index,\n  nullable current `{ side, line }` source address, and `files`, the visible files\n  in review order), `ctx.navigation` (live,\n  guarded `selectFile`/`selectHunk`/`revealLine`, the\n  last landing one exact `(side, line)` near the viewport top), `ctx.commands`\n  (`isEnabled`/`execute` for public semantic `hunk.*` commands),\n  `ctx.keyboardModes` (enter/exit/probe this extension's session modes), `ctx.review`\n  (deeply immutable snapshots of stable files and complete saved store notes),\n  `ctx.dialogs` (`confirm`/`select`/`input`, queued and attributed),\n  `ctx.statusLine` (set/clear persistent status-row items), `ctx.prompts`\n  (`line`: an inline status-row input resolving the text or `null`, queued and\n  attributed like dialogs), and\n  `ctx.workspace` (`readDocument`, `canWriteDocument`, `writeDocument` with consent).\n- **Pane components** get frozen `files`, selection, placement, exact dimensions,\n  nullable immutable delegated-source `review` metadata, optional `currentLine` paint\n  (with `{ side, line }` when opted in), semantic `theme`, resolved `keybindings`, and\n  guarded navigation/notification `actions`. Availability callbacks receive the same\n  `review` value, so a pane can consume no geometry for ordinary reviews.\n- **File-view `layout`** gets `file`, `width`, `signal`, `changes`, and a lazy\n  `readDocument(side)`.\n- **File-view `mode` handlers** get `ctx.file` and `ctx.fileViews`. `onKey`,\n  `onEnter`, and `onExit` must answer **synchronously** — `onKey`'s return value\n  (`\"handled\"`/`\"pass\"`/`\"exit\"`) is the routing decision, so kick off async work\n  and report it later through `notify` or `refresh`. A passed key reaches any\n  active session keyboard mode before ordinary Hunk routing. Escape is host-owned\n  and never reaches `onKey`.\n- **Session keyboard-mode handlers** get only `ctx.commands`, `ctx.highlights`,\n  `ctx.statusLine`, and activation-scoped `ctx.keyboardModes` beyond the standard\n  context. A prompt-shaped interaction is a command plus `ctx.prompts.line()`,\n  not a mode. Those controls become inert on\n  exit, and lifecycle callbacks cannot change keyboard ownership. Keys are frozen\n  snapshots; dialogs, focused inputs, and file-view modes outrank them. When the\n  session mode owns input, Escape exits it; the status badge and Extensions menu\n  are unconditional host-owned exits.\n\nEvent payloads, pane props, and a command's selection all hand you frozen\n`ExtensionDiffFile` / `ExtensionDiffHunk` views. A changeset transform is the\nexception: it receives the live changeset and is expected to return a new one.\n`metadata` is unfrozen either way — it is the renderer's parsed diff, so pass it\nthrough untouched.\n\n## Rules that bite\n\nMost extension bugs are one of these:\n\n- **Registering a surface does not show it.** Panes need `defaultOpen`,\n  `replaces: \"hunk:files\"`, or a command that opens them. File views remain raw\n  until selected from the **View** menu.\n- **A rejected file-view layout silently becomes raw diff.** `hunkRows` needs one\n  in-bounds, inclusive entry per parsed hunk at the same array index, and\n  `sourceRanges` may not overlap on a side; invalid, oversized, cancelled, and\n  throwing layouts warn once and fall back.\n- **Syntax paint stays declarative.** File views declare complete `codeDocuments` and\n  map exact symbolic spans with `syntax`; Hunk owns tokenization, theme colors,\n  visible-window demand, resource limits, and plain fallback. Keep gutters and\n  separators in non-syntax spans, and use `sourceRanges` separately for notes/navigation.\n- **Never bundle or vendor React.** Hunk serves its own `react` and `@opentui/*`\n  to extension files; a second copy means a second hooks dispatcher and the\n  component fails to render. Import them normally. OpenTUI intrinsics (`box`,\n  `text`, `scrollbox`) need no import.\n- **`layout` is a pure derivation of `(file, width)`.** A stateful view keeps\n  painting its first answer until `ctx.fileViews.refresh(viewId)` — scope it with\n  `{ fileId }` when the state belongs to one file.\n- **Handler state must live outside the component.** Panes unmount when closed;\n  bridge module-level state into React with `useSyncExternalStore` and immutable\n  snapshots (`review-triage/index.tsx` is the working version).\n- **Use `ctx.review.snapshot()` for complete saved-note state.** `note_created` and\n  `note_edited` are incremental UI events, not an authoritative collection. Snapshots\n  include stale and orphaned saved notes, exclude drafts and static sidecar annotations,\n  and should be re-read before irreversible async work; compare both generation and revision.\n  `review-note-navigator` shows how to join stable note ids and file keys back to guarded\n  navigation after awaiting a selector; file filters can still refuse hidden targets.\n- **Retained review controls expire on reload.** An old handler cannot control\n  replacement content: pane/navigation calls become inert, dialogs cancel, and\n  workspace reads or not-yet-started writes return `null`/`unavailable`. A\n  consented write already in progress reports its real outcome, holds graceful\n  exit until it settles, and reconciles the active review on success. `shutdown`\n  runs after revocation, so use it only\n  to release extension-owned resources.\n- **A reload keeps your factory and renames the files.** Factories re-run only\n  after a trust grant or a cwd change, so module state survives — but a file's\n  `id` encodes its position in the changeset, so a reload that adds or drops a\n  file renumbers the rest. Key durable per-file state by `path`, or reconcile it\n  on `changeset_loaded`. Pick one deliberately.\n- **Transforms must preserve `metadata`** (spreading a file does), keep ids\n  unique, and return a real changeset — otherwise the transform is skipped with a\n  warning and the previous changeset carries forward.\n- **Chords are defaults.** Users remap by command id in `[keybindings]`; built-ins\n  win conflicts, refused one chord at a time. Bind the character shift produces\n  (`\"!\"`, not `\"shift+1\"`).\n- **Keyboard modes are grammar, not behavior.** Keep pending sequences and\n  numeric prefixes in the extension, then call one public `ctx.commands.execute`\n  after resolving an action. `vim-navigation` demonstrates counts, Ctrl chords,\n  and a `:` key passed to a registered command whose host input dialog temporarily\n  outranks the still-active mode.\n- **`ctx.commands` invokes Hunk, not other extensions.** Probe with\n  `isEnabled(\"hunk.review.nextHunk\")`, then call `execute(id, { count })` for an\n  explicitly public built-in. Counts are positive whole numbers up to 10,000,\n  applied atomically to movement; one-shot actions run once. Unknown, disabled,\n  private, extension-owned, or stale commands return `false`.\n- **Repo config can set `[extension.<id>]` for a globally installed extension.**\n  Treat `hunk.config` as untrusted for anything exec-adjacent (binary paths,\n  shell commands, module loading).\n- **`ctx.workspace` writes only apply to reloadable, unstaged working-tree\n  reviews**, by reviewed file id, inside the review root, with consent. Everything\n  else returns `{ ok: false, reason }` — check `canWriteDocument` first.\n- **File-view note placement is all-or-raw per file**: an unbound or range-less\n  visible note makes Hunk render the complete raw diff instead of guessing.\n- **Failures are contained, not sandboxed.** A throwing factory is rolled back to\n  zero registrations and a throwing handler is a warning naming the extension —\n  containment against bugs, not against code that should not have been loaded.\n- **The API touches nothing outside the review.** No clipboard, no filesystem, no\n  process surface beyond `ctx.workspace` — an extension is ordinary code, so shell\n  out for the rest. Never write to stdout: the renderer owns it. For the same\n  reason `hunk.log` is collected as diagnostics and printed nowhere; `ctx.notify`\n  is how a user hears from you.\n- **`HunkExtensionUserError`** (detected structurally by `name`) buys the full\n  treatment — message plus `suggestions`, no stack trace — only from a VCS adapter\n  operation, which is where Hunk formats it for the CLI. From a command or event\n  handler only the message survives, as a warning toast.\n\n## Verifying\n\nHunk's TUI needs a real terminal, and the review UI is the user's — **do not\nlaunch `hunk diff`/`hunk show` to test, and do not reach for a pipe.** No\ninvocation applies extensions headlessly: `hunk diff … | cat` still starts the\napp and still takes the keyboard, so it hangs holding the user's terminal.\nPractical checks, in order of cost:\n\n1. **Typecheck.** In a checkout, `bun run typecheck` covers\n   `examples/extensions/**` via the `hunkdiff/extension` path mapping. Standalone,\n   add `hunkdiff` as a dev dependency and run `tsc --noEmit`; for a `.tsx`\n   extension also add `react`, `@types/react` (React ships no declarations of its\n   own), `@opentui/core`, and `@opentui/react` as **dev** dependencies and set\n   `\"jsx\": \"react-jsx\"` with\n   `\"jsxImportSource\": \"@opentui/react\"`, or every `<box>` and `<text>` is an\n   untyped intrinsic. Types only — shipping those packages is the second-React bug.\n2. **Unit-test the logic.** When parsing, matching, or formatting is worth\n   testing, put it in helper modules with plain `bun test` coverage.\n3. **PTY integration.** In a checkout, `test/pty/extensions-integration.test.ts`\n   launches Hunk over a PTY with `--extension <path>` and asserts on rendered\n   snapshots; extend it via `test/pty/harness.ts` and run `bun run test:integration`.\n4. **Hand it to the user** to run: `hunk diff --extension ./my-ext`. `--extension`\n   loads immediately with no trust prompt, so it is the iteration path. Ask them\n   what the footer notices and toasts said.\n5. **Triage with `--no-extensions`** to confirm a symptom belongs to an extension\n   (bundled VCS backends, the built-in files pane, and the `/` content search stay loaded\n   either way).\n\n## If it does not load\n\n- No startup notice at all → a successful load is silent, so either it loaded and\n  nothing opened it, or discovery never saw the file. Check the directory, the\n  entry suffix, or the folder's `package.json` `hunk.extensions` paths.\n- Notice naming the extension → id rejected (reserved, malformed, or already\n  claimed), import failure, missing default export, or a throwing factory.\n- Repo-local extension silently absent → the trust prompt was dismissed or denied;\n  decisions are stored per repo root in `~/.config/hunk/state.json`.\n- Pane closes with a toast → the component threw; a second React copy is\n  the usual cause.\n- Pane or file view never appears → nothing opened it (no `defaultOpen`, no\n  command), `matches` returned false, or the layout was rejected.\n- Command never fires → its chord lost to a built-in or an earlier extension (a\n  warning says so); it is still reachable from the **Extensions** menu and\n  bindable by `<id>.<commandId>`.\n\n## Changing Hunk itself\n\nOnly when the work is in the `hunk` repo rather than in a user extension:\n\n- Shipped VCS backends, `hunk gh`, the built-in files pane, and the `/` content search are\n  **bundled extensions**, registering through the same public API. That dogfooding is deliberate —\n  if the public contract cannot express something, that is a real gap, not a reason for a private\n  path. `default/vcs/` loads from VCS adapter resolution and must stay renderer-free;\n  `default/core/` loads session-owned CLI capabilities from private workspaces such as\n  `packages/hunk-gh`; and `default/ui/` holds process-cached rendering registrations.\n- `packages/hunk/src/extension-api/types.ts` must stay **import-free**; declaration emission\n  publishes whatever it reaches, and `scripts/packaging/check-pack.ts` fails the pack\n  otherwise. Shapes shared with internal code are declared there and re-exported\n  inward.\n- New API surface means updating `docs/extensions.md` (its examples are\n  typechecked as consumer code), the matching hand-written page under\n  `website/src/content/docs/docs/extend/` (only `cli.md` and `config.md` are\n  generated), `docs/extension-architecture.md` if ownership moves, and a changeset.\n- `AGENTS.md` and `docs/extension-architecture.md` own the rest of these rules.\n\n<!-- File: packages/hunk/skills/hunk-review/SKILL.md (Tokens: ~3511 | Category: anthropic-skill) -->\n---\nname: hunk-review\ndescription: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files, hunks, and exact lines, reloads session contents, adds inline review comments, and paints attention marks on character ranges. 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\n10. hunk session highlight add ...                      # light up the exact range you are explaining\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 (<session-id> | --repo <path>) [--json]\nhunk session context (<session-id> | --repo <path>) [--json]\nhunk session review (<session-id> | --repo <path>) [--include-patch] [--include-notes] [--json]\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- `review --include-notes` also returns the live review notes alongside the file and hunk structure\n\n### Navigate\n\n```bash\nhunk session navigate (<session-id> | --repo <path>) --file <path> (--hunk <n> | --old-line <n> | --new-line <n>) [--json]\nhunk session navigate (<session-id> | --repo <path>) --comment <id> [--json]\nhunk session navigate (<session-id> | --repo <path>) (--next-comment | --prev-comment) [--json]\n```\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\nExact comment navigation uses the `commentId` returned by `hunk session comment list --json` and does not require `--file`:\n\n```bash\nhunk session navigate --repo . --comment comment-1\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- A line target lands the user's viewport on that exact line (falling back to its hunk when the line is inside a collapsed region); `--hunk` lands on the hunk\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 (<session-id> | --repo <path> | --session-path <path>) [--source <path>] [--json] -- diff [ref] [-- <pathspec...>]\nhunk session reload (<session-id> | --repo <path> | --session-path <path>) [--source <path>] [--json] -- show [ref] [-- <pathspec...>]\n```\n\nExamples:\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 (<session-id> | --repo <path>) (--reply-to <note-id> | --file <path> (--old-line <n> | --new-line <n>)) --summary <text> [--rationale <text>] [--author <name>] [--markup <stml>] [--focus] [--json]\nhunk session comment apply (<session-id> | --repo <path>) --stdin [--focus] [--json]\nhunk session comment list (<session-id> | --repo <path>) [--file <path>] [--type <live|all|ai|agent|user>] [--json]\nhunk session comment rm (<session-id> | --repo <path>) <comment-id> [--json]\nhunk session comment clear (<session-id> | --repo <path>) [--file <path>] [--include-user|--all] --yes [--json]\n```\n\nExamples:\n\n```bash\nhunk session comment add --repo . --file README.md --new-line 103 --summary \"Tighten this wording\"\nhunk session comment add --repo . --reply-to user:123 --summary \"Addressed in the latest revision\"\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' | hunk session comment apply --repo . --stdin\n```\n\n- Agent notes start hidden: the user sees them only after pressing `a` or setting `agent_notes = true`. `--focus` jumps to the new note (or the first note in a batch) and turns agent notes on, so pass it on the first note you leave in a session, or tell the user to press `a`\n- `comment list` shows the comments added through `hunk session comment` by default; `--type user` lists notes the user wrote with `c`, `--type ai` lists notes loaded from an `--agent-context` sidecar, `--type agent` lists agent-authored notes, and `--type all` lists every note\n- `comment add` is best for one note; `comment apply` is best when an agent already has several notes ready\n- Root `comment add` notes require `--file`, `--summary`, and exactly one of `--old-line` or `--new-line`; replies use `--reply-to <note-id>` with `--summary` and inherit the parent's anchor\n- `comment apply` items require `summary` plus either `replyTo` by itself or `filePath` with 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- `comment list` and `comment clear` accept optional `--file`\n- Quote `--summary` and `--rationale` defensively in the shell\n\n### Attention marks\n\nHighlights paint character ranges inside the diff lines the user is looking at — use them to light up the exact expression you are explaining while you narrate.\n\n```bash\nhunk session highlight add (<session-id> | --repo <path>) --file <path> (--old-line <n> | --new-line <n>) --start <n> --end <n> [--tone <tone>] [--focus] [--json]\nhunk session highlight clear (<session-id> | --repo <path>) [--file <path>] [--json]\n```\n\nExamples:\n\n```bash\nhunk session highlight add --repo . --file src/App.tsx --new-line 42 --start 6 --end 19\nhunk session highlight add --repo . --file src/App.tsx --new-line 42 --start 6 --end 19 --tone warning --focus\nhunk session highlight clear --repo .\n```\n\n- `highlight add` requires `--file`, exactly one of `--old-line` or `--new-line`, and the `--start` / `--end` offsets\n- `--start` is a 0-based inclusive offset into the line's text and `--end` is exclusive, counted in UTF-16 code units — the same `[start, end)` range extensions use\n- Tones: `match` (default), `info`, `warning`, `error`, `dim`; `current` renders as reverse video and is best reserved for the one range under discussion\n- Pass `--focus` to also land the viewport on the marked line\n- Marks survive scrolling, navigation, and reloads that leave the marked file's content unchanged; a reload that changes that file drops its marks, and `highlight clear` removes them explicitly (optionally per `--file`)\n- Marks are visual only — pair them with a `comment add` when the explanation should persist as a note\n\n### Experimental rich markup notes (STML)\n\nOnly use STML when `hunk session context --json` lists `stml` in `experimentalFeatures`. The user opts into that experience by launching the review with `--experimental`; do not ask a normal session to render markup.\n\nFor an opted-in session, `--markup` (or a `markup` field on apply items) renders the note body as STML — a small HTML-like markup for terminal UI (boxes, rows, gauges, badges, lists, code). Keep `--summary` a real sentence: it is the fallback and the `comment list` text.\n\nBefore writing markup, run `hunk markup guide` once — it has copy-paste patterns and the width rules. The session context also reports `noteMarkupWidth` (the live render width); preview with `hunk markup render - --width <that>`. Comment responses echo `markupWidth` and return `markupNotes` when markup degraded — fix what they flag.\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 `highlight add --focus` to steer the user's eyes to the exact expression while you explain it, and `highlight clear` before moving to the next topic\n- Use `comment apply` for agent-generated batches and `comment add` for one-off notes\n- Use `--focus` on your first note so agent notes become visible, then sparingly when a note 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 diff file matches ...\"** -- the file is not in the loaded review. Check `context`, then `reload` if needed.\n- **\"No active Hunk sessions\"** -- if Hunk is visibly running, localhost may be blocked by the agent sandbox; retry with network/sandbox escalation. Otherwise ask the user to open Hunk.\n- **\"Multiple active sessions match\"** -- pass `<session-id>` explicitly.\n- **\"No active 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 exactly one comment target\"** -- pass `comment add` one of `--old-line` or `--new-line`.\n- **\"Specify exactly one highlight target\"** -- pass `highlight add` one of `--old-line` or `--new-line`.\n- **\"Highlight --end must be greater than --start\"** -- offsets are `[start, end)` UTF-16 code units into the line text; end is exclusive.\n- **\"Specify either --next-comment or --prev-comment, not both.\"** -- choose one comment-navigation direction.\n- **\"The session daemon is ...\"** -- a `daemon-build-mismatch` (the `--json` error carries `daemon`, `cli`, `attachedSessions`, and `recommendedAction`). Tell the user which build is newer and how many windows are attached, then **ask** before running `hunk daemon restart --yes`; never restart unprompted. After the restart, windows that failed to register attach on their own, so re-run `hunk session list` instead of relaunching anything. When `recommendedAction` is `use-newer-hunk`, the daemon is the newer build: use that Hunk instead.\n- **\"Could not read the raw diff for ...\"** -- the session reloaded or closed while `--include-patch` was reading it. Re-run `review`; drop `--include-patch` if you only need file and hunk structure.\n\n<!-- File: skills/hunk-launch-video/SKILL.md (Tokens: ~6552 | Category: anthropic-skill) -->\n---\nname: hunk-launch-video\ndescription: Produces Hunk videos by driving the real TUI headlessly in a PTY, compositing captioned 1080p frames in Chromium, and encoding with ffmpeg. Use for feature demos, workflow explainers, announcements, launch videos, and full-release roundups.\nmetadata:\n  internal: true\n---\n\n# Hunk video pipeline\n\nSource-checkout only: the pipeline lives in `scripts/launch-video/`, which never ships to npm.\nIt can be used for pull-request demos as well as maintainer release videos. Unix-only — the\ncapture scripts exec `/bin/bash`.\n\nGenerates product videos where every terminal frame is the real Hunk TUI —\nno screen recording, no mockups. Three stages:\n\n```text\ncapture.ts   (bun)               drive Hunk over a PTY, snap styled keyframes to PNG\ncompose.mjs  (node + Playwright) render each PNG on a 1920x1080 HTML stage in Chromium\nffmpeg                           encode the composited PNGs at 30fps to mp4/webm\n```\n\nPlaywright controls headless Chromium: for each planned frame it loads the\nterminal PNG onto the HTML stage, applies the window chrome, cards, captions,\nand transition state, then screenshots the completed stage back to PNG. ffmpeg\nsequences those composited screenshots into the final videos.\n\nThe generic machinery (PTY driving, keyframe rendering, storyboard planning,\nChromium compositing, the stage template) is the `@hunk/term-video` workspace\npackage in `packages/term-video/`; `scripts/launch-video/` holds only Hunk's\nscenes, captions, and cards on top of it.\n\n## Choosing a recipe\n\nThe pipeline is not release-specific. Choose the editorial scope, then use the\nsame capture → composite → encode stages:\n\n- **Single feature:** a short demonstration of one capability or workflow. Use\n  the single-feature recipe below and capture only the required scene.\n- **Full release:** the social promo for a release, in Hunk's house style\n  (`scripts/launch-video/promo/`). Use the full-release recipe: write one\n  storyboard file for the release; the style kit stays unchanged.\n- **Custom video:** author any set of scenes and `SHOTS` for tutorials,\n  comparisons, announcements, or workflow explainers; follow the scene and\n  storyboard rules below.\n\n## Creating a video\n\nExpect ~3–6 min for capture and ~2–4 min for compose — run both with a long\ntimeout (or in the background); each logs per-snap / per-shot progress.\n`compose.mjs` needs node ≥ 18 on PATH (bun alone is not enough).\n\n```sh\n# 0. dependencies (tuistory is a devDependency; ghostty-opentui arrives transitively)\nbun install    # if a postinstall hook fails in a sandbox, retry with --ignore-scripts\n\n# 1. capture the current storyboard's default scenes. Output defaults to\n#    <repo>/.video-work/ regardless of cwd (pass a path argument to override),\n#    but the PROCESS must run from the repo root — see gotchas.\nbun run scripts/launch-video/capture.ts\n\n# 2. one-time portable compositor setup. Playwright installs a Chromium build\n#    that exactly matches its browser driver (see gotchas to reuse a system or\n#    sandbox browser instead).\nprintf '{\"name\":\"hunk-video-work\",\"private\":true}\\n' > .video-work/package.json\ncd .video-work\nbun add playwright playwright-core\nbunx playwright install chromium\ncd ..\n\n# 3. composite the storyboard\nnode scripts/launch-video/compose.mjs .video-work\n\n# 4. encode\ncd .video-work\nffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n  -c:v libx264 -preset slow -crf 18 -movflags +faststart launch.mp4\nffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n  -c:v libvpx-vp9 -b:v 0 -crf 32 -row-mt 1 launch.webm\n```\n\nOverride the default scene set to iterate on one or more scenes:\n\n```sh\nSCENES=review bun run scripts/launch-video/capture.ts   # comma-separated scene names\n```\n\nScene names are the `wants(\"...\")` guards in `capture.ts`'s `main()`. `SCENES=`\nreplaces the canonical default scene set; `compose.mjs` preflights that every\nframe its `SHOTS` table references exists in `frames/` and fails fast listing\nany missing ones, so a full composite still needs every referenced scene\ncaptured at least once.\n\n## Single-feature recipe\n\nFor a short test, demo, or one-feature announcement, capture and composite only\nthe scene for that feature:\n\n1. Pick one user-visible capability and find its scene name in the\n   `wants(\"...\")` guards. If it does not have a scene, author one using the\n   guidance below. Capture only that scene:\n\n   ```sh\n   SCENES=review bun run scripts/launch-video/capture.ts\n   ```\n\n2. Make a scratch compositor beside the canonical one so its imports and\n   repo-relative paths continue to work:\n\n   ```sh\n   cp scripts/launch-video/compose.mjs scripts/launch-video/compose-one-feature.mjs\n   ```\n\n3. In the scratch copy, trim `SHOTS` to an opening card, only the selected\n   feature's frames, and an outro card. Rewrite those cards and captions for\n   the scoped cut. Sequence lengths must still match the captured frame names.\n4. Composite into the same work directory, then run the normal ffmpeg commands\n   with descriptive output names:\n\n   ```sh\n   node scripts/launch-video/compose-one-feature.mjs .video-work\n   cd .video-work\n   ffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n     -c:v libx264 -preset slow -crf 18 -movflags +faststart hunk-feature-demo.mp4\n   ffmpeg -y -f concat -safe 0 -i concat.txt -vf \"format=yuv420p\" -r 30 \\\n     -c:v libvpx-vp9 -b:v 0 -crf 32 -row-mt 1 hunk-feature-demo.webm\n   cd ..\n   rm scripts/launch-video/compose-one-feature.mjs\n   ```\n\nKeep the scratch compositor uncommitted. The canonical `compose.mjs` remains\nthe checked-in reference storyboard. If you added a capture scene only to make\nPR evidence, revert that scene after encoding; retain it only when it is useful\nchecked-in demo coverage and belongs to the submitted change.\n\n## Full-release recipe\n\nRelease videos use the promo style kit in `scripts/launch-video/promo/`, so\nevery release looks like part of one series. The kit renders through\n`@hunk/term-video/timeline`:\n\n```text\nstoryboards/<version>.mjs   release content: chapters, captions, montage, credits range\nengine.js + stage.html      the style: sting, countdown hook, chapter cards, terminal\n                            scenes, montage, contributors h, outro, transitions\ncontributors.mjs            git range -> contributors.json + avatars/\nrender.mjs                  storyboard + frames + contributors -> promo-<version>.mp4\n```\n\n1. **Pick the headlines.** Read the release section in `CHANGELOG.md` (or its\n   hand-written **Highlights**) and choose 4–6 chapters ordered by impact: new\n   capabilities first, then visible UI/UX changes, then smaller conveniences.\n   Per-PR entries that are too small for a chapter go in the montage. Check\n   that each claim is accurate: a Patch-level fix can earn a chapter, but name\n   what it does rather than calling it new. Confirm the shortlist with the\n   user before capturing.\n2. **Start the storyboard** by copying the newest one, for example\n   `cp storyboards/0.24.0.mjs storyboards/0.25.0.mjs`. Set `version`,\n   rewrite `chapters` and `montage`, set `montage.fixes.count` from the\n   changelog's patch entries, and point `contributors.from`/`to` at the\n   previous release tag and this release's tag. Leave `aliases` from older\n   releases out unless they still apply.\n3. **Capture** each chapter's keyframes, reusing or adding scenes in\n   `capture.ts` (see \"Authoring scenes\"); every `frames[].img` must name a\n   captured keyframe. `SCENES=a,b bun run scripts/launch-video/capture.ts`.\n4. **Credits.** Fetch tags and history (`git fetch --tags --unshallow` in a\n   shallow clone), then run\n   `node scripts/launch-video/promo/contributors.mjs .video-work`. Review the\n   printed table: everyone who authored or co-authored a commit in the range\n   appears once, bots and AI co-author trailers excluded. Map unresolved\n   people to logins with `contributors.aliases` (email or name to a login, or\n   `{ login, id }` when the GitHub users API is unavailable) and rerun; anyone\n   still unresolved is credited by name with an initials tile. Never guess an\n   account.\n5. **Work dir.** Besides `playwright-core` (see gotchas), the kit needs the\n   brand font: `cd .video-work && bun add @fontsource-variable/jetbrains-mono`.\n6. **Stills first.** `node scripts/launch-video/promo/render.mjs .video-work\n--stills 2.6,8,14,...` renders review frames in seconds; check every\n   chapter's callouts and captions before the full render. `render.mjs`\n   validates the storyboard and lists any missing keyframes up front.\n7. **Render** with `node scripts/launch-video/promo/render.mjs .video-work`\n   (about 3 minutes for a minute of video) and verify the cut (see\n   \"Verification\"). When `hunk-release` invoked this workflow, return the\n   approved MP4 to that skill for versioned naming and embedding; do not upload\n   or edit the public release without its confirmation gate.\n\n### Chapter reference\n\nTimes are seconds from the start of the chapter's terminal scene; camera,\nhighlight, and label coordinates are fractions of the captured frame.\n\n- `label`, `title` (lines; prefix a word with `*` to color it), `sub`, `cmd`\n  (typed into the title card's command chip), `winTitle`.\n- `frames: [{ img, dur, key?, fade? }]` plays keyframes in order. `key` shows\n  that keycap pressing as the frame appears, so one frame per keypress reads\n  as typing; `fade` crossfades from the previous frame (use it for layout\n  swaps). The `steps()` helper in the 0.24.0 storyboard builds key-walk runs.\n- `cameras: [[t, { x, y, scale }, motionSeconds?]]` must start at time 0.\n- `highlights: [{ t0, t1, x, y, w, h }]` and `labels: [{ t0, t1, x, y, text }]`\n  outline and annotate source regions; keep edges in blank rows and labels off\n  terminal text (see \"Camera and callout composition\").\n- `captions: [[t, html]]` with `<b>` for the accent color.\n- `bodyH` gives short shell output a compact window instead of a full screen.\n\n### Extending the style\n\nKeep release copy out of `engine.js`: it reads everything release-specific\nfrom the storyboard, and its defaults hold only brand constants (tagline,\ninstall command, site). When a release needs something the kit cannot show,\nadd a reusable scene type or storyboard field to the engine and document it\nhere rather than special-casing one release. The brand geometry in the engine\nfollows `website/public/brand/`; update both together.\n\nThe caption storyboard below (`compose.mjs` on the shared stage) remains the\ntool for single-feature demos, PR evidence, and custom explainers.\n\n## Per-video editorial surface\n\nThis section covers the caption storyboard used for single-feature, PR, and\ncustom videos; release promos keep their content in `promo/storyboards/`. The\ncapture machinery is reusable, but the storyboard is editorial content for one\nvideo. Rewrite it to match the video's scope. The checked-in reference is the\n0.24 release roundup, not a frozen release artifact:\n\n- `compose.mjs`: the whole `SHOTS` table; opening, feature, and outro cards;\n  every caption; camera targets; and callout rectangles. A `NEW` badge is a\n  claim about the video at hand, so drop or move it as features age.\n- `capture.ts`: `SCENES` selects from a reusable scene library. With no\n  override it captures only the current storyboard's default scene; update\n  that default whenever the canonical storyboard changes. Hunk-side glue\n  (`launchHunk`, `launchHunkShell`, demo repositories, and the keyboard probe)\n  remains reusable.\n\nReusable machinery lives in `@hunk/term-video` (`packages/term-video/`) —\nextend it there, don't fork it into the scripts: `createKeyframer`,\n`launchApp`/`launchShell`, `createCommandWrapper`, `typeCommand`,\n`ensureKeyboardIsLive`, `makeSceneFilter` (`src/capture.ts`); the unit-tested\nstoryboard planner with the caption/timing semantics (`src/plan.mjs`);\n`composeStoryboard` with font/Chromium resolution and the missing-keyframe\npreflight (`src/compose.mjs`); the stage template (`src/stage.html`); and\n`renderTimeline` for free-form motion pages such as the promo kit\n(`src/timeline.mjs`).\n\n## Environment gotchas\n\nSandbox-specific bullets are marked; each cost real debugging time.\n\n- **Run `capture.ts` with bun from the repo root.** tuistory uses subpath\n  self-imports (`tuistory/pty`) that only resolve inside this repo's\n  `node_modules`; running the script from elsewhere resolves tuistory from\n  bun's global cache and crashes. (Output location is unaffected — it defaults\n  to `<repo>/.video-work/` via `import.meta.url`.)\n- **ghostty-opentui is a transitive dep** (via tuistory) with an exports map:\n  import `\"ghostty-opentui/image\"` (not `.../dist/image.js`), resolved\n  relative to tuistory — `@hunk/term-video/capture`'s `createKeyframer` does\n  the `Bun.resolveSync` dance.\n- **Playwright must match the Chromium it drives.** The portable setup above\n  installs `playwright` and `playwright-core` together, then downloads their\n  matching Chromium build. It works on macOS and Linux and is preferred when\n  bandwidth and browser downloads are available; leave `CHROMIUM_PATH` unset\n  so Playwright uses that managed browser. On Linux, if Chromium reports\n  missing system libraries, run `bunx playwright install-deps chromium` (it\n  may require sudo) before retrying.\n\n  To reuse an existing system, CI, or sandbox Chromium instead, install the\n  `playwright-core` version provided by that environment and set its executable\n  explicitly:\n\n  ```sh\n  cd .video-work\n  bun add playwright-core@<matching-version>\n  cd ..\n  CHROMIUM_PATH=/path/to/chromium node scripts/launch-video/compose.mjs .video-work\n  ```\n\n  Common executable locations include `$(command -v chromium)` or\n  `$(command -v google-chrome)` on Linux and\n  `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` on macOS.\n  In an environment with a preinstalled Playwright toolchain, read that\n  toolchain's `package.json` to get the exact driver version. For example, the\n  Anthropic sandbox exposes it through `/opt/pw-browsers/.links/*`:\n\n  ```sh\n  cat \"$(cat /opt/pw-browsers/.links/* | head -1)/package.json\" | grep '\"version\"'\n  # e.g. \"1.56.1\" -> bun add playwright-core@1.56.1\n  ```\n\n  `compose.mjs` picks its browser as `$CHROMIUM_PATH`, then\n  `/opt/pw-browsers/chromium` when present, then Playwright's managed browser.\n  If a dependency update leaves Playwright asking for an uninstalled browser\n  revision, either rerun `bunx playwright install chromium` in `.video-work/`\n  or set `CHROMIUM_PATH` to a known system browser such as `/usr/bin/chromium`.\n\n- **Give `.video-work/` its own `package.json` before `bun add`.** Without\n  one, bun walks up and installs into the repo's `package.json` — revert with\n  `git checkout package.json bun.lock` if that happens.\n- **Chromium needs `--allow-file-access-from-files`** (already in\n  `compose.mjs`): the stage samples each keyframe through a canvas to\n  color-match the window background, and file:// images taint the canvas\n  without it.\n- **mp4 needs an ffmpeg with libx264.** Sandbox: `apt-get install ffmpeg`\n  (run `apt-get update` first if packages 404). macOS: `brew install ffmpeg`.\n  Verify `ffmpeg -encoders | grep -E 'libx264|libvpx-vp9'` shows both before\n  encoding — playwright's bundled `ffmpeg-*/ffmpeg-linux` only does VP8/WebM\n  and cannot produce the mp4.\n- **Caption font**: JetBrains Mono ships inside ghostty-opentui;\n  `findCaptionFont` in `packages/term-video/src/compose.mjs` searches bun's\n  isolated layout (`node_modules/.bun/node_modules/…`) then a hoisted\n  `node_modules/…`. If it still throws `caption font not found`, locate the\n  file with `find node_modules -name jetbrains-mono-nerd.ttf` and pass it as\n  `fontPath` to `composeStoryboard`.\n\n## Authoring scenes (capture.ts)\n\n- Shared geometry is 140x32 cells rendered at fontSize 16 / dpr 2 → 2688x1536\n  PNGs. Keep every scene at this size so all frames fit one window.\n- Keep time-sensitive fixtures recent and timezone-stable. The canonical\n  history scene derives commit dates from the current UTC day and launches\n  Hunk with `TZ=UTC`, so day groups and relative-age labels remain useful on\n  future runs. Preserve that policy in custom history scenes.\n- Helpers: `createDemoRepo()` and `launchHunkShell()` are hunk-side glue in\n  the script (git repo built from `examples/2-mini-app-refactor`; interactive\n  bash with a real `hunk` command on PATH and a clean `❯` prompt); `snap`,\n  `typeCommand`, `launchApp`/`launchShell`, and `createCommandWrapper` come\n  from `@hunk/term-video/capture`.\n- Always `waitForText` on scene-specific content before the first snap, and\n  call `ensureKeyboardIsLive()` before scripted keypresses — the first key\n  after startup can be dropped (real race, the helper toggles `?` to prove\n  keys land).\n- **Animation = one snap per keypress.** Cursor walks and typing effects are\n  just every `j`/`k`/character captured as its own frame and played back at\n  0.2–0.3s per frame. Prefer this over sparse keyframes: three stills read as\n  a slideshow, per-press frames read as motion.\n- `renderTerminalToImage` auto-trims trailing blank rows, so short outputs\n  (CLI scenes) produce short PNGs — the stage handles this by sampling the\n  image's bottom-left pixel and painting the window body to match.\n- `manifest.json` is a capture-side inventory of the _current run_ only;\n  `compose.mjs` ignores it (frames resolve by name from `SHOTS`), and after a\n  `SCENES=` run it is partial while `frames/` stays cumulative.\n- Demo content that must exist: STML notes come from\n  `examples/9-agent-markup-notes` (launch with `--experimental`), extension\n  scenes from `examples/extensions/` loaded via `--extension <path>` (explicit\n  paths skip the repo trust prompt). The pager pipe is `git diff | hunk pager`\n  — bare `hunk` on piped stdin prints help. Sidebar toggle is `s`; comment\n  draft is `c`, save with Ctrl+S (`\\x13`).\n\n## Storyboard model (compose.mjs)\n\n- `SHOTS` is the whole edit: one entry per shot, `dur` in seconds, played as\n  unique frames + per-frame durations in an ffmpeg concat list (holds cost one\n  frame, so runtime is dominated by transitions, not length).\n- `capKey` is caption identity: the caption slides in only when `capKey`\n  changes, and continuation shots that share a `capKey` without restating\n  `caption` keep the previous caption on screen. Sequences (walks, typing) are\n  generated with `Array.from` spreads.\n- **Sequence lengths must match capture loop bounds**: the open capture's\n  four-step tree walk plus its select and expand presses produce\n  `open-tree-0..5`, consumed by a six-entry `Array.from` spread in `SHOTS`.\n  Change one side and the other breaks — the preflight check names any frame\n  that's missing.\n- `enter: true` fades/scales the surface in — use it for cards and the first\n  terminal shot only.\n- Terminal shots may set normalized `camera: { x, y, scale }` targets and\n  `highlight: { x, y, width, height, label? }` rectangles. `motion` controls\n  the transition length in seconds. The planner interpolates camera changes\n  and outline geometry, while the stage keeps the camera inside the captured\n  image and paints an animated glow around the source-aligned region. Set the\n  same `cameraKey` or `highlightKey` on related keyframes when their normalized\n  coordinates share one capture geometry. Cross-image camera changes cut to\n  their target unless `cameraKey` explicitly permits a pan, and outlines never\n  leak across unrelated images.\n- Caption HTML vocabulary: `<span class=\"badge\">NEW</span>` amber pill,\n  `<span class=\"hl\">` amber highlight, `<span class=\"dim\">` muted. Cards use\n  `badge` / `h1`/`h2` / `sub` / `cmds`+`cmd` / `foot` classes from\n  `packages/term-video/src/stage.html`.\n- Target pacing: money shots hold 3–4s, context shots 2–3s, typing/walk frames\n  0.2–0.6s; keep the total near 60s.\n\n## Camera and callout composition\n\nTreat `camera` and `highlight` as one composition. Both use normalized source\ncoordinates, but the stage projects highlights through the active camera. A\nrectangle that fits within the source can still lose an edge after zooming.\n\nUse this framing process:\n\n1. Start with a full-frame establishing shot, then pan or zoom to one readable\n   subject. Use `motion` around 0.7–0.9s for a deliberate move rather than a\n   cut disguised as animation.\n2. Fit the target plus its outline inside the camera viewport. Leave about 1%\n   of the visible width on each side and at least one terminal row above and\n   below the meaningful content. If that does not fit, reduce `camera.scale`.\n3. Put outline edges in actual blank rows. Mathematical padding can move a\n   border onto a neighboring date heading, status row, or metadata line.\n4. Check both sides of the capture. A zoom that preserves left-aligned subjects\n   can still crop hashes, authors, or status text aligned to the right.\n5. Give irregular states explicit rectangles. A growing commit range that\n   crosses day separators should use measured per-keyframe heights instead of\n   assuming every added item occupies the same vertical distance.\n6. Share `cameraKey` or `highlightKey` only when captures use the same source\n   geometry. This allows pans and outline morphs across related captures;\n   unrelated keyframes cut or fade separately.\n\nThe outline should direct attention, not become the subject. Use one callout at\na time, keep labels short, and remove the outline once the viewer has enough\ncontext to follow the interaction unaided.\n\n## Command and output legibility\n\nTerminal commands and their results must be readable at normal playback size.\nNever rely on a full-width terminal prompt or output region as the only way the\nviewer can understand a command scene; text that looks acceptable in a 1080p\nsource frame is often illegible in an embedded player or social feed.\n\nUse this default treatment:\n\n1. **Present the exact command in large type.** The easiest treatment is a\n   `cmd` card or an oversized editorial callout immediately before the real\n   terminal result. A typing animation may remain for motion, but it does not\n   replace the large command treatment.\n2. **Zoom the real output.** Crop or scale the captured terminal frame around\n   the meaningful result so both the command and the important output lines\n   are comfortably readable. A brief full-window establishing shot is fine,\n   but the result's main hold must use the focused view.\n3. **Keep the evidence real.** Never fabricate terminal output. Large command\n   text may reproduce the exact command as an editorial overlay; any enlarged\n   output must come from the captured PTY frame. If reusable crop/zoom controls\n   are missing, add them to `@hunk/term-video` rather than baking one-off image\n   edits into a storyboard.\n4. **Trim before shrinking.** Prefer fewer relevant lines and a tighter crop\n   over fitting a long transcript into the frame. Split a workflow across\n   multiple focused shots when one crop cannot keep every important line\n   legible.\n\nTreat command legibility as a release gate: if the command or its result cannot\nbe read when the 1920x1080 video is displayed at 50% size, revise the shot.\n\n## Content accuracy (learned the hard way)\n\n- **Verify install commands against reality**, not the README: check\n  `npm view hunkdiff dist-tags`. A prerelease needs `npm i -g hunkdiff@beta`;\n  `brew install hunk` only serves stable (homebrew-core Autobump, lags npm) —\n  omit brew on prerelease cards.\n- **Label demo extensions as examples.** The triage board, CSS palette, and\n  semver views are `examples/extensions/`, not shipped features — caption them\n  with a dimmed `example:` prefix. The real features are the APIs (sidebars,\n  file views, commands, dialogs).\n- Window titles are decorative but must not lie: the shell scenes run bash,\n  so keep their titles generic (`shell — …`) rather than naming a shell the\n  capture doesn't launch.\n- STML requires `--experimental`; say so on the outro card.\n- The video is silent — never imply audio in the video or its announcement copy.\n\n## Verification and delivery\n\n- Eyeball keyframes in `.video-work/frames/` (Read renders PNGs) after\n  capture — especially new scenes — before compositing.\n- Inspect every command scene at 50% display size. Confirm the exact command is\n  shown in large type and the meaningful real output is zoomed tightly enough\n  to read; a readable caption does not compensate for unreadable terminal text.\n- Composite and encode the MP4 first. Inspect it before spending time on the\n  slower VP9/WebM encode; produce both final formats only after the visual pass.\n- After encoding, return to the repository root, set `VIDEO` to the produced\n  MP4, and inspect a settled frame for each callout, a mid-animation point,\n  each new scene, and the outro:\n\n  ```sh\n  VIDEO=.video-work/hunk-feature-demo.mp4 # or .video-work/launch.mp4\n  ffmpeg -y -ss 2 -i \"$VIDEO\" -frames:v 1 -update 1 .video-work/check.png\n  ffprobe -v error -show_entries format=duration,size \\\n    -show_entries stream=codec_name,width,height,r_frame_rate \\\n    -of default=noprint_wrappers=1 \"$VIDEO\"\n  ```\n\n  Captions must persist through extracted animation frames. Every callout must\n  show all four borders, keep its label clear of terminal text, leave visible\n  padding around its subject, and avoid clipping right-aligned metadata.\n\n- Compare the storyboard planner's reported duration with `ffprobe`; they\n  should agree within one frame. The concat manifest declares the PNG input\n  rate so 30 fps animation timestamps stay exact. Investigate larger drift\n  before delivery.\n\n- Outputs stay under `.video-work/`: the full-release recipe creates\n  `launch.mp4`/`launch.webm`, while the single-feature recipe above creates\n  `hunk-feature-demo.mp4`/`hunk-feature-demo.webm`. `.video-work/` is\n  gitignored — never commit the video or its frames. For a standalone video\n  request, send both files to the user directly (mp4: social/Slack; webm: web\n  embeds), report duration and file sizes, and flag if the mp4 exceeds ~10 MB\n  (Slack) or ~15 MB (X). When invoked by `hunk-release`, hand both files back\n  to that workflow instead; it owns versioned filenames, GitHub's\n  user-attachment limit, public embedding, and inline-player verification.\n  Copy them elsewhere only if the user names a destination.\n\n<!-- File: skills/hunk-release/SKILL.md (Tokens: ~3556 | Category: anthropic-skill) -->\n---\nname: hunk-release\ndescription: Prepares, publishes, verifies, and curates Hunk releases. Use for release metadata, benchmarks, tags, publishing, release videos, backports, or recovery.\nmetadata:\n  internal: true\n---\n\n# Hunk release workflow\n\nMaintainer-focused and source-checkout only. The tag workflow publishes `hunkdiff` plus five platform packages, attests the binary archives, and creates the GitHub release.\n\n## Safety\n\n- Ask when the version, release branch, previous tag, or channel is ambiguous.\n- Get explicit confirmation before pushing a tag, triggering publication, or editing a public release.\n- Never reuse an npm version or move a tag after publication.\n- Never bypass a benchmark regression without an approved, recorded reason.\n- Never retry a partial publish before inventorying every package and artifact.\n\n## 1. Confirm the release\n\nRecord the version, tag, branch, previous tag, and expected channel:\n\n- newer stable: npm `latest` and GitHub Latest;\n- prerelease: npm `beta`, not GitHub Latest;\n- older-series backport: npm `backport-X.Y`, leaving both latest pointers unchanged.\n\nFor backports, include only commits present between the previous tag and the release tip on that maintenance branch. Inspect `.changeset/pre.json` before changing prerelease state.\n\n## 2. Prepare\n\nStart from a clean, current release branch:\n\n```sh\ngit status --short --branch\ngit fetch origin --tags --prune\ngit tag --sort=-version:refname | head -10\nbun install --frozen-lockfile\nbun run changeset:status\n```\n\nRead the pending Changesets, then generate metadata and the published release notes:\n\n```sh\nbun run release:version\nbun run generate:changelog\ngit diff -- package.json bun.lock packages CHANGELOG.md .changeset website\n```\n\nVerify the intended versions, consumed Changesets, and new changelog section. Do not force a bump by hand-editing generated versions.\n\n`generate:changelog` projects `CHANGELOG.md` into `hunk.dev/changelog`. Never hand-edit its output under `website/src/content/docs/changelog/`, `website/public/changelog/`, `website/releases/dates.json`, or `website/releases/latest.json`. For a minor or major release, add the hand-authored parts to `website/releases/notes.json` under the `major.minor` key and regenerate:\n\n- `summary` — one sentence; replaces the changelog's `### Highlights` lead paragraph on the page.\n- `tagline` — short phrase for the landing-page release ribbon.\n- `links` — docs pages the highlights describe, rendered as \"Related documentation\".\n- `video` — `mp4` plus optional `webm`, `poster`, `duration`, and `title`, filled in at step 5.\n\nThe release tag does not exist yet at this point. A stable version is rendered as `Unreleased` without an install command, while an undated prerelease remains off the site entirely; the tag date is picked up by the next generation. Recorded dates in `website/releases/dates.json` are never recomputed, which is what lets `check:changelog` gate CI without Git tags.\n\nGenerate and compare the committed release benchmark:\n\n```sh\nbun run bench:release\nbun run bench:release:compare\n```\n\nA material regression blocks the release unless the user approves an `acceptedRegressions` entry following `benchmarks/release/README.md`.\n\nRun the validation required by `AGENTS.md`, plus the release packaging checks:\n\n```sh\nbun run check:docs\nbun run check:changelog\nbun run check:pack\nbun run build:prebuilt:npm\nbun run check:prebuilt-pack\nbun run smoke:prebuilt-install\n```\n\nCommit the generated metadata and `benchmarks/release/bench-X.Y.Z.json`, then follow normal review\npolicy. The Firecracker evidence must come from that reviewed release tip, not the pre-generation\ncommit. Push the reviewed tip before using the manual workflow.\n\nRun the full Firecracker install compatibility suite once from the clean reviewed release tip on a\nLinux x64 host with working KVM, either locally or through the manually dispatched\n`install-vm.yml` workflow:\n\n```sh\nset -euo pipefail\nmkdir -p tmp/install-vm/runs\nresult_dir=$(mktemp -d tmp/install-vm/runs/release-XXXXXXXX)\nbun run test:install-vm -- --output \"$result_dir\"\nbun run ./test/cli/install-vm/validate-release-result.ts \"$result_dir/result.json\"\n```\n\nThe explicit output directory prevents a failed invocation from falling back to stale evidence. The\nvalidator requires the complete checked-in scenario manifest, passing statuses, and the current\ncheckout's source identity. A skipped result does not satisfy release validation. For the manual\nworkflow, dispatch the full suite from the reviewed tip, download its `result.json` beneath the\nignored `tmp/install-vm/` directory of a checkout at that exact tip, and run the validator there; a\ngreen job alone is insufficient because unsupported runners may use the intentional skip path.\nFirecracker validates Linux x64 packaging behavior, while the existing native release jobs remain\nresponsible for macOS, Windows, and other architectures. Wait for required CI before continuing.\n\n## 3. Tag and publish\n\nImmediately before tagging, fail unless the reviewed branch tip is clean and matches its remote:\n\n```sh\nset -euo pipefail\nbranch=$(git branch --show-current)\ntest -n \"$branch\"\ngit fetch origin \"$branch\" --tags\ntest \"$(git rev-parse HEAD)\" = \"$(git rev-parse \"origin/$branch\")\"\nif [ -n \"$(git status --porcelain --untracked-files=all)\" ]; then\n  git status --short\n  exit 1\nfi\ngit log -1 --format='%H %s'\n```\n\nPresent the tag, commit, benchmark result, validation, and release highlights. After explicit confirmation:\n\n```sh\nversion=X.Y.Z\ntag=\"v$version\"\ngit tag -a \"$tag\" -m \"$tag\"\ngit push origin \"$tag\"\n```\n\nThe tag push starts `.github/workflows/release-prebuilt-npm.yml`. Find only the run for that tagged commit and propagate failure:\n\n```sh\nrelease_sha=$(git rev-parse \"$tag^{}\")\nrun_id=$(\n  gh run list \\\n    --workflow release-prebuilt-npm.yml \\\n    --event push \\\n    --commit \"$release_sha\" \\\n    --limit 1 \\\n    --json databaseId \\\n    --jq '.[0].databaseId // empty'\n)\ntest -n \"$run_id\"\ngh run view \"$run_id\" --json url,status,conclusion\ngh run watch \"$run_id\" --exit-status\n```\n\nIf lookup is empty, repeat it after GitHub registers the run; never select an unrelated run. Success requires the benchmark, all builds, staging smoke test, npm publish, attestation, and GitHub release jobs.\n\n## 4. Verify publication\n\nCheck the meta-package, platform packages, dist-tags, release body, and five binary archives:\n\n```sh\nnpm view \"hunkdiff@$version\" version\nfor package in \\\n  hunkdiff-darwin-arm64 hunkdiff-darwin-x64 \\\n  hunkdiff-linux-arm64 hunkdiff-linux-x64 \\\n  hunkdiff-windows-x64; do\n  npm view \"$package@$version\" version\ndone\nnpm view hunkdiff dist-tags --json\ngh release view \"$tag\" --json tagName,name,isPrerelease,url,assets,body\n```\n\nStop if versions, channel, tag, archives, or attestations disagree.\n\nThen record the tag date and publish the release notes. The published page is what the update notice and the GitHub release body point at, so it is part of the release, not follow-up work:\n\n```sh\nbun run generate:changelog\nbun run generate:og\ngit diff --stat -- website\n```\n\nThis backfills the new tag's date, publishes its notes, and redraws the social cards whose contents changed. Stable releases also add the install command and move the landing-page ribbon; prereleases publish a clearly labeled series without advancing either stable surface. Commit it to `main` — the diff should only touch `website/releases/`, `website/src/content/docs/changelog/`, `website/public/changelog/rss.xml`, and the redrawn cards under `website/public/changelog/og/`. Run this for backports too: they publish into their older series without advancing the latest stable release.\n\nPrereleases publish to `hunk.dev/changelog` after their tag exists. A beta contributes its series page, exact version anchor, index row, feed item, and social card, while the stable latest marker, landing ribbon, and default install instructions continue to name the newest stable release.\n\n## 5. Add the release video and final notes\n\nOnly after publication verifies, create a detached worktree at the released tag and follow `skills/hunk-launch-video/SKILL.md`'s full-release recipe:\n\n```sh\ngit worktree add --detach \"../hunk-release-video-$version\" \"$tag\"\n```\n\nThat skill owns capture, encoding, and media checks; release videos use its promo style kit, with this release's content in a new `scripts/launch-video/promo/storyboards/<version>.mjs`. Keep generated media out of Git and Git LFS. Commit the release's storyboard file and any capture scenes it needs only with separate approval, so the next release can start from them.\n\nPublish the video so the release page can carry it, then record it in `website/releases/notes.json` under the series' `video` key and regenerate. Host it where the site can serve it rather than committing it: the 1080p master stays out of Git and Git LFS.\n\nDraft the final body from the released changelog and actual branch diff. Replace GitHub's generated PR inventory with editorial notes that help someone decide whether to install the release:\n\n- Open with one short paragraph describing the release's product theme and user impact.\n- Group a small number of meaningful changes under descriptive headings. Explain what users can now do; do not restate commit titles or reproduce the changelog.\n- Include both upgrade and npm installation instructions using the exact release: `hunk update <version>` for npm- and curl-managed installs and `npm install -g hunkdiff@<version>` for npm installs or first-time npm users. Homebrew cannot select an exact version: tell its users to run plain `hunk update` only after the Homebrew readiness gate below passes, or label Homebrew propagation as pending.\n- Add a clearly labeled compatibility section for runtime requirements, changed CLI interpretation, extension API variants, migrations, or other upgrade risks.\n- Add a **Community contributors** section that names every external contributor in the release, links each relevant PR, and briefly describes their contribution. Derive this from the actual release diff; do not limit acknowledgment to first-time contributors and do not bury contributors in an autogenerated list.\n- Preserve a complete PR inventory inside a collapsed GitHub `<details>` block after the editorial sections. Use one concise bullet per merged PR with its author and link, include maintenance work there, and verify the list against the actual previous-tag comparison. The collapsed inventory is for completeness; it does not replace the curated highlights or contributor acknowledgments.\n- Link the comparison from the actual previous tag and point every release at its `hunk.dev` series page, which carries the full notes, video, and docs links.\n- Keep maintenance-only PRs out of the editorial highlights unless they materially affect installation, compatibility, security, or performance.\n\nUse this shape rather than a flat `What's Changed` list:\n\n````md\n## <release name or product theme>\n\n<one short release summary>\n\n```sh\nhunk update <version>\nnpm install -g hunkdiff@<version>\n```\n\nhttps://github.com/user-attachments/assets/<video-id>\n\n### <user-facing theme>\n\n<what changed, why it matters, and links to the defining PRs>\n\n### Compatibility notes\n\n- <upgrade requirement or changed behavior>\n\n### Community contributors\n\n- [@contributor](https://github.com/contributor) <concise contribution>. [#123](PR URL)\n\n<details>\n<summary>All merged pull requests</summary>\n\n- <concise PR title> by @author in [#123](PR URL)\n\n</details>\n\n**Release notes**: https://hunk.dev/changelog/<major.minor>/\n**Full changelog**: https://github.com/modem-dev/hunk/compare/<previous-tag>...<new-tag>\n````\n\nPresent the local MP4 and draft notes for explicit confirmation. Then attach the H.264 MP4 in GitHub's release editor, replace the placeholder with its generated user-attachment URL, and apply the reviewed body:\n\n```sh\ngh release edit \"$tag\" --notes-file /tmp/hunk-release-notes.md\n```\n\nOpen the public release in a browser and verify inline playback, final notes, and the unchanged five immutable binary archives.\n\n## 6. Distribution channels\n\nOnly stable releases that advance `latest` should propagate to Homebrew and mise. Let Homebrew Autobump update `Homebrew/homebrew-core`; use `brew bump-formula-pr` only if maintainers request it or Autobump stalls.\n\nTreat Homebrew availability as an announcement check only. Never delay or fail the tag, npm publication, GitHub release, changelog, release notes, video, or other release completion work because Homebrew is pending. Homebrew detects the published release on its own schedule and merges a separate formula PR. Immediately before an external promotional announcement that names Homebrew or recommends `hunk update` without qualifying the install method, read the live formula API and require its stable version to match:\n\n```sh\nversion=X.Y.Z\nformula_version=$(\n  curl -fsSL https://formulae.brew.sh/api/formula/hunk.json |\n    bun -e 'console.log(JSON.parse(await Bun.stdin.text()).versions.stable)'\n)\nprintf 'Homebrew formula: %s (release: %s)\\n' \"$formula_version\" \"$version\"\ntest \"$formula_version\" = \"$version\"\n```\n\nIf the check fails, still mark the software release complete, but hold the external promotional announcement unless it explicitly says that Homebrew is still propagating and offers npm or the install script meanwhile. Check for an open or merged Autobump PR:\n\n```sh\ngh api --method GET /search/issues \\\n  -f q=\"repo:Homebrew/homebrew-core is:pr in:title \\\"hunk $version\\\"\" \\\n  --jq '.items[] | [.html_url, .state, .title] | @tsv'\n```\n\nDetermine the next expected Autobump scan from Homebrew's live workflow rather than relying on a remembered cadence. This command prints the current cron expression and, while Homebrew retains its present three-hour schedule, the next nominal UTC scan. Scheduled GitHub Actions can start late, so report this as an estimate rather than an availability promise:\n\n```sh\nautobump_workflow=$(\n  gh api repos/Homebrew/homebrew-core/contents/.github/workflows/autobump.yml \\\n    --jq .content | base64 --decode\n)\nautobump_cron=$(\n  printf '%s\\n' \"$autobump_workflow\" |\n    awk -F'\"' '/^[[:space:]]*- cron:/ { print $2; exit }'\n)\nprintf 'Homebrew Autobump schedule: %s\\n' \"$autobump_cron\"\nAUTOBUMP_CRON=\"$autobump_cron\" bun -e '\n  const cron = process.env.AUTOBUMP_CRON;\n  if (cron !== \"45 1-23/3 * * *\") {\n    console.error(\"Homebrew changed its Autobump schedule; inspect the workflow before announcing.\");\n    process.exit(1);\n  }\n  const next = new Date();\n  next.setUTCSeconds(0, 0);\n  do next.setUTCMinutes(next.getUTCMinutes() + 1);\n  while (next.getUTCMinutes() !== 45 || (next.getUTCHours() - 1) % 3 !== 0);\n  console.log(`Next nominal Homebrew Autobump scan: ${next.toISOString()}`);\n'\n```\n\nAfter the formula version matches, run `brew update`, verify `brew info hunk` reports the release, and only then describe Homebrew as available. Verify mise independently against fresh registry data:\n\n```sh\nMISE_AQUA_BAKED_REGISTRY=false mise latest hunk\n```\n\nUse `mise cache clear` only when cached registry data is stale. Do not claim Homebrew or mise support for prereleases or older-series backports.\n\n## Failure invariants\n\n- Before tag push: fix, regenerate, validate, and request confirmation again.\n- After any npm publication: keep the version and tag; inventory all six packages before recovery.\n- Keep a verified software release intact when video or notes fail; retry only the approved media/edit step.\n\n<!-- File: website/src/content/docs/docs/agents/agent-context-and-stml.md (Tokens: ~1009 | Category: subagent-persona) -->\n---\ntitle: Agent context and STML\ndescription: Load review notes from a JSON sidecar and opt into experimental rich note markup.\n---\n\n[Live agent comments](/docs/agents/comments-and-annotations/) are the recommended workflow. A sidecar file is useful when the notes already exist before Hunk starts, or need to travel with a patch.\n\n## Load a JSON sidecar\n\n```bash\nhunk diff --agent-context notes.json\nhunk patch change.patch --agent-context notes.json\n```\n\nUse `--agent-context -` to read the sidecar from stdin. With `--watch`, Hunk also reloads when the sidecar file changes, so an agent can rewrite the file and the open review updates; a stdin sidecar cannot be watched.\n\nSidecar annotations are agent notes, so they start hidden. Press `a` to show them, or launch with `--agent-notes`.\n\n## Sidecar format\n\n```json\n{\n  \"version\": 1,\n  \"files\": [\n    {\n      \"path\": \"src/search.ts\",\n      \"summary\": \"Scores and sorts matches instead of returning the first substring hit.\",\n      \"annotations\": [\n        {\n          \"newRange\": [15, 35],\n          \"summary\": \"Prefix and exact matches now outrank weaker substring hits.\",\n          \"rationale\": \"The old behavior made every match look equally good.\",\n          \"author\": \"sonnet\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nTop level:\n\n| Field     | Type   | Notes                                                                            |\n| --------- | ------ | -------------------------------------------------------------------------------- |\n| `version` | number | Optional; defaults to `1`.                                                       |\n| `summary` | string | Optional changeset summary. Extensions can read it; the TUI does not display it. |\n| `files`   | array  | One entry per file you want to describe.                                         |\n\nEach `files[]` entry:\n\n| Field         | Type   | Notes                                                                         |\n| ------------- | ------ | ----------------------------------------------------------------------------- |\n| `path`        | string | Required. Matches the file's current path, or its previous path for a rename. |\n| `summary`     | string | Optional. Used by the file filter, not shown as a card.                       |\n| `annotations` | array  | Optional notes for this file.                                                 |\n\nEach `annotations[]` entry:\n\n| Field                                             | Type           | Notes                                                                                                                  |\n| ------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------- |\n| `summary`                                         | string         | Required. The note's headline.                                                                                         |\n| `newRange` / `oldRange`                           | `[start, end]` | Optional 1-based, inclusive line range on the new or old side. A note with no range attaches to the file's first hunk. |\n| `rationale`                                       | string         | Optional longer explanation shown under the summary.                                                                   |\n| `author`                                          | string         | Optional label for the card, such as `sonnet note`.                                                                    |\n| `markup`                                          | string         | Optional [STML](#opt-into-stml) body. Ignored unless the session launched with `--experimental`.                       |\n| `id`, `tags`, `confidence`, `source`, `createdAt` | various        | Optional metadata kept with the note. `confidence` is `low`, `medium`, or `high`.                                      |\n\nFiles listed in the sidecar appear first in the review, in sidecar order; other changed files follow in their usual order. Hunk rejects a sidecar with a missing `summary` or `path`, or a range whose end is before its start.\n\nKeep it concise: short file summaries and only rationale that improves the review. A fuller example lives at [`examples/3-agent-review-demo/agent-context.json`](https://github.com/modem-dev/hunk/blob/main/examples/3-agent-review-demo/agent-context.json).\n\n## Opt into STML\n\nSTML is experimental markup for richer note bodies: boxes, rows, badges, lists, and code. It is off by default and only the launch flag turns it on:\n\n```bash\nhunk --experimental diff --agent-context notes.json\n```\n\nA reload cannot turn STML on later, and there is no config key for it. Every note still needs a plain `summary`, which is the fallback whenever markup is unavailable. Without `--experimental`, a sidecar's `markup` is ignored and a live `--markup` comment is rejected.\n\nBefore sending markup, an agent should check support and width:\n\n```bash\nhunk session context --repo . --json\nhunk markup guide\nhunk markup render - --width <reported-noteMarkupWidth>\n```\n\nOnly send `--markup` when `experimentalFeatures` includes `stml`. `hunk markup guide` prints the tag reference and copy-paste patterns. Keep colors symbolic and markup compact so notes stay close to their code.\n\n<!-- File: website/src/content/docs/docs/agents/comments-and-annotations.md (Tokens: ~1013 | Category: subagent-persona) -->\n---\ntitle: Comments and annotations\ndescription: Attach human or agent review notes to the code and navigate them in context.\n---\n\nNotes are hunk-specific and render beside the rows they explain. Hunk keeps them in the review flow rather than in a separate comments screen.\n\n## Kinds of notes\n\n| Term                   | What it is                                                                                           |\n| ---------------------- | ---------------------------------------------------------------------------------------------------- |\n| **Note**               | Any card shown beside the code.                                                                      |\n| **Agent comment**      | A note an agent adds to a live window with `hunk session comment`. This is the recommended path.     |\n| **Sidecar annotation** | A note loaded at launch from an [`--agent-context` JSON file](/docs/agents/agent-context-and-stml/). |\n| **Your note**          | A note you write in Hunk with `c`.                                                                   |\n| **Highlight**          | A temporary mark on a character range. It draws attention but is not a note.                         |\n\nAgent comments and sidecar annotations are both **agent notes**. They start hidden: press `a` to show or hide them, or launch with `--agent-notes`. Your own notes are always visible.\n\nNotes live in the Hunk window. Closing the window discards them.\n\n## Add one agent comment\n\n```bash\nhunk session comment add \\\n  --repo . \\\n  --file README.md \\\n  --new-line 103 \\\n  --summary \"Tighten this wording\" \\\n  --focus\n```\n\nChoose exactly one `--old-line` or `--new-line` target. `--focus` moves the user's viewport to the new note and turns agent notes on; use it for the first note in a session and when a note should actively steer the review.\n\nOptional fields:\n\n- `--rationale <text>`: a longer explanation shown under the summary.\n- `--author <name>`: the label on the note card, such as `Claude note` instead of `Agent note`.\n- `--markup <stml>`: an experimental rich body; see [Agent context and STML](/docs/agents/agent-context-and-stml/#opt-into-stml).\n\n## Apply a batch\n\n```bash\nprintf '%s\\n' '{\"comments\":[{\"filePath\":\"README.md\",\"newLine\":103,\"summary\":\"Tighten this wording\"}]}' \\\n  | hunk session comment apply --repo . --stdin --focus\n```\n\nEach item needs a `summary` and either:\n\n- `filePath` with exactly one target: `hunk`, `hunkNumber`, `oldLine`, or `newLine`, or\n- `replyTo` by itself, to reply to an existing note.\n\nItems also accept `rationale`, `author`, and `markup`. Hunk validates the complete batch before changing the live session. `--focus` jumps to the first note in the batch.\n\n## Reply to a note\n\nA reply inherits the parent note's file and line:\n\n```bash\nhunk session comment add --repo . --reply-to user:123 --summary \"Addressed in the latest revision\"\n```\n\n## Hand your notes to the agent\n\nYour notes are a way to ask the agent questions in context:\n\n1. Press `c` on a line and write the question.\n2. Ask your agent to read your notes. It runs `hunk session comment list --repo . --type user --json` or `hunk session review --repo . --include-notes --json`.\n3. The agent answers with `--reply-to <note-id>`, and the reply appears under your note.\n\nAgents can remove your notes during cleanup but cannot create or edit them.\n\n## Inspect and clean up\n\n```bash\nhunk session comment list --repo .\nhunk session comment list --repo . --type all\nhunk session comment rm --repo . <comment-id>\nhunk session comment clear --repo . --file README.md --yes\n```\n\n`comment list` shows agent comments by default. `--type` selects other notes:\n\n| `--type`         | Lists                                         |\n| ---------------- | --------------------------------------------- |\n| `live` (default) | Comments added through `hunk session comment` |\n| `user`           | Your notes                                    |\n| `ai`             | Sidecar annotations                           |\n| `agent`          | Agent-authored notes                          |\n| `all`            | Every note                                    |\n\n`comment clear` removes agent comments. Add `--all` (or its synonym `--include-user`) to clear your notes too. Clearing always requires `--yes`.\n\n## Add your own note\n\nIn the TUI, move to a line and press `c`, or click an add-note affordance. Press `Ctrl+S` to save or Escape to cancel. Human and agent notes are labeled by source. Press `{` and `}` to move through annotated hunks across the review stream.\n\n<!-- File: website/src/content/docs/docs/agents/live-session-control.md (Tokens: ~796 | Category: subagent-persona) -->\n---\ntitle: Live session control\ndescription: Inspect, target, navigate, and reload Hunk windows through the local session broker.\n---\n\nEach normal Hunk TUI registers with one loopback daemon. `hunk session ...` finds a registered window and sends it review actions.\n\n## Find the session\n\n```bash\nhunk session list\nhunk session get --repo .\nhunk session context --repo .\n```\n\nUse `--repo <path>` for normal worktrees. Use an explicit session ID when multiple windows share a repository.\n\n## Inspect without overloading context\n\n```bash\nhunk session review --repo . --json\n```\n\nThis returns files and hunks. Add flags only when required:\n\n```bash\nhunk session review --repo . --include-notes --json\nhunk session review --repo . --include-patch --json\n```\n\n## Navigate the visible window\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 . --next-comment\nhunk session navigate --repo . --comment <comment-id>\n```\n\nHunk numbers are 1-based. Absolute navigation needs a file and exactly one hunk, old-line, or new-line target. `--next-comment` and `--prev-comment` move between annotated hunks; `--comment` jumps to one note by the id that `comment list --json` returns.\n\n## Reload the review\n\nAlways place `--` before the nested Hunk command:\n\n```bash\nhunk session reload --repo . -- diff\nhunk session reload --repo . -- show HEAD~1 -- README.md\n```\n\nAdvanced reloads can target the live window by `--session-path` and load from a separate `--source` directory. Prefer `--repo` until those roles genuinely need to differ.\n\n## Security and access\n\nThe daemon listens on `127.0.0.1:47657` and refuses non-loopback addresses. Session commands authenticate with an owner-private credential that Hunk discovers automatically, so only your user account can control your windows; there are no credential flags to pass. The daemon starts with the first Hunk window and shuts down about a minute after the last one closes.\n\n| Variable                         | Effect                                                                                     |\n| -------------------------------- | ------------------------------------------------------------------------------------------ |\n| `HUNK_MCP_HOST`, `HUNK_MCP_PORT` | Move the daemon to another loopback host or port, for example to avoid a port conflict.    |\n| `HUNK_MCP_DISABLE=1`             | Start the TUI without registering a session.                                               |\n| `HUNK_MCP_UNSAFE_ALLOW_REMOTE=1` | Allow a non-loopback host. Anyone who can reach the port can attempt to connect; avoid it. |\n\nIf a visible Hunk window does not appear in `session list`, an agent sandbox may be blocking loopback access. Grant the agent local network access and retry; don't expose the daemon remotely. `hunk daemon serve` starts the daemon in the foreground for debugging.\n\n## After upgrading Hunk\n\nA daemon from the previous build keeps running while any older Hunk window is open. Until it is replaced:\n\n- a window from the new build shows a status-bar notice naming both builds, and\n- every `hunk session` command fails with a `daemon-build-mismatch` error (reported in-band under `--json`).\n\nTo fix it:\n\n1. Run `hunk daemon status` to see the daemon's build and its attached windows.\n2. Run `hunk daemon restart` and confirm. Windows from the new build reattach on their own.\n3. Relaunch any windows from the old build. Their notes are lost.\n\nAgents should report the mismatch and ask before restarting the daemon.\n\n<!-- File: website/src/content/docs/docs/agents/review-skill.md (Tokens: ~422 | Category: subagent-persona) -->\n---\ntitle: Hunk review skill\ndescription: Install the versioned skill that teaches coding agents Hunk's live review commands.\n---\n\nEvery Hunk installation bundles a `hunk-review` skill. It teaches agents how to find a live session, inspect the review efficiently, navigate, reload, and leave notes, and it tells them not to launch the interactive TUI themselves.\n\n## Install it\n\nInstall the skill with the `skills` CLI:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-review\n```\n\n`-g` installs the skill for your user instead of one project. Add `-a <agent>` (for example `-a claude-code`) to install it for a single agent.\n\nThe installed file is a pointer: it tells the agent to run `hunk skill show hunk-review` and follow the output. The instructions therefore come from the Hunk binary on your `PATH` and stay in step with it after every upgrade; you don't need to reinstall the skill.\n\n## Load it without the skills CLI\n\n| Agent setup                             | Command                                               |\n| --------------------------------------- | ----------------------------------------------------- |\n| Loads instructions on demand            | `hunk skill show` prints the skill to stdout.         |\n| Loads or symlinks skill files from disk | `hunk skill path` prints the bundled file's path.     |\n| Can only fetch URLs                     | Use the [published copy](/docs/hunk-review-skill.md). |\n\nThe published copy tracks the newest Hunk release, so prefer `hunk skill show` when your installed Hunk might be older.\n\n## Other bundled skills\n\n`hunk-extensions` teaches agents to build [extensions](/docs/extend/extensions/) against the `hunkdiff/extension` API:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-extensions\nhunk skill show hunk-extensions\n```\n\nFor the human side of the workflow, start with [Review with an agent](/docs/agents/review-with-an-agent/).\n\n<!-- File: website/src/content/docs/docs/agents/review-with-an-agent.md (Tokens: ~802 | Category: subagent-persona) -->\n---\ntitle: Review with an agent\ndescription: Let a coding agent inspect and guide a live Hunk review without giving up the human review UI.\n---\n\nThe Hunk window stays with you. Your agent uses non-interactive `hunk session` commands from another terminal to inspect the same review, navigate it, and leave inline notes.\n\n## Install the review skill\n\nThe `hunk-review` skill teaches your agent the session commands. Install it once with the `skills` CLI:\n\n```bash\nnpx skills add modem-dev/hunk -g --skill hunk-review\n```\n\nThe installed skill is a small pointer that runs `hunk skill show`, so the instructions your agent reads always match the Hunk version on your `PATH`. Add `-a <agent>` (for example `-a claude-code`) to install it for a single agent.\n\nIf your agent has no skill system, ask it to run `hunk skill show` and follow the output. See [Hunk review skill](/docs/agents/review-skill/) for other ways to load it.\n\n## Start the review\n\n```bash\nhunk diff\n```\n\nKeep that window open. Normal Hunk sessions register with a local loopback daemon so the session CLI can find them.\n\n## Ask the agent to review\n\nIn your agent, ask for the review:\n\n```text\nUse the hunk-review skill to walk me through the changes in my open Hunk session.\n```\n\nThe skill tells agents not to launch the interactive TUI themselves. A typical agent flow is:\n\n```bash\nhunk session list\nhunk session get --repo .\nhunk session review --repo . --json\nhunk session navigate --repo . --file src/App.tsx --hunk 2\nhunk session comment add --repo . --file src/App.tsx --new-line 42 --summary \"Check this boundary\" --focus\n```\n\n`review --json` exposes structure without forcing the full patch into agent context. The agent should request `--include-patch` only when it actually needs raw unified diff text.\n\n## See the agent's notes\n\nAgent notes are hidden until you turn them on. Press `a` to show or hide them, or launch with `--agent-notes` (or set `agent_notes = true` in your [config](/docs/configure/configuration/)) to show them from the start. When the agent adds a note with `--focus`, Hunk jumps to it and turns agent notes on for you.\n\n![A Hunk review with agent rationale rendered directly beside the annotated diff hunk](/docs/images/agent-comments.webp)\n\nAgent notes stay attached to the code they explain. Press `{` and `}` to move between annotated hunks while keeping the full changeset visible.\n\n## Keep control\n\nThe agent can move the visible selection and add notes, but you stay in the review stream and can navigate normally. Ask it to summarize when finished, then walk the annotated hunks with `{` and `}`.\n\nTo hand questions back to the agent, leave your own notes with `c`; see [Comments and annotations](/docs/agents/comments-and-annotations/#hand-your-notes-to-the-agent).\n\n## Give the agent the docs\n\nThese docs are published as plain Markdown so an agent can read them without scraping HTML:\n\n- [/llms.txt](https://hunk.dev/llms.txt): an index of every page, for pulling only what is needed.\n- [/llms-small.txt](https://hunk.dev/llms-small.txt): a compact corpus for tight context budgets.\n- [/llms-full.txt](https://hunk.dev/llms-full.txt): every page and the full changelog in one file.\n\nAny docs page URL also returns its Markdown source with `.md` appended, so `https://hunk.dev/docs/reference/cli.md` is the CLI reference as the agent should read it.\n\n<!-- File: examples/extensions/cli-tools/README.md (Tokens: ~193 | Category: plugin-manifest) -->\n# CLI tools extension\n\nDemonstrates a generic top-level command tree. The handler owns every token below `cli-tools`, can stream headless output, receives cooperative cancellation, and can hand terminal ownership to one built-in Hunk command.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx --extension ./examples/extensions/cli-tools cli-tools status\nbun run packages/hunk/src/main.tsx --extension ./examples/extensions/cli-tools cli-tools review\n```\n\n`status` writes to stdout and exits. `review` performs signal-aware asynchronous preprocessing, writes progress to stderr, then delegates to `hunk diff`. A delegating handler must not write stdout or read stdin because the built-in command or TUI takes ownership of both.\n\nThe example parses its raw argument tokens directly. Extensions may use their own parser, make network requests, spawn processes, or access the filesystem under Hunk's ordinary extension trust model.\n\n<!-- File: examples/extensions/code-document-file-view/README.md (Tokens: ~212 | Category: plugin-manifest) -->\n# Code-document file view\n\nThis API-v28 example presents one old/new code pair per diff hunk. It declares the complete source documents so Hunk can tokenize with multiline lexical context, while each displayed span references either a complete old-side line or a partial new-side UTF-16 slice. Gutters and the unhighlighted prefix stay ordinary symbolic spans.\n\nHunk owns the language, active syntax theme, workers, caching, terminal safety, and plain fallback. The extension never supplies token colors.\n\nRun it from this checkout against two JavaScript or TypeScript files:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/code-document-file-view \\\n  path/to/before.ts path/to/after.ts\n```\n\nChoose **View → File presentation → Code documents: old / new**. The example intentionally omits `sourceRanges`: syntax references paint code but do not claim note/navigation ownership. A visible note therefore makes Hunk use the raw diff, as required by the all-or-raw note policy.\n\n<!-- File: examples/extensions/inline-edit/README.md (Tokens: ~1867 | Category: plugin-manifest) -->\n# Inline edit extension\n\nA miniature line editor for the file under review. Press `Ctrl-E`, type into the diff, press `Ctrl-S`, and Hunk writes the file back to your working tree after asking you first.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\nIt exists to demonstrate that Hunk's interactive extension surfaces compose, so it uses them all at once:\n\n| Capability                              | Where this extension uses it                                                                                                                                                                                                                                           |\n| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `ctx.workspace`                         | `canWriteDocument` gates the affordance, `readDocument(\"new\")` fills the buffer, `writeDocument` performs the consented write and reloads the review.                                                                                                                  |\n| `mode` on a registered file view        | `onKey` claims arrows, editable characters, Backspace, Enter, and `Ctrl-S`, while `]`, `?`, and `q` pass through so navigation, help, and quit keep working. `Ctrl-S` is recognized with `matchesKey`, so the bare control byte terminals send for it matches too.     |\n| `fileViews.enterMode(viewId)`           | One call makes the view the file's presentation _and_ gives its mode the keyboard, so `Ctrl-E` opens the editor in a single press.                                                                                                                                     |\n| `fileViews.refresh(viewId, { fileId })` | Every buffer or caret change re-derives the layout. A view's layout is a pure function of `(file, width)`, so this is the only way a stateful presentation redraws — and the buffer belongs to one file, so the refresh is scoped to it and no other file re-lays out. |\n\n## Try it from this checkout\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/inline-edit\n```\n\n## Install it globally\n\nCopy the whole folder — it has no dependencies:\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/inline-edit ~/.config/hunk/extensions/\n```\n\nHunk discovers the folder automatically on later launches. Open **View** and choose **File presentation: Inline edit**, or press `Ctrl-E`. The command is named `inline-edit.edit` for `[keybindings]` customization.\n\n## Keys\n\n| Key                           | Does                                                                          |\n| ----------------------------- | ----------------------------------------------------------------------------- |\n| `Ctrl-E`                      | Starts editing the selected file, showing the view if it was not already.     |\n| `↑` `↓` `←` `→`               | Move the caret. `←`/`→` wrap across line ends.                                |\n| any other printable character | Types at the caret — including characters bound to Hunk commands, like `z`.   |\n| `Backspace`                   | Deletes back one character, joining with the previous line at column 0.       |\n| `Enter`                       | Splits the line at the caret.                                                 |\n| `Ctrl-S`                      | Asks Hunk to write the buffer. Hunk confirms, writes, and reloads the review. |\n| `Esc`                         | Leaves the editor and **discards** everything typed since the last write.     |\n| everything else               | Still Hunk's: `]` moves to the next hunk, `?` opens help, `q` quits.          |\n\nThe header row reads `EDITING — Esc exits · ctrl+s writes`, plus a `MODIFIED` marker whenever the buffer differs from the text on disk.\n\n## How the pieces fit\n\n`Ctrl-E` is one press. The command gates on `canWriteDocument`, starts reading the document, and calls `fileViews.enterMode` before awaiting that read. Entry therefore uses the same selected file the command captured; a selection change cannot attach a late buffer to another file. `enterMode` makes the view that file's presentation and gives its mode the keyboard together, then the completed read builds the buffer and refreshes the view. When entry refuses (no `mode`, a file Hunk keeps on raw diff, a view that does not match), it warns by name and returns `false` without installing a buffer.\n\nThe editor slot is claimed synchronously, before that command's first `await`. Reading the document suspends the handler, so a guard that only checked \"is a session live?\" would let a second `Ctrl-E` through the window in between, and one of the two handlers would then be parked forever on a session nothing could end. The claim is released on every way out — an unwritable review, an unreadable document, a refused `enterMode` — and once a session is live it is the live session that answers the next press.\n\n`onKey` has to answer synchronously — its return value _is_ the routing decision — and the mode context carries only `file` and `fileViews`. So a keystroke can never write anything itself. What `Ctrl-S` does instead is post a request into the edit session, and the command handler that entered the mode is still awaiting that session: `ctx.workspace` is valid for the whole life of a command handler's promise, so the handler is the mode's async runtime.\n\nThat loop ends the same way from every direction, because `onExit` runs on every exit path:\n\n- **Saved.** A successful `writeDocument` reloads the session, the reload exits the mode, `onExit` ends the loop.\n- **Escaped.** Escape is host-owned, so it exits the mode without ever reaching `onKey`, and the loop ends the same way.\n- **Cancelled write.** `{ ok: false, reason: \"cancelled\" }` is the user answering, not a failure. The editor keeps running.\n- **Failed write.** The `detail` sentence is shown as a warning and the editor keeps running, so nothing typed is lost to a full disk.\n\n## Limitations\n\nThis is a demonstration, not an editor:\n\n- **No wrapping.** Lines are truncated at the pane width with `…`. A file-view layout must be deterministic for `(file, width)`, and wrapping would be a second layout problem on top of the one this example is about.\n- **Whole-line truncation.** The caret can move past the visible edge; the row does not scroll horizontally to follow it.\n- **One buffer, no undo.** Escape discards everything since the last write, with no confirmation beyond the notice it leaves behind.\n- **Writes are working-tree only**, which is a property of `ctx.workspace`: `hunk show`, `hunk patch`, a staged diff, and a file-pair diff have no working-tree document to replace, so the command refuses instead of opening an editor that could not save.\n- **Agent notes describe the document as it was loaded.** They stay on the lines they were written about, but nothing re-reads the changeset while you type.\n\n## Source bindings and provenance\n\nRows carry `sourceRanges` so Hunk can place its own inline notes inside this presentation, and a row may only bind a line it honestly still _is_. Row position cannot answer that once you split or join a line, so the edit session keeps **provenance**: for each buffer line, the document line it came from, or nothing at all.\n\n| Edit               | Provenance                                                               |\n| ------------------ | ------------------------------------------------------------------------ |\n| typing in a line   | kept — an edited line is still the line it came from                     |\n| `Enter` (split)    | the first line keeps it; the new tail gets none                          |\n| `Backspace` (join) | the merged line presents both source lines so either note stays attached |\n\nSo a line you inserted binds nothing, the lines below a split keep the numbers they had, and a joined row keeps every source line it now presents. The hunk extents in `hunkRows` are derived from the same provenance, so the hunk highlight follows the rows still holding a hunk's lines instead of drifting down by however many lines you added above them. A join across two distinct hunks is refused because one bound row cannot belong to two hunk extents.\n\nOne thing this shows that is easy to get wrong: Hunk accepts a binding only on a row exactly one `hunkRows` extent owns, and rejects the whole layout otherwise — so a last pass drops the bindings no extent owns, and context lines outside every hunk are presented without one. The [rendered Markdown example](../rendered-markdown/) ends its layout with the same pass, for the same reason.\n\n## Where this is documented\n\n- [`docs/extensions.md` → Interactive file views](../../../docs/extensions.md#interactive-file-views)\n- [`docs/extensions.md` → Reading and writing a reviewed file](../../../docs/extensions.md#reading-and-writing-a-reviewed-file)\n- [`docs/extensions.md` → `hunk.registerFileView(view)`](../../../docs/extensions.md#hunkregisterfileviewview-experimental)\n\n<!-- File: examples/extensions/jsx-file-view-gallery/mixed-review/README.md (Tokens: ~301 | Category: plugin-manifest) -->\n# Mixed preview review\n\nThis launches one real five-file Git working-tree review that is intentionally taller than a terminal viewport:\n\n- `README.md` — raw Markdown diff;\n- `package.json` — dependency version-segment highlights;\n- `scripts/deploy.py` — raw Python diff;\n- `src/invoice.ts` — responsive change-atlas cards;\n- `styles/theme.css` — exact-source color swatches.\n\nThe launcher creates a temporary repository, commits the checked-in `before` fixtures, copies the `after` fixtures into its working tree, and starts Hunk from this checkout. The repository is removed when Hunk exits.\n\nFrom the Hunk repository root:\n\n```bash\nbun run ./examples/extensions/jsx-file-view-gallery/mixed-review/run.ts\n```\n\nRaw diff is deliberately the default. To build the mixed stream:\n\n1. Click `package.json` in the sidebar and press **F8**.\n2. Click `src/invoice.ts` and press **F8**.\n3. Click `styles/theme.css` and press **F8**.\n4. Click `README.md` to return to the top, then scroll through the main pane.\n\nSelecting files only jumps the main review stream; it does not collapse other files. The three preview selections therefore remain active together, interleaved with the two raw Pierre diffs. Use `[` and `]` while scrolling to verify that hunk navigation crosses raw and custom sections using the same host-owned geometry.\n\n<!-- File: examples/extensions/jsx-file-view-gallery/README.md (Tokens: ~713 | Category: plugin-manifest) -->\n# JSX file-view gallery\n\nThree opt-in presentations exercise the constrained React/OpenTUI row contract against checked-in, realistic file pairs. Run one command from the repository root, then press **F8** or choose **Extensions → Toggle JSX demo for current file**. Press F8 again to restore Pierre's raw diff.\n\n## 1. Change atlas\n\nNested boxes, responsive meters, semantic color, and selected-hunk styling summarize a multi-hunk TypeScript refactor. It uses no source parser and works from public hunk/change metadata alone.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/before.ts \\\n  examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/after.ts\n```\n\n## 2. CSS palette delta\n\nThe extension lazily reads both exact documents, associates changed opaque three- or six-digit hexadecimal custom properties with each real diff hunk, and paints old/new terminal color swatches inside deterministic two-row rectangles.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/css-palette/before.css \\\n  examples/extensions/jsx-file-view-gallery/fixtures/css-palette/after.css\n```\n\n## 3. Dependency delta\n\nA conservative package-file parser highlights only the changed semantic-version segment: patch-only changes emphasize the patch number, minor upgrades emphasize the minor number, and major upgrades emphasize the full old/new strings. It retains positional bounds for every parsed hunk; invalid JSON or unavailable source falls back to raw diff.\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/jsx-file-view-gallery \\\n  --mode unified \\\n  examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/before/package.json \\\n  examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/after/package.json\n```\n\n## Mixed five-file review\n\nTo see all three preview types retained together between ordinary raw diffs—and enough content to exercise stream scrolling—run:\n\n```bash\nbun run ./examples/extensions/jsx-file-view-gallery/mixed-review/run.ts\n```\n\nFollow the short activation sequence in [`mixed-review/README.md`](./mixed-review/README.md).\n\n## Contract illustrated\n\n- Every painter stays inside a declared fixed-height row; geometry, scrolling, windowing, and hunk navigation remain host-owned.\n- Every row keeps useful symbolic spans for row-local error fallback, clipped to the same fixed rectangle.\n- Semantic data is captured in closures during `layout`; painters receive only bounded paint props and use Hunk's live paint-only semantic theme palette.\n- The demos intentionally have no pointer handlers. Registered commands are the supported interaction path.\n- Layouts return `null` when exact source is unavailable or no supported semantic row can be attributed. Mixed diffs may retain neutral summary rows for non-semantic hunks so navigation stays positional.\n- Rows bind conservatively attributed exact-source ranges. Hunk renders a note inside the alternate view only when its preferred-side anchor resolves uniquely; otherwise the complete file falls back to raw diff.\n\nThis gallery is experimental and is not loaded unless you explicitly pass or install its folder. See [`docs/file-view-jsx-poc.md`](../../../docs/file-view-jsx-poc.md) for the full contract.\n\n<!-- File: examples/extensions/jsx-file-view/README.md (Tokens: ~327 | Category: plugin-manifest) -->\n# JSX file-view POC extension\n\nAn opt-in proof of concept for fixed-height React/OpenTUI rows in alternate file presentations. It appears only for files with at least two parsed hunks and creates two custom rows per hunk, with stable row IDs and explicit hunk bounds.\n\nRun it from this checkout against a multi-hunk working-tree change:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/jsx-file-view\n```\n\nChoose **Extensions → Toggle JSX hunk cards (POC)**. The row component uses a React state hook and OpenTUI `box`/`text` elements. Its registered F8 command/menu item is the supported keyboard path. A cooperatively delivered, un-dragged left-button mouse-up toggles local detail and stops propagation; wheel, drag, and unhandled input remain host-owned. The row is a non-focusable paint surface, with no portal, renderer, focus, or input-delivery guarantee. Each component is a closure over the hunk summary; Hunk passes it only bounded paint props, including a live semantic theme palette that does not participate in layout. The `spans` on every row are the host-rendered fallback, clipped to the same declared fixed height if the component fails. Hook state survives selected-hunk updates while mounted, but is intentionally lost when windowing unmounts the row or a new layout generation replaces it.\n\nThis example is deliberately opt-in and experimental. See [`docs/file-view-jsx-poc.md`](../../../docs/file-view-jsx-poc.md) for constraints.\n\n<!-- File: examples/extensions/pane-layout/README.md (Tokens: ~85 | Category: plugin-manifest) -->\n# Pane layout extension\n\nRegisters a resizable right pane and fixed two-row top and bottom panes.\n\nRun it from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/pane-layout\n```\n\nPress `ctrl+p` or use the **Extensions** menu. Drag the right divider to resize.\n\nCopy this directory to your Hunk extensions directory to install it.\n\n<!-- File: examples/extensions/rendered-markdown/README.md (Tokens: ~419 | Category: plugin-manifest) -->\n# Rendered Markdown extension\n\nAn optional Markdown preview for Hunk's experimental file-view API. It parses Markdown with [Marked](https://marked.js.org/) and presents headings, inline formatting, links, lists, quotes, tables, and fenced code as host-owned symbolic rows.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\n## Try it from this checkout\n\nThe repository's root install supplies the example's development dependency:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff \\\n  --extension ./examples/extensions/rendered-markdown \\\n  before.md after.md\n```\n\n## Install it globally\n\nCopy the whole folder, then install its local dependency:\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/rendered-markdown ~/.config/hunk/extensions/\ncd ~/.config/hunk/extensions/rendered-markdown\nbun install\n```\n\nHunk discovers the folder automatically on later launches. Open **View** and choose **File presentation: Rendered Markdown**, or press `F8`. The command is named `rendered-markdown.toggle-rendered-markdown` for `[keybindings]` customization.\n\nRaw Pierre diff remains the default and fallback. The preview reads exact text through `input.readDocument`, returns hunk row bounds in source-hunk order, binds rendered blocks to exact new-side ranges, and retains hunk navigation and selection highlighting. Hunk inserts inline note cards at uniquely bound rows; if any visible note cannot be resolved, the complete file temporarily returns to raw rendering so review data is never hidden.\n\n## Why it returns rows instead of an OpenTUI Markdown component\n\nOpenTUI includes a capable `MarkdownRenderable`, but mounting it would make the extension own opaque layout geometry. Hunk needs exact rows before mount for review-stream measurement, windowing, scrolling, hunk navigation, and fallback. This example therefore uses the same Marked parser OpenTUI uses, then translates its tokens into the file-view contract's generic tones and text attributes.\n\n<!-- File: examples/extensions/review-note-navigator/README.md (Tokens: ~301 | Category: plugin-manifest) -->\n# Review note navigator extension\n\nLists every note currently saved in Hunk's shared ReviewStore, then navigates to a selected visible note's authoritative source anchor. The example shows how `ctx.review.snapshot()` complements dialogs and live navigation: selection and note events alone cannot recover notes that already existed before the extension began listening.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-note-navigator\n```\n\nSave one or more review notes, then run **Extensions → Navigate saved review note…** (`F8`). Each choice includes its reconciliation status, file, preferred line, side, and summary.\n\n- Active notes reveal their current source line.\n- Stale notes reveal the last authoritative anchor Hunk retained.\n- Orphaned notes remain visible in the inventory but produce a warning because they have no current review location.\n- A note whose file is hidden by Hunk's current file filter remains in the inventory, but guarded navigation refuses the hidden target with a warning; clear the filter and retry.\n- Drafts and static sidecar annotations that never entered ReviewStore are intentionally absent.\n\nThe command captures the complete note inventory before opening its selector. After the user chooses, it reads the authoritative snapshot again and resolves the selected note by stable note id, so an edit made by another review surface cannot make it navigate using an obsolete anchor. A reload cancels the dialog and retires the command's review controls.\n\n<!-- File: examples/extensions/review-snapshot-export/README.md (Tokens: ~341 | Category: plugin-manifest) -->\n# Review snapshot export extension\n\nExports Hunk's authoritative saved review state as JSON. The example shows why `ctx.review.snapshot()` is more useful than accumulating `note_created` events: one command receives every note currently retained by the shared ReviewStore, including stale and orphaned notes an exporter must handle explicitly.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-snapshot-export\n```\n\nAdd one or more review notes, then run **Extensions → Export review snapshot…** (`F9`) and choose a new output path. Relative paths resolve from the review's working directory; the example refuses to overwrite an existing file.\n\nThe JSON includes:\n\n- the opaque producer generation and ReviewStore revision\n- every reviewed file's stable `fileKey`, runtime navigation id, content identity, status, and path\n- all saved live and reviewer notes with their resolved old/new anchors and reconciliation status\n\nDraft notes and static sidecar annotations that never entered ReviewStore are intentionally absent. Saved notes appear in live-arrival order followed by reviewer-creation order.\n\nThe command captures a snapshot before opening its path dialog, then reads the controls again before writing. If either the generation or revision changed, it refuses the stale export and asks the user to rerun the command. Publishers can use the same check before an irreversible network request.\n\n## Trust\n\nThis example uses `node:fs` directly to write the user-selected export path. Hunk extensions run with the user's full permissions; this is distinct from `ctx.workspace`, which mediates writes to reviewed files and asks for consent. Install and run only extensions you trust.\n\n<!-- File: examples/extensions/review-triage/README.md (Tokens: ~389 | Category: plugin-manifest) -->\n# Review triage extension\n\nA session-local hunk review board for Hunk. It records which hunks you have visited and lets you mark the selected hunk **approved**, **investigate**, or **blocked** with an optional rationale.\n\nRun it directly from this checkout:\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-triage\n```\n\nOr copy the directory to your Hunk extensions directory and keep its `package.json`; its manifest makes the folder a single `review-triage` extension.\n\n## Use\n\nOpen **Extensions → Toggle review triage** (`Y`). The right pane lists each visible file's hunks; click a hunk to navigate the review stream. Use **Extensions → Mark selected hunk…** (`x`) to choose a status and enter an optional rationale. **Center current review line**, **Set review focus…**, and **Clear triage decisions** are menu-only commands.\n\nThe board intentionally keeps state only for the running Hunk session. Reloading reconciles decisions against the newly parsed hunks and drops entries that no longer match, rather than silently transferring a decision to changed code.\n\n## API surface exercised\n\n- `registerPane` renders public file/hunk summaries and navigates with pane actions.\n- `registerCommand` supplies the Extensions-menu items and user-remappable defaults; `ctx.commands.execute` delegates the dedicated centering action to Hunk's public semantic command.\n- `dialogs.select`, `dialogs.input`, and `dialogs.confirm` implement the review decision and clear flows.\n- Lifecycle handlers track changeset loads, reloads, selection, viewed hunks, Hunk notes, filters, and pending watch reloads through a `useSyncExternalStore` bridge.\n- `hunk.events` publishes decisions and listens for `review-triage:open`, so another extension can reveal the board without importing its state.\n\n<!-- File: examples/extensions/vim-navigation/README.md (Tokens: ~666 | Category: plugin-manifest) -->\n# Vim navigation extension\n\nA small Vim-style normal mode for Hunk's whole review stream. It demonstrates session keyboard modes and public semantic command execution without accessing scroll boxes, renderer objects, or viewport coordinates.\n\nThis example is **not bundled or loaded by Hunk**. Install it explicitly if you want it.\n\n## Try it from this checkout\n\n```bash\nbun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/vim-navigation\n```\n\nPress `F6` or choose **Extensions → Toggle Vim navigation**. The persistent status badge shows when the mode owns review-level keys; click the badge, choose the host-owned exit menu item, or press `Esc` to leave.\n\n## Install it globally\n\n```bash\nmkdir -p ~/.config/hunk/extensions\ncp -R examples/extensions/vim-navigation ~/.config/hunk/extensions/\n```\n\n## Keys\n\n| Key                 | Action                                                      |\n| ------------------- | ----------------------------------------------------------- |\n| `j` / `k`           | Move the current review line down/up                        |\n| `[` / `]`           | Move to the previous/next hunk                              |\n| `gg` / `G`          | Jump to the start/end of the review                         |\n| `zt` / `zz` / `zb`  | Align the current line at the top/center/bottom             |\n| `Ctrl-D` / `Ctrl-U` | Move down/up by half pages                                  |\n| positive digits     | Prefix the next relative motion, for example `5j` or `3]`   |\n| `:`                 | Open the host-rendered Vim command line                     |\n| `Esc`               | Exit the mode (host-owned; the extension never receives it) |\n| everything else     | Pass through to normal Hunk routing                         |\n\nCounts are parsed by the extension and capped at 10,000. Once a normal-mode sequence resolves, the extension calls `ctx.commands.execute(id, { count })` exactly once, so Hunk applies movement atomically. A bare `0` passes to Hunk's normal layout shortcut; `0` can extend a count that already began with `1`–`9`.\n\nPressing `:` passes the key to the example's registered command, which opens `ctx.prompts.line()` on the status row with a `:` prefix. That focused host input captures typed keys ahead of the still-active session mode until Enter submits, or Escape clears the buffer and a second Escape cancels. The deliberately small Ex-style command set is:\n\n| Command   | Action                          |\n| --------- | ------------------------------- |\n| `:top`    | Jump to the start of the review |\n| `:bottom` | Jump to the end of the review   |\n\nUnsupported commands produce an attributed warning. Absolute source-line commands such as Vim's `:100` are intentionally absent because Hunk does not expose source-line targeting as a public semantic command; relative counted movement such as `100j` remains available in normal mode.\n\nThe example enables Hunk's host-owned current-line marker on entry so the `z*` alignment commands have a target. It resets all pending prefix/count state on entry and exit. Invalid continuations clear pending state and pass the current key back to Hunk.\n\n<!-- File: packages/hunk-git/README.md (Tokens: ~205 | Category: plugin-manifest) -->\n# @hunk/git\n\nPrivate, statically bundled Git provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-tree, revision, and stash\nreviews; history; exact old/new source reads; staged changes; untracked and large-file handling;\nand filesystem-assisted watch plans.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-git` and `bun run deps:check` after changes.\n\n<!-- File: packages/hunk-jj/README.md (Tokens: ~207 | Category: plugin-manifest) -->\n# @hunk/jj\n\nPrivate, statically bundled Jujutsu provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-copy and revision reviews,\nhistory, exact source reads where the revision shape permits them, and signature-based refresh.\nStaged and stash reviews are not supported.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-jj` and `bun run deps:check` after changes.\n\n<!-- File: packages/hunk-sapling/README.md (Tokens: ~211 | Category: plugin-manifest) -->\n# @hunk/sapling\n\nPrivate, statically bundled Sapling provider for Hunk. This `0.0.0` workspace is not a public or\nversioned SDK.\n\nThe package exports its provider from `src/index.ts`. It supports working-copy and revision reviews,\nuntracked-file discovery, and signature-based refresh. History, staged and stash reviews, exact\nsource reads, and filesystem watch plans are not supported.\n\nProvider code may import local modules, platform built-ins, the public `hunkdiff/extension`\ncontract, and explicit `@hunk/vcs/*` leaves. It must not import Hunk core, app, session, or UI\ninternals.\n\nHunk registers the provider in `packages/hunk/src/extensions/default/vcs/index.ts` and composes it\ninto the provider-neutral catalog in `packages/hunk/src/app/vcsCatalog.ts`. Keep tests beside the\nprovider source. Run `bun test packages/hunk-sapling` and `bun run deps:check` after changes.\n\n<!-- File: packages/hunk-vcs/README.md (Tokens: ~214 | Category: plugin-manifest) -->\n# @hunk/vcs\n\nPrivate implementation helpers shared by Hunk's bundled VCS providers. This `0.0.0` workspace is\nnot a public or versioned SDK.\n\nThe package has no root export. Providers import only the leaf they need:\n\n- `@hunk/vcs/async-process`\n- `@hunk/vcs/diff-target`\n- `@hunk/vcs/large-file`\n- `@hunk/vcs/path`\n- `@hunk/vcs/source`\n\n`@hunk/vcs` sits at the bottom of the workspace graph and does not import other workspace packages.\nProvider implementations combine these helpers with the public `hunkdiff/extension` contract; Hunk\nregisters them in `packages/hunk/src/extensions/default/vcs/index.ts` and composes the catalog in\n`packages/hunk/src/app/vcsCatalog.ts`.\n\nKeep unit tests beside the source. Run `bun test packages/hunk-vcs` and `bun run deps:check` after\nchanging this package.\n\n<!-- File: packages/hunk/README.md (Tokens: ~3667 | Category: plugin-manifest) -->\n# hunk\n\nHunk is an interactive terminal diff viewer for reviewing code changes, built on [OpenTUI](https://github.com/anomalyco/opentui) and [Pierre diffs](https://www.npmjs.com/package/@pierre/diffs).\n\n**[hunk.dev](https://hunk.dev)** · [Documentation](https://hunk.dev/docs/)\n\n[![CI status](https://img.shields.io/github/actions/workflow/status/modem-dev/hunk/ci.yml?branch=main&style=for-the-badge&label=CI)](https://github.com/modem-dev/hunk/actions/workflows/ci.yml?branch=main)\n[![Latest release](https://img.shields.io/github/v/release/modem-dev/hunk?style=for-the-badge)](https://github.com/modem-dev/hunk/releases)\n[![MIT License](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE)\n[![Join the Discord community](https://img.shields.io/badge/Discord-Join%20community-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/WZFjaP6Gt8)\n\n- multi-file review stream with sidebar navigation\n- inline AI and agent annotations beside the code\n- split, unified, and responsive auto layouts\n- watch mode for auto-reloading file and Git-backed reviews\n- keyboard, mouse, pager, and Git difftool support\n\n<table>\n <tr>\n   <td width=\"60%\" align=\"center\">\n     <img width=\"845\" alt=\"image\" src=\"https://raw.githubusercontent.com/modem-dev/hunk/refs/heads/main/website/public/docs/images/review-stream.webp\" />\n     <br />\n     <sub>Split view with sidebar and inline AI notes</sub>\n   </td>\n   <td width=\"40%\" align=\"center\">\n     <img width=\"507\"alt=\"image\" src=\"https://raw.githubusercontent.com/modem-dev/hunk/refs/heads/main/website/public/docs/images/agent-comments.webp\" />\n     <br />\n     <sub>Unified view and mouse-selectable menus</sub>\n   </td>\n </tr>\n</table>\n\n## Install\n\nOn macOS and Linux, install the standalone binary:\n\n```bash\ncurl -fsSL https://hunk.dev/install.sh | sh\n```\n\nWindows users can install with npm or mise. Other installation methods are also available:\n\n```bash\nnpm i -g hunkdiff                    # macOS, Linux, or Windows; requires Node.js 22+\nbrew install hunk                    # macOS or Linux\nmise use -g hunk                     # macOS, Linux, or Windows\n```\n\n> [!NOTE]\n> If you previously installed hunk via `modem-dev/tap`, be sure to uninstall it first with `brew uninstall modem-dev/tap/hunk`.\n\nWindows requires mise 2026.8.6 or newer. Nix users can use the `default` package exported in `flake.nix`; see [the Nix guide](https://github.com/modem-dev/hunk/blob/main/nix/README.md) for details. Hunk also ships as a default tool in [Omarchy](https://omarchy.org), installed through mise.\n\nRequirements:\n\n- macOS (arm64, x64), Linux (arm64, x64; glibc), or Windows (x64); prebuilt binaries are not published for Windows on arm64 or musl-based Linux such as Alpine\n- On x86-64, a CPU with SSE4.2 (Intel Nehalem 2008+, AMD Bulldozer 2011+); arm64 has no CPU feature floor\n- Node.js 22+ for the npm install; the install script, Homebrew, mise, and Nix ship a standalone binary that does not require Node.js\n- Git recommended for most workflows\n\n### Update Hunk\n\nFor npm, Homebrew, and default install-script installs (Hunk 0.20+):\n\n```bash\nhunk update          # install the newest release\nhunk update --check  # check without installing\nhunk update 0.20.0   # select an exact npm or default install-script release\n```\n\nOn an older release, update once with the installer or package manager that installed Hunk, then use `hunk update` going forward. Custom `HUNK_INSTALL_DIR` installs must re-run the installer with the same directory; mise, Nix, and source installs use their owning tools instead.\n\n## Quick start\n\n```bash\nhunk           # show help\nhunk --version # print the installed version\n```\n\n### Working with Git\n\nUse familiar Git commands to open changes in Hunk:\n\n```bash\nhunk diff                      # review current repo changes, including untracked files\nhunk diff --watch              # auto-reload as the working tree changes\nhunk show                      # review the latest commit\nhunk show HEAD~1               # review an earlier commit\nhunk log                       # browse history on a terminal; print when redirected\nhunk log --static              # force static output, paging when needed\nhunk status                    # view workspace status and sibling worktrees (Git)\nhunk status --static           # print one read-only workspace snapshot\nhunk status --json             # emit one versioned workspace snapshot\n```\n\n`hunk status` is read-only: it does not fetch or change the repository. See [workspace status](docs/status.md) for details.\n\n`hunk log` opens an interactive history browser in a terminal and prints text when piped or redirected. Use `--oneline` for compact output. In the browser, enable **Graph view** from the View menu to see commit branches, or press `v` to select a commit range. Quit a commit review to return to history.\n\n### Working with GitHub\n\nHunk bundles `hunk gh`, so pull requests, commits, and comparisons open directly without the\nGitHub CLI or a separate extension:\n\n```bash\nhunk gh pr                         # infer the open PR for the current branch\nhunk gh pr 123                     # infer the repository from origin\nhunk gh pr 'modem-dev/hunk#123'    # quote this form for shells that treat # as a comment\nhunk gh commit a1b2c3d --repo modem-dev/hunk\nhunk gh compare main...feature --repo modem-dev/hunk\n```\n\nPublic repositories work anonymously. Set `GH_TOKEN`, or `GITHUB_TOKEN` when it is absent, for\nprivate repositories and higher API limits. GitHub Enterprise is not currently supported.\n\n### Working with Jujutsu and Sapling\n\nHunk auto-detects Jujutsu and Sapling checkouts, so `hunk diff [revset]` and `hunk show [revset]` use native revsets inside jj or Sapling workspaces. `hunk log --vcs jj` also reads JJ history directly, including in a non-colocated workspace. To override VCS detection, set `vcs = \"git\"` or `vcs = \"jj\"` or `vcs = \"sl\"` in [config](#config).\n\n### Viewing complete files and browsing directories\n\n```bash\nhunk open                 # browse the current directory\nhunk open README.md       # view a complete, syntax-highlighted document\nhunk open path/to/project # browse another directory without requiring a repository\n```\n\nUse arrows and Enter to navigate, Tab to switch focus, `i` to show hidden/Git-ignored entries, and `e` to edit the displayed file in `$EDITOR`. See [the open workflow](docs/open.md) for details and file limits.\n\n### Working with raw files and patches\n\n```bash\nhunk diff --files before.ts after.ts        # compare two files directly\nhunk diff --files before.ts after.ts --watch # auto-reload when either file changes\ngit diff --no-color | hunk patch -          # review a patch from stdin\n```\n\n### Working with agents\n\n1. Install the review skill once: `npx skills add modem-dev/hunk -g --skill hunk-review`.\n2. Open Hunk in another terminal with `hunk diff` or `hunk show`.\n3. Ask your agent to use the hunk-review skill against the live Hunk session.\n4. Press `a` in Hunk to show agent notes; they start hidden.\n\nIf your agent has no skill system, ask it to run `hunk skill show` and follow the output.\n\nSee [Review with an agent](https://hunk.dev/docs/agents/review-with-an-agent/) for live sessions, `--agent-context`, and experimental rich notes.\n\n## Feature comparison\n\n| Capability                           | [hunk](https://github.com/modem-dev/hunk) | [lumen](https://github.com/jnsahaj/lumen) | [difftastic](https://github.com/Wilfred/difftastic) | [delta](https://github.com/dandavison/delta) | [diff-so-fancy](https://github.com/so-fancy/diff-so-fancy) | [diff](https://www.gnu.org/software/diffutils/) |\n| ------------------------------------ | ----------------------------------------- | ----------------------------------------- | --------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |\n| Review-first interactive UI          | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Multi-file review stream + sidebar   | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Inline agent / AI annotations        | ✅                                        | ❌                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Responsive auto split/unified layout | ✅                                        | ❌                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Mouse support inside the viewer      | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Runtime view toggles                 | ✅                                        | ✅                                        | ❌                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Syntax highlighting                  | ✅                                        | ✅                                        | ✅                                                  | ✅                                           | ❌                                                         | ❌                                              |\n| Structural diffing                   | ❌                                        | ❌                                        | ✅                                                  | ❌                                           | ❌                                                         | ❌                                              |\n| Pager-compatible mode                | ✅                                        | ❌                                        | ✅                                                  | ✅                                           | ✅                                                         | ✅                                              |\n\n## Advanced\n\n### Config\n\nYou can persist preferences to a config file:\n\n- `$XDG_CONFIG_HOME/hunk/config.toml` (default `~/.config/hunk/config.toml`)\n- `.hunk/config.toml` in a repository\n\nPersistent state uses `$XDG_STATE_HOME/hunk/state.json` when the variable is\nabsolute, or `~/.local/state/hunk/state.json` otherwise. Hunk preserves existing\nstate by copying the old config-directory file on first use, keeping the original\nand falling back to it if migration fails. Global extension paths are unchanged.\n\nFor example:\n\n```toml\ntheme = \"github-dark-default\"\nmode = \"auto\"               # auto, split, unified\nauto_split_min_width = 160  # minimum available diff columns for split in auto mode\nline_numbers = true\nwrap_lines = false\n```\n\nSee the [configuration guide](https://hunk.dev/docs/configure/configuration/) for command overrides and examples, the [full reference](https://hunk.dev/docs/reference/config/) for every option, or the [theme guide](https://hunk.dev/docs/configure/themes/) to customize colors.\n\n### Keybindings\n\nRemap shortcuts with a `[keybindings]` table in your user config. See [the keybinding guide](https://hunk.dev/docs/configure/keybindings/) for available commands, default keys, and examples.\n\n### Git integration\n\nSet Hunk as your Git pager so `git diff` and `git show` open in Hunk automatically:\n\n> [!NOTE]\n> Untracked files are auto-included only for Hunk's own `hunk diff` working-tree loader. If you open `git diff` through `hunk pager`, Git still decides the patch contents, so untracked files will not appear there.\n\n```bash\ngit config --global core.pager \"hunk pager\"\n```\n\nOr in your Git config:\n\n```ini\n[core]\n    pager = hunk pager\n```\n\nIf you want to keep Git's default pager and add opt-in aliases instead:\n\n```bash\ngit config --global alias.hdiff \"-c core.pager=\\\"hunk pager\\\" diff\"\ngit config --global alias.hshow \"-c core.pager=\\\"hunk pager\\\" show\"\n```\n\n### Jujutsu pager integration\n\nTo use Hunk as jj's pager, run `jj config edit --user` and update:\n\n```toml\n[ui]\npager = [\"hunk\", \"pager\"]\ndiff-formatter = \":git\"\n```\n\n### Sapling pager integration\n\nTo use Hunk as Sapling's pager, run `sl config -u` and update:\n\n```ini\n[pager]\npager = hunk pager\n```\n\n### Extensions (experimental)\n\nThe extension API is experimental and may change in breaking ways between\nminor releases while it stabilizes; breaking changes are called out in\nrelease notes.\n\nHunk loads plain TypeScript extensions from `~/.config/hunk/extensions/`, from a\nrepository's `.hunk/extensions/` (after you explicitly trust that repository),\nand from `--extension <path>` for development. `--no-extensions` turns those off\nfor one run; Hunk's own bundled backends (Git, Jujutsu, and Sapling) stay loaded.\n\nExtensions can add commands, themes, VCS backends, custom sidebars, and more. For example, filter lockfiles out of a review:\n\n```ts\n// ~/.config/hunk/extensions/collapse-lockfiles.ts\nimport type { HunkExtensionAPI } from \"hunkdiff/extension\";\n\nexport default function (hunk: HunkExtensionAPI) {\n  hunk.transformChangeset((changeset, ctx) => {\n    const files = changeset.files.filter((file) => !file.path.endsWith(\".lock\"));\n    ctx.notify(`Collapsed ${changeset.files.length - files.length} lockfiles`);\n    return { ...changeset, files };\n  });\n}\n```\n\nExtensions shared as git repositories install straight from their host, and a\n`hunk-extension` GitHub topic marks community ones:\n\n```bash\nhunk extension install acme/hunk-word-diff@v1.2.0   # or git:host/path, a URL, a local path\nhunk extension list                                 # then update [name] / remove <name>\n```\n\nBrowse community extensions at\n[github.com/topics/hunk-extension](https://github.com/topics/hunk-extension);\npublish yours by pushing the extension to a repository root and adding that\ntopic.\n\nSee [the extension guide](https://hunk.dev/docs/extend/extensions/) for the API, configuration, and publishing instructions.\nInstallable examples include\n[review triage](https://github.com/modem-dev/hunk/tree/main/examples/extensions/review-triage),\n[review snapshot export](https://github.com/modem-dev/hunk/tree/main/examples/extensions/review-snapshot-export), an optional\n[rendered Markdown file view](https://github.com/modem-dev/hunk/tree/main/examples/extensions/rendered-markdown), and a\n[Vim navigation mode](https://github.com/modem-dev/hunk/tree/main/examples/extensions/vim-navigation) built from public semantic commands.\n\n### OpenTUI component\n\nHunk also publishes `HunkDiffView` and lower-level primitives from `hunkdiff/opentui` for embedding the same diff renderer in your own OpenTUI app.\n\nSee [the OpenTUI component guide](https://hunk.dev/docs/reference/opentui-components/) for install, API, and runnable examples.\n\n## Examples\n\nReady-to-run demo diffs live in [the examples directory](https://github.com/modem-dev/hunk/tree/main/examples).\n\nEach example includes the exact command to run from the repository root.\n\n## Contributing\n\n💬 _Chat with users/contributors on the [Modem Discord server](https://discord.gg/WZFjaP6Gt8)_\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution guidelines.\n\n## Sponsor\n\nSponsored by [Modem](https://modem.dev?utm_source=github&utm_medium=oss&utm_campaign=oss_hunk&utm_content=readme_footer).\n\n<a href=\"https://modem.dev?utm_source=github&utm_medium=oss&utm_campaign=oss_hunk&utm_content=readme_footer\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://modem.dev/images/logo/svg/modem-combined-white.svg\">\n    <source media=\"(prefers-color-scheme: light)\" srcset=\"https://modem.dev/images/logo/svg/modem-combined-black.svg\">\n    <img src=\"https://modem.dev/images/logo/svg/modem-combined-black.svg\" alt=\"Modem\" width=\"220\">\n  </picture>\n</a>\n\n## License\n\n[MIT](https://github.com/modem-dev/hunk/blob/main/LICENSE)\n\n<!-- ... +5 more modular skills available via /api/skills/modem-dev/hunk?path={path} -->\n</yakaai_skills>"}