hunkdiff 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,58 +1,103 @@
1
1
  # hunk
2
2
 
3
- Hunk is a desktop-inspired terminal diff viewer for understanding AI-authored changesets in Bun + TypeScript with OpenTUI.
3
+ Hunk is a terminal diff viewer for reviewing agent-authored changesets with a desktop-style UI.
4
+
5
+ - full-screen multi-file review stream
6
+ - split, stacked, and responsive auto layouts
7
+ - keyboard and mouse navigation
8
+ - optional agent rationale beside annotated hunks
9
+ - Git pager and difftool integration
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm i -g hunkdiff
15
+ ```
4
16
 
5
17
  ## Requirements
6
18
 
7
- - Bun
8
- - Zig
19
+ - Node.js 18+
20
+ - Git for `hunk diff`, `hunk show`, `hunk stash show`, and pager integration
9
21
 
10
- ## Install
22
+ ## Quick start
23
+
24
+ Review your current working tree:
11
25
 
12
26
  ```bash
13
- bun install
27
+ hunk diff
14
28
  ```
15
29
 
16
- ## Run
30
+ Review staged changes:
17
31
 
18
32
  ```bash
19
- bun run src/main.tsx -- diff
33
+ hunk diff --staged
20
34
  ```
21
35
 
22
- ## Standalone binary
36
+ Review a commit:
23
37
 
24
- Build a local executable:
38
+ ```bash
39
+ hunk show HEAD~1
40
+ ```
41
+
42
+ Compare two files directly:
25
43
 
26
44
  ```bash
27
- bun run build:bin
28
- ./dist/hunk diff
45
+ hunk diff before.ts after.ts
29
46
  ```
30
47
 
31
- Install it into `~/.local/bin`:
48
+ Open a patch from stdin:
32
49
 
33
50
  ```bash
34
- bun run install:bin
35
- hunk
36
- hunk diff
51
+ git diff --no-color | hunk patch -
37
52
  ```
38
53
 
39
- If you want a different install location, set `HUNK_INSTALL_DIR` before running the install script.
54
+ ## Feature comparison
55
+
56
+ | Capability | hunk | difftastic | delta | diff |
57
+ | --- | --- | --- | --- | --- |
58
+ | Dedicated interactive review UI | ✅ | ❌ | ❌ | ❌ |
59
+ | Multi-file review stream with navigation sidebar | ✅ | ❌ | ❌ | ❌ |
60
+ | Agent / AI rationale sidecar | ✅ | ❌ | ❌ | ❌ |
61
+ | Split diffs | ✅ | ✅ | ✅ | ✅ |
62
+ | Stacked diffs | ✅ | ✅ | ✅ | ✅ |
63
+ | Auto responsive layouts | ✅ | ❌ | ❌ | ❌ |
64
+ | Themes | ✅ | ❌ | ✅ | ❌ |
65
+ | Syntax highlighting | ✅ | ✅ | ✅ | ❌ |
66
+ | Syntax-aware / structural diffing | ❌ | ✅ | ❌ | ❌ |
67
+ | Mouse support inside the diff viewer | ✅ | ❌ | ❌ | ❌ |
68
+ | Runtime toggles for wrapping / line numbers / hunk metadata | ✅ | ❌ | ❌ | ❌ |
69
+ | Pager-compatible mode | ✅ | ✅ | ✅ | ✅ |
70
+
71
+ ## Benchmarks
72
+
73
+ Quick local timing snapshot from one Linux machine on the same 120-line TypeScript file pair. Metric: time until a changed marker first became visible.
74
+
75
+ | Tool | Avg first-visible changed output |
76
+ | --- | ---: |
77
+ | `diff` | ~37 ms |
78
+ | `delta --paging=never` | ~35 ms |
79
+ | `hunk diff` | ~219 ms |
80
+ | `difft --display side-by-side` | ~266 ms |
81
+
82
+ Takeaway:
83
+
84
+ - `diff` and `delta` are fastest here because they print plain diff text and exit.
85
+ - `hunk` spends more startup time on an interactive UI, syntax highlighting, navigation state, and optional agent context.
86
+ - `difftastic` spends more startup time on structural diffing.
40
87
 
41
- ## Workflows
88
+ ## Common workflows
42
89
 
43
- - `hunk` — print standard CLI help with the most common commands
44
- - `hunk diff` — review local working tree changes in the full Hunk UI
45
- - `hunk diff --staged` / `hunk diff --cached` — review staged changes in the full Hunk UI
90
+ - `hunk` — print CLI help
91
+ - `hunk diff` — review working tree changes
92
+ - `hunk diff --staged` / `hunk diff --cached` — review staged changes
46
93
  - `hunk diff <ref>` — review changes versus a branch, tag, or commit-ish
47
- - `hunk diff <ref1>..<ref2>` / `hunk diff <ref1>...<ref2>` — review common Git ranges
48
- - `hunk diff -- <pathspec...>` — review only selected paths
49
- - `hunk show [ref]` — review the last commit or a given ref in the full Hunk UI
50
- - `hunk stash show [ref]` — review a stash entry in the full Hunk UI
51
- - `hunk diff <left> <right>` compare two concrete files directly
52
- - `hunk patch [file|-]` — review a patch file or stdin, including pager mode
53
- - `hunk pager` — act as a general Git pager wrapper, opening Hunk for diff-like stdin and falling back to normal text paging otherwise
94
+ - `hunk diff <ref1>..<ref2>` / `hunk diff <ref1>...<ref2>` — review Git ranges
95
+ - `hunk diff -- <pathspec...>` — limit review to selected paths
96
+ - `hunk show [ref]` — review the last commit or a specific ref
97
+ - `hunk stash show [ref]` — review a stash entry
98
+ - `hunk patch [file|-]`review a patch file or stdin
99
+ - `hunk pager` — act as a Git pager wrapper, opening Hunk for diff-like stdin and falling back to plain text paging otherwise
54
100
  - `hunk difftool <left> <right> [path]` — integrate with Git difftool
55
- - `hunk git [range]` — legacy alias for the original Git-style diff entrypoint
56
101
 
57
102
  ## Interaction
58
103
 
@@ -70,6 +115,38 @@ If you want a different install location, set `HUNK_INSTALL_DIR` before running
70
115
  - `tab` cycle focus regions
71
116
  - `q` or `Esc` quit
72
117
 
118
+ ## Git integration
119
+
120
+ Use Hunk directly for full-screen review:
121
+
122
+ ```bash
123
+ hunk diff
124
+ hunk diff --staged
125
+ hunk diff main...feature
126
+ hunk show
127
+ hunk stash show
128
+ ```
129
+
130
+ Use Hunk as a pager for `git diff` and `git show`:
131
+
132
+ ```bash
133
+ git config --global core.pager 'hunk patch -'
134
+ ```
135
+
136
+ Or scope it just to diff/show:
137
+
138
+ ```bash
139
+ git config --global pager.diff 'hunk patch -'
140
+ git config --global pager.show 'hunk patch -'
141
+ ```
142
+
143
+ Use Hunk as a Git difftool:
144
+
145
+ ```bash
146
+ git config --global diff.tool hunk
147
+ git config --global difftool.hunk.cmd 'hunk difftool "$LOCAL" "$REMOTE" "$MERGED"'
148
+ ```
149
+
73
150
  ## Configuration
74
151
 
75
152
  Hunk reads layered TOML config with this precedence:
@@ -77,12 +154,10 @@ Hunk reads layered TOML config with this precedence:
77
154
  1. built-in defaults
78
155
  2. global config: `$XDG_CONFIG_HOME/hunk/config.toml` or `~/.config/hunk/config.toml`
79
156
  3. repo-local config: `.hunk/config.toml`
80
- 4. command-specific sections like `[git]`, `[diff]`, `[show]`, `[stash-show]`, `[patch]`, `[difftool]`
157
+ 4. command-specific sections like `[diff]`, `[show]`, `[stash-show]`, `[patch]`, `[difftool]`
81
158
  5. `[pager]` when Hunk is running in pager mode
82
159
  6. explicit CLI flags
83
160
 
84
- When you change persistent view settings inside Hunk, it writes them back to `.hunk/config.toml` in the current repo when possible, or to the global config file outside a repo.
85
-
86
161
  Example:
87
162
 
88
163
  ```toml
@@ -101,16 +176,7 @@ line_numbers = false
101
176
  mode = "split"
102
177
  ```
103
178
 
104
- CLI overrides are available when you want one-off or pager-specific behavior:
105
-
106
- ```bash
107
- hunk diff --mode split --line-numbers
108
- hunk show HEAD~1 --theme paper
109
- hunk patch - --mode stack --no-line-numbers
110
- hunk diff before.ts after.ts --theme paper --wrap
111
- ```
112
-
113
- Supported persistent CLI overrides:
179
+ Supported one-off CLI overrides:
114
180
 
115
181
  - `--mode <auto|split|stack>`
116
182
  - `--theme <theme>`
@@ -119,11 +185,11 @@ Supported persistent CLI overrides:
119
185
  - `--hunk-headers` / `--no-hunk-headers`
120
186
  - `--agent-notes` / `--no-agent-notes`
121
187
 
122
- ## Agent sidecar format
188
+ ## Agent context sidecar
123
189
 
124
190
  Use `--agent-context <file>` to load a JSON sidecar and show agent rationale next to the diff.
125
191
 
126
- The order of `files` in the sidecar is significant. Hunk uses that order for the sidebar and main review stream so an agent can tell a story instead of relying on raw patch order.
192
+ The order of `files` in the sidecar is significant. Hunk uses that order for the sidebar and the main review stream so an agent can present a review narrative instead of raw patch order.
127
193
 
128
194
  ```json
129
195
  {
@@ -142,130 +208,40 @@ The order of `files` in the sidecar is significant. Hunk uses that order for the
142
208
  "confidence": "high"
143
209
  }
144
210
  ]
145
- },
146
- {
147
- "path": "src/ui/App.tsx",
148
- "summary": "Presents the new workflow after the loader changes.",
149
- "annotations": [
150
- {
151
- "newRange": [90, 136],
152
- "summary": "Uses the normalized model in the review shell.",
153
- "rationale": "The reader should inspect this after understanding the loader changes.",
154
- "tags": ["ui"],
155
- "confidence": "medium"
156
- }
157
- ]
158
211
  }
159
212
  ]
160
213
  }
161
214
  ```
162
215
 
163
- Files omitted from the sidecar keep their original diff order and appear after the explicitly ordered files.
164
-
165
- ## Codex workflow
166
-
167
- For Codex-driven changes, keep a transient sidecar at `.hunk/latest.json` and load it during review:
216
+ For local agent-driven review, keep a transient sidecar at `.hunk/latest.json` and load it with:
168
217
 
169
218
  ```bash
170
219
  hunk diff --agent-context .hunk/latest.json
171
220
  ```
172
221
 
173
- Suggested pattern:
174
-
175
- - Codex makes code changes.
176
- - Codex refreshes `.hunk/latest.json` with a concise changeset summary, file summaries, and hunk-level rationale.
177
- - You open `hunk diff`, `hunk diff --staged`, or `hunk show <ref>` with that sidecar.
178
-
179
- Keep the sidecar concise. It should explain why a hunk exists, what risk to review, and how the files fit together. It should not narrate obvious syntax edits line by line.
180
-
181
- ## Comparison
182
-
183
- ### Feature comparison
184
-
185
- | Capability | hunk | difftastic | delta | diff |
186
- | --- | --- | --- | --- | --- |
187
- | Dedicated interactive review UI | ✅ | ❌ | ❌ | ❌ |
188
- | Multi-file review stream with navigation sidebar | ✅ | ❌ | ❌ | ❌ |
189
- | Agent / AI rationale sidecar | ✅ | ❌ | ❌ | ❌ |
190
- | Split diffs | ✅ | ✅ | ✅ | ✅ |
191
- | Stacked diffs | ✅ | ✅ | ✅ | ✅ |
192
- | Auto responsive layouts | ✅ | ❌ | ❌ | ❌ |
193
- | Themes | ✅ | ❌ | ✅ | ❌ |
194
- | Syntax highlighting | ✅ | ✅ | ✅ | ❌ |
195
- | Syntax-aware / structural diffing | ❌ | ✅ | ❌ | ❌ |
196
- | Mouse support inside the diff viewer | ✅ | ❌ | ❌ | ❌ |
197
- | Runtime toggles for wrapping / line numbers / hunk metadata | ✅ | ❌ | ❌ | ❌ |
198
- | Pager-compatible mode | ✅ | ✅ | ✅ | ✅ |
199
-
200
- ### Local timing snapshot
201
-
202
- These numbers are **not a universal benchmark**. They are a quick local comparison from one Linux machine using tmux panes, measuring **time until a changed marker first became visible** on the same 120-line TypeScript file pair.
203
-
204
- Commands used:
205
-
206
- - `hunk diff before.ts after.ts`
207
- - `difft --display side-by-side before.ts after.ts`
208
- - `delta --paging=never before.ts after.ts`
209
- - `diff -u before.ts after.ts`
222
+ ## Development
210
223
 
211
- | Tool | Avg first-visible changed output |
212
- | --- | ---: |
213
- | `diff` | ~37 ms |
214
- | `delta --paging=never` | ~35 ms |
215
- | `hunk diff` | ~219 ms |
216
- | `difft --display side-by-side` | ~266 ms |
217
-
218
- Interpretation:
219
-
220
- - `diff` and `delta` are fastest here because they emit plain diff text and exit.
221
- - `hunk` pays extra startup cost for an interactive terminal UI, syntax highlighting, navigation state, and optional agent context.
222
- - `difftastic` pays extra cost for syntax-aware / structural diffing.
223
- - For larger review sessions, Hunk is optimized for **navigating and understanding** a changeset, not just dumping the quickest possible patch text.
224
-
225
- ## Git integration
226
-
227
- For full-screen review, you can invoke Hunk directly with Git-shaped commands:
224
+ Install dependencies:
228
225
 
229
226
  ```bash
230
- hunk diff
231
- hunk diff --staged
232
- hunk diff main...feature
233
- hunk show
234
- hunk show HEAD~1
235
- hunk stash show
236
- ```
237
-
238
- Use Hunk as the default Git pager when you want it to behave like a normal pager under `git diff` / `git show`:
239
-
240
- ```bash
241
- git config --global core.pager 'hunk patch -'
227
+ bun install
242
228
  ```
243
229
 
244
- Or scope it just to `git diff` and `git show`:
230
+ Validate a change:
245
231
 
246
232
  ```bash
247
- git config --global pager.diff 'hunk patch -'
248
- git config --global pager.show 'hunk patch -'
233
+ bun run typecheck
234
+ bun test
235
+ bun run test:tty-smoke
249
236
  ```
250
237
 
251
- When Hunk reads a patch from stdin, it automatically switches to pager-style chrome, strips Git's color escape sequences before parsing, and binds keyboard input to the controlling terminal so it works correctly as a Git pager.
252
-
253
- Then:
238
+ Build the npm runtime bundle used for publishing:
254
239
 
255
240
  ```bash
256
- git diff
257
- git show HEAD
241
+ bun run build:npm
242
+ bun run check:pack
258
243
  ```
259
244
 
260
- If you want Git to launch Hunk as a difftool for file-to-file comparisons:
245
+ ## License
261
246
 
262
- ```bash
263
- git config --global diff.tool hunk
264
- git config --global difftool.hunk.cmd 'hunk difftool "$LOCAL" "$REMOTE" "$MERGED"'
265
- ```
266
- e comparisons:
267
-
268
- ```bash
269
- git config --global diff.tool hunk
270
- git config --global difftool.hunk.cmd 'hunk difftool "$LOCAL" "$REMOTE" "$MERGED"'
271
- ```
247
+ [MIT](LICENSE)
package/bin/hunk.cjs ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require("node:child_process");
4
+ const path = require("node:path");
5
+
6
+ const entrypoint = path.join(__dirname, "..", "dist", "npm", "main.js");
7
+
8
+ let bunBinary;
9
+
10
+ try {
11
+ bunBinary = require.resolve("bun/bin/bun.exe");
12
+ } catch (error) {
13
+ console.error(
14
+ "Failed to resolve the bundled Bun runtime. Try reinstalling hunkdiff.",
15
+ );
16
+ if (error && error.message) {
17
+ console.error(error.message);
18
+ }
19
+ process.exit(1);
20
+ }
21
+
22
+ const result = spawnSync(bunBinary, [entrypoint, ...process.argv.slice(2)], {
23
+ stdio: "inherit",
24
+ env: process.env,
25
+ });
26
+
27
+ if (result.error) {
28
+ console.error(result.error.message);
29
+ process.exit(1);
30
+ }
31
+
32
+ process.exit(typeof result.status === "number" ? result.status : 1);
package/dist/npm/main.js CHANGED
@@ -69515,7 +69515,6 @@ function renderCliHelp() {
69515
69515
  " hunk patch [file] review a patch file or stdin",
69516
69516
  " hunk pager general Git pager wrapper with diff detection",
69517
69517
  " hunk difftool <left> <right> [path] review Git difftool file pairs",
69518
- " hunk git [range] legacy alias for git diff-style review",
69519
69518
  "",
69520
69519
  "Options:",
69521
69520
  " -h, --help show help",
@@ -69626,28 +69625,6 @@ async function parseShowCommand(tokens, argv) {
69626
69625
  options: buildCommonOptions(parsedOptions, argv)
69627
69626
  };
69628
69627
  }
69629
- async function parseGitCommand(tokens, argv) {
69630
- const { commandTokens, pathspecs } = splitPathspecArgs(tokens);
69631
- const command = createCommand2("git", "legacy alias for Git diff-style review").option("--staged", "show staged changes instead of the working tree").option("--cached", "alias for --staged").argument("[range]");
69632
- let parsedRange;
69633
- let parsedOptions = {};
69634
- command.action((range, options) => {
69635
- parsedRange = range;
69636
- parsedOptions = options;
69637
- });
69638
- if (commandTokens.includes("--help") || commandTokens.includes("-h")) {
69639
- return { kind: "help", text: `${command.helpInformation().trimEnd()}
69640
- ` };
69641
- }
69642
- await parseStandaloneCommand(command, commandTokens);
69643
- return {
69644
- kind: "git",
69645
- range: parsedRange,
69646
- staged: Boolean(parsedOptions.staged) || Boolean(parsedOptions.cached),
69647
- pathspecs: pathspecs.length > 0 ? pathspecs : undefined,
69648
- options: buildCommonOptions(parsedOptions, argv)
69649
- };
69650
- }
69651
69628
  async function parsePatchCommand(tokens, argv) {
69652
69629
  const command = createCommand2("patch", "review a patch file, or read a patch from stdin").argument("[file]");
69653
69630
  let parsedFile;
@@ -69758,8 +69735,6 @@ async function parseCli(argv) {
69758
69735
  return parseDiffCommand(rest, argv);
69759
69736
  case "show":
69760
69737
  return parseShowCommand(rest, argv);
69761
- case "git":
69762
- return parseGitCommand(rest, argv);
69763
69738
  case "patch":
69764
69739
  return parsePatchCommand(rest, argv);
69765
69740
  case "pager":
package/package.json CHANGED
@@ -1,18 +1,17 @@
1
1
  {
2
2
  "name": "hunkdiff",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Desktop-inspired terminal diff viewer for understanding agent-authored changesets.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.10",
7
7
  "bin": {
8
- "hunk": "dist/npm/main.js"
8
+ "hunk": "./bin/hunk.cjs"
9
9
  },
10
10
  "files": [
11
+ "bin",
11
12
  "dist/npm",
12
13
  "README.md",
13
- "LICENSE",
14
- "CONTRIBUTING.md",
15
- "SECURITY.md"
14
+ "LICENSE"
16
15
  ],
17
16
  "scripts": {
18
17
  "start": "bun run src/main.tsx",
@@ -46,7 +45,7 @@
46
45
  "url": "https://github.com/modem-dev/hunk/issues"
47
46
  },
48
47
  "engines": {
49
- "bun": ">=1.3.10"
48
+ "node": ">=18"
50
49
  },
51
50
  "publishConfig": {
52
51
  "access": "public"
@@ -60,6 +59,7 @@
60
59
  "@opentui/core": "^0.1.88",
61
60
  "@opentui/react": "^0.1.88",
62
61
  "@pierre/diffs": "^1.1.0",
62
+ "bun": "^1.3.10",
63
63
  "commander": "^14.0.3",
64
64
  "diff": "^8.0.3",
65
65
  "parse-diff": "^0.11.1",