Skip to main content

hunk-extensions

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.

معلومات المصدر

المستودع
modem-dev/hunk
آخر نشاط في المصدر
٢٩ سبتمبر ٢٠٢٦ في ٠٣:٥٢
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٩٬٥٧٢
التفرعات
٣١٢

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
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.
# Building Hunk extensions A Hunk extension is **one TypeScript (or JSX/JS) file that default-exports a factory**. Hunk imports it at startup and hands it an API object. No build step, no manifest required. ```ts // ~/.config/hunk/extensions/hello.ts import type { HunkExtensionAPI } from "hunkdiff/extension"; export default function (hunk: HunkExtensionAPI) { hunk.on("startup", (_event, ctx) => ctx.notify("Hello")); } ``` This skill is a map of the touchpoints, not a recipe. Decide what to build from the user's request; use the table below to find the call, then read the linked material before writing code. ## Sources of truth — read before writing | Source | What it answers | | ------------------------------------------ | ------------------------------------------------------------ | | `docs/extensions.md` | The authoring guide. Every call, every rule. Start here. | | `packages/hunk/src/extension-api/types.ts` | The contract — exact field names, optionality, doc comments. | | `examples/extensions/*` | Working extensions. Copy these patterns rather than invent. | | `docs/extension-architecture.md` | Hunk's internals. Needed only when changing the host. | | `docs/keybindings.md`, `docs/themes.md` | Chord grammar and theme token rules that extensions inherit. | Outside a Hunk checkout the guide is split across <https://hunk.dev/docs/extend/extensions/> (discovery, trust, config) and its companion pages — extension-api, file-previews, vcs-adapters, custom-panes — and the contract ships as `node_modules/hunkdiff/dist/npm/extension/index.d.ts`. The examples, by what they demonstrate: - `review-triage/` — pane + commands + all three dialog shapes + lifecycle events + the extension event bus + a `useSyncExternalStore` bridge. - `inline-edit/` — an interactive file-view `mode` driving `ctx.workspace` writes; its README explains the async lifetime rules better than anything else in tree. - `rendered-markdown/` — a file view producing host-rendered rows from parsed Markdown, and a folder extension with an npm dependency. - `code-document-file-view/` — API-v28 host-owned syntax paint over complete old/new code documents, including semantic gutters and partial UTF-16 ranges. - `jsx-file-view/`, `jsx-file-view-gallery/` — the experimental fixed-height JSX row component contract. ## Where extensions live | Source | Trust | | ------------------------------------------ | ---------------- | | `--extension <path>` (repeatable) | runs immediately | | `[extensions] paths` in user config | runs immediately | | `~/.config/hunk/extensions/` (XDG-aware) | runs immediately | | `.hunk/extensions/` or repo-config `paths` | **trust prompt** | Only the repo-local group is gated. Everything else — including `--extension`, even when its path points inside the repository under review — is read as explicit user intent and executes with full user permissions, no prompt. Never pass or suggest a path you have not read, including one copied from a repository's own README. A directory matches `*.ts`/`*.tsx`/`*.js`/`*.jsx`/`*.mjs` at its top level, plus one level of folder extensions. A folder is an extension if it has a `package.json` with `{"hunk": {"extensions": ["./index.ts"]}}`, or an `index.{ts,tsx,js,jsx,mjs}`. Reach for a folder only when you need npm dependencies, helper modules, or a README; a single file keeps the install to one `cp`. A `.hunk/extensions/` folder extension's `node_modules` has to exist on every machine that loads it — keep a repo-shared extension dependency-free. Shared extensions install from git with `hunk extension install <source>` (`owner/repo[@ref]`, `git:host/path[@ref]`, a git URL, or a local path) into `~/.config/hunk/extensions/installed/<repo-name>/`, where they load with global origin; `list`, `update`, and `remove` manage them. Declared `dependencies` are `bun install`ed at install time. The manifest may state `{"hunk": {"apiVersion": N}}` — the minimum extension API version — and an older Hunk refuses the extension with a startup notice instead of failing mid-factory. To publish, push the folder-extension layout to a git repository's root with real `name`/`version`/`description`, tag releases for `@ref` pins, and add the `hunk-extension` GitHub topic so it appears at <https://github.com/topics/hunk-extension>. The **id** is the file stem, or the folder name for a folder extension — unless its manifest declares several entries, in which case each entry is its own extension named by its own stem (numeric suffix on collision). The id is the namespace it owns: commands are `<id>.<commandId>`, panes and keyboard modes are `<id>:<localId>`, config `[extension.<id>]`. Ids match `/^[A-Za-z0-9][A-Za-z0-9_-]*$/`; `hunk`, `git`, `jj`, and `sl` are reserved. A bad or duplicate id is skipped with a startup notice. `[extensions] disabled = ["<id>"]` filters a user extension before import or trust prompting. `--disable-extension <id>` and `--enable-extension <id>` are ordered one-run overrides; `--no-extensions` remains the hard switch for all user extensions. Bundled capabilities use separate host-owned selection ids such as `hunk.gh`, even when their public registrations remain under the `hunk` namespace. ## Pick the touchpoint | To do this | Call | | -------------------------------------------------------- | -------------------------------------------- | | Keep demo/training view settings temporary | `hunk.configureSession(options)` | | Add a selectable color theme | `hunk.registerTheme(theme)` | | Highlight an extension, exact filename, or filename glob | `hunk.registerFileLanguage(matcher, lang)` | | Support another VCS (`git`/`jj`/`sl` are reserved) | `hunk.registerVcsAdapter(adapter)` | | Add a navigation/list/status pane beside the review | `hunk.registerPane(pane)` | | Present a file as something other than a raw diff | `hunk.registerFileView(view)` (experimental) | | Mark character ranges inside diff lines | `hunk.registerLineHighlighter(highlighter)` | | Interpret review keys as a temporary global mode | `hunk.registerKeyboardMode(mode)` | | Add a generic top-level CLI command tree | `hunk.registerCliCommand(command, handler)` | | Bind a key / add an Extensions-menu entry | `hunk.registerCommand(command, handler)` | | Show persistent text on the bottom status row | `ctx.statusLine.set(item)` in a handler | | Ask for one line of text inline, `less`-style | `ctx.prompts.line(options)` in a command | | Hide, reorder, retitle files before review | `hunk.transformChangeset(fn)` | | React to loads, selection, view movement, notes, reloads | `hunk.on(event, handler)` | | Coordinate with another loaded extension | `hunk.events.emit` / `hunk.events.on` | | Reload after an external agent changes reviewed inputs | `ctx.review.requestReload()` in an event | | Read user-supplied settings | `hunk.config` (`[extension.<id>]` table) | | Snapshot stable files and every saved review note | `ctx.review.snapshot()` in a command | | Branch on the API generation (currently `28`) | `hunk.apiVersion` | Registration is only valid while the factory runs — Hunk seals the API object afterwards. Promise-returning VCS `watchSignature` hooks and watch cancellation require API version 25. Declare `{"hunk": {"apiVersion": 25}}` in the manifest, or branch on `hunk.apiVersion` and return signatures synchronously on older hosts. Use async I/O and honor `ctx.signal` on API 25; existing synchronous hooks remain supported. ### Generic CLI handlers Register one lowercase-kebab top-level token; the handler owns every raw token below it. Built-ins and aliases cannot be shadowed, and discovery order makes the first extension claim win. During development, place the explicit path before the extension command: ```bash hunk --extension ./my-ext.ts my-command sync --help ``` The handler receives frozen args plus `ctx.cwd`, `ctx.signal`, streaming `ctx.stdin`, and leased `ctx.stdout`/`ctx.stderr` writers. `summary` and `usage` are listed when a token reaches discovery unclaimed, so write them as one short line each. Return `{ kind: "exit", code? }` or `{ kind: "delegate", argv: ["diff", ...] }`. Delegation is built-in-only and one-time: do not write stdout or read stdin before delegating; use stderr for progress. Reading stdin is an exit-only workflow. Respect cancellation promptly. Repo-local providers remain trust-gated; `--no-extensions` performs no discovery or import, while a leading explicit `--extension` path is immediate consent. Use `packages/hunk-gh/src/` as the reference for a complete bundled CLI preprocessor: direct authenticated HTTP with cancellation, temporary artifacts with platform-accurate permission claims retained through delegated startup, cleanup on `shutdown`, and a one-time handoff to built-in `patch` without touching stdin or stdout. ## What handlers receive Every event, bus, command, and file-view mode handler — plus every changeset transform — gets `ctx.cwd` and `ctx.notify(message, type?)`. A file view's `matches` and `layout` get no context at all. Beyond that: - **Event and bus handlers** also get `ctx.panes` (open/close/toggle/isOpen on any pane), live `ctx.navigation`, attributed `ctx.dialogs`, `ctx.statusLine` (set/clear this extension's status-row items), review reloads through `ctx.review.requestReload()`, and `ctx.events.emit`. `ctx.sidebars` is a deprecated alias for `ctx.panes`. - **Command handlers** get `ctx.panes`, `ctx.fileViews` (select/toggle/isActive/ refresh/enterMode/exitMode), `ctx.highlights` (refresh prepared line marks, whole or `{ fileId }`-scoped), `ctx.selection` (a snapshot of file, hunk index, nullable current `{ side, line }` source address, and `files`, the visible files in review order), `ctx.navigation` (live, guarded `selectFile`/`selectHunk`/`revealLine`, the last landing one exact `(side, line)` near the viewport top), `ctx.commands` (`isEnabled`/`execute` for public semantic `hunk.*` commands), `ctx.keyboardModes` (enter/exit/probe this extension's session modes), `ctx.review` (deeply immutable snapshots of stable files and complete saved store notes), `ctx.dialogs` (`confirm`/`select`/`input`, queued and attributed), `ctx.statusLine` (set/clear persistent status-row items), `ctx.prompts` (`line`: an inline status-row input resolving the text or `null`, queued and attributed like dialogs), and `ctx.workspace` (`readDocument`, `canWriteDocument`, `writeDocument` with consent). - **Pane components** get frozen `files`, selection, placement, exact dimensions, nullable immutable delegated-source `review` metadata, optional `currentLine` paint (with `{ side, line }` when opted in), semantic `theme`, resolved `keybindings`, and guarded navigation/notification `actions`. Availability callbacks receive the same `review` value, so a pane can consume no geometry for ordinary reviews. - **File-view `layout`** gets `file`, `width`, `signal`, `changes`, and a lazy `readDocument(side)`. - **File-view `mode` handlers** get `ctx.file` and `ctx.fileViews`. `onKey`, `onEnter`, and `onExit` must answer **synchronously** — `onKey`'s return value (`"handled"`/`"pass"`/`"exit"`) is the routing decision, so kick off async work and report it later through `notify` or `refresh`. A passed key reaches any active session keyboard mode before ordinary Hunk routing. Escape is host-owned and never reaches `onKey`. - **Session keyboard-mode handlers** get only `ctx.commands`, `ctx.highlights`, `ctx.statusLine`, and activation-scoped `ctx.keyboardModes` beyond the standard context. A prompt-shaped interaction is a command plus `ctx.prompts.line()`, not a mode. Those controls become inert on exit, and lifecycle callbacks cannot change keyboard ownership. Keys are frozen snapshots; dialogs, focused inputs, and file-view modes outrank them. When the session mode owns input, Escape exits it; the status badge and Extensions menu are unconditional host-owned exits. Event payloads, pane props, and a command's selection all hand you frozen `ExtensionDiffFile` / `ExtensionDiffHunk` views. A changeset transform is the exception: it receives the live changeset and is expected to return a new one. `metadata` is unfrozen either way — it is the renderer's parsed diff, so pass it through untouched. ## Rules that bite Most extension bugs are one of these: - **Registering a surface does not show it.** Panes need `defaultOpen`, `replaces: "hunk:files"`, or a command that opens them. File views remain raw until selected from the **View** menu. - **A rejected file-view layout silently becomes raw diff.** `hunkRows` needs one in-bounds, inclusive entry per parsed hunk at the same array index, and `sourceRanges` may not overlap on a side; invalid, oversized, cancelled, and throwing layouts warn once and fall back. - **Syntax paint stays declarative.** File views declare complete `codeDocuments` and map exact symbolic spans with `syntax`; Hunk owns tokenization, theme colors, visible-window demand, resource limits, and plain fallback. Keep gutters and separators in non-syntax spans, and use `sourceRanges` separately for notes/navigation. - **Never bundle or vendor React.** Hunk serves its own `react` and `@opentui/*` to extension files; a second copy means a second hooks dispatcher and the component fails to render. Import them normally. OpenTUI intrinsics (`box`, `text`, `scrollbox`) need no import. - **`layout` is a pure derivation of `(file, width)`.** A stateful view keeps painting its first answer until `ctx.fileViews.refresh(viewId)` — scope it with `{ fileId }` when the state belongs to one file. - **Handler state must live outside the component.** Panes unmount when closed; bridge module-level state into React with `useSyncExternalStore` and immutable snapshots (`review-triage/index.tsx` is the working version). - **Use `ctx.review.snapshot()` for complete saved-note state.** `note_created` and `note_edited` are incremental UI events, not an authoritative collection. Snapshots include stale and orphaned saved notes, exclude drafts and static sidecar annotations, and should be re-read before irreversible async work; compare both generation and revision. `review-note-navigator` shows how to join stable note ids and file keys back to guarded
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub