git-hunk 0.3.0__tar.gz → 0.4.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: git-hunk
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Non-interactive, programmatic git hunk staging with durable Hunk IDs.
5
5
  Project-URL: Homepage, https://github.com/wkentaro/git-hunk
6
6
  Project-URL: Issues, https://github.com/wkentaro/git-hunk/issues
@@ -41,6 +41,9 @@ Conditional IDs.
41
41
 
42
42
  <img src="https://raw.githubusercontent.com/wkentaro/git-hunk/main/assets/teaser.png" alt="git-hunk teaser" width="800">
43
43
 
44
+ [Comparison](#comparison) • [Install](#install) • [For AI agents](#for-ai-agents) •
45
+ [Quick start](#quick-start) • [Usage](#usage) • [JSON output](#json-output)
46
+
44
47
  ## Why?
45
48
 
46
49
  `git add -p` requires interactive input. That makes it unusable for:
@@ -52,38 +55,46 @@ Conditional IDs.
52
55
  `git-hunk` solves this by assigning each staged or unstaged Hunk a durable ID
53
56
  and exposing simple stage/unstage/discard commands.
54
57
 
55
- ## Eval
58
+ ## Comparison
59
+
60
+ | | Interactive | Programmatic | Hunk IDs | Line-level control | JSON output |
61
+ | ---------------- | ----------- | ------------ | -------- | ------------------ | ----------- |
62
+ | `git add -p` | Yes | No | No | Yes | No |
63
+ | `git add <file>` | No | Yes | No | No | No |
64
+ | **`git-hunk`** | **No** | **Yes** | **Yes** | **Yes** | **Yes** |
65
+
66
+ ### Eval
56
67
 
57
68
  One agent (Claude Code 2.1.226, `claude-sonnet-5`, reasoning effort `high`)
58
- attempted the same eight tasks from identical repository state, three times per
69
+ attempted the same eight tasks from identical repository state, five times per
59
70
  variant: organize a dirty working tree into correct, focused commits, once
60
71
  following git-hunk's bundled skills and once restricted to bare Git. The
61
- [checked-in eval harness](https://github.com/wkentaro/git-hunk/tree/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval)
72
+ [checked-in eval harness](https://github.com/wkentaro/git-hunk/tree/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval)
62
73
  grades the exact resulting repository state — commit partition and order, final
63
74
  tree, index, and leftovers. This table records the qualifying run; `make eval`
64
75
  reruns the protocol and prints a table in the same format.
65
76
 
66
- | Task | git-hunk | bare Git |
77
+ | Task | git-hunk | bare Git |
67
78
  | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------- |
68
- | [split_refactor_vs_feature](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/split_refactor_vs_feature.py) | PASS 3/3 · 3c · 4t | PASS 3/3 · 3c [2-3] · 4t [3-4] |
69
- | [separate_mixed_hunks](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/separate_mixed_hunks.py) | PASS 3/3 · 3c [3-4] · 4t [4-5] | MIXED 1/3 partition · 15c [11-20] · 16t [12-21] |
70
- | [drop_debug_lines](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/drop_debug_lines.py) | PASS 3/3 · 3c · 4t | MIXED 2/3 partition · 16c [8-19] · 17t [9-20] |
71
- | [protect_unrelated_work](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/protect_unrelated_work.py) | PASS 3/3 · 3c · 4t | PASS 3/3 · 2c [2-3] · 3t [3-4] |
72
- | [split_single_hunk](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/split_single_hunk.py) | PASS 3/3 · 5c [3-5] · 6t [4-6] | PASS 3/3 · 14c [9-15] · 15t [10-16] |
73
- | [separate_formatter_noise](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/separate_formatter_noise.py) | PASS 3/3 · 4c [3-4] · 5t [4-5] | PASS 3/3 · 16c [9-30] · 17t [10-31] |
74
- | [pick_duplicate_hunk](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/pick_duplicate_hunk.py) | PASS 3/3 · 3c · 4t | PASS 3/3 · 10c [9-17] · 11t [10-18] |
75
- | [commit_parseable_subset](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/commit_parseable_subset.py) | PASS 3/3 · 5c [3-6] · 6t [4-7] | PASS 3/3 · 14c [12-15] · 15t [13-16] |
76
- | **total** | **8/8 · 29c [24-31] · 37t [32-39]** | **6/8 (2 mixed) · 90c [62-122] · 98t [70-130]** |
77
-
78
- `c` = tool calls, `t` = turns; a cell reports the median of its three repeats
79
+ | [split_refactor_vs_feature](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/split_refactor_vs_feature.py) | PASS 5/5 · 3c · 4t | PASS 5/5 · 3c [2-5] · 4t [3-6] |
80
+ | [separate_mixed_hunks](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/separate_mixed_hunks.py) | PASS 5/5 · 3c · 4t | PASS 5/5 · 8c [7-12] · 9t [8-13] |
81
+ | [drop_debug_lines](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/drop_debug_lines.py) | PASS 5/5 · 3c [3-4] · 4t [4-5] | MIXED 4/5 partition · 8c [7-27] · 9t [8-28] |
82
+ | [protect_unrelated_work](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/protect_unrelated_work.py) | PASS 5/5 · 3c · 4t | PASS 5/5 · 3c [3-4] · 4t [4-5] |
83
+ | [split_single_hunk](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/split_single_hunk.py) | PASS 5/5 · 4c [3-4] · 5t [4-5] | PASS 5/5 · 11c [9-19] · 12t [10-20] |
84
+ | [separate_formatter_noise](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/separate_formatter_noise.py) | PASS 5/5 · 4c [4-6] · 5t [5-7] | PASS 5/5 · 13c [12-16] · 14t [13-17] |
85
+ | [pick_duplicate_hunk](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/pick_duplicate_hunk.py) | PASS 5/5 · 3c · 4t | PASS 5/5 · 8c [7-25] · 9t [8-26] |
86
+ | [commit_parseable_subset](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/commit_parseable_subset.py) | PASS 5/5 · 4c [3-5] · 5t [4-6] | MIXED 4/5 partition · 13c [10-23] · 14t [11-24] |
87
+ | **total** | **8/8 · 27c [25-31] · 35t [33-39]** | **6/8 (2 mixed) · 67c [57-131] · 75t [65-139]** |
88
+
89
+ `c` = tool calls, `t` = turns; a cell reports the median of its five repeats
79
90
  with the observed range in brackets, dropped where every repeat agreed, and the
80
- pass column counts passing repeats. `MIXED j/3` means the variant passed j of its
81
- three repeats, and `partition` names the failure: the commits made do not match
91
+ pass column counts passing repeats. `MIXED j/5` means the variant passed j of its
92
+ five repeats, and `partition` names the failure: the commits made do not match
82
93
  the required change groups. The cost column is omitted: bare Git runs second in
83
94
  each pair and partly reads the prompt cache the git-hunk run warmed, so raw costs
84
95
  are not order-neutral
85
- ([#224](https://github.com/wkentaro/git-hunk/issues/224)). Three samples per task
86
- variant, dated 2026-08-09 at commit `9829b7a`.
96
+ ([#224](https://github.com/wkentaro/git-hunk/issues/224)). Five samples per task
97
+ variant, dated 2026-08-10 at commit `0ef14be`.
87
98
 
88
99
  ## Install
89
100
 
@@ -303,32 +314,32 @@ body; `show --json` adds a structured `lines` array. A `show --json` hunk
303
314
  }
304
315
  ```
305
316
 
306
- | Field | Type | Description |
317
+ | Field | Type | Description |
307
318
  | ---------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
308
- | `schema_version` | int | Envelope version; bumped on any incompatible change to the shape below. |
309
- | `hunks` | array | The hunks (empty array when there are no changes). |
310
- | `id` | string | Full canonical SHA-256 Hunk ID; empty for an `untracked` entry, which no command can address. Human output uses a unique prefix of at least seven characters. |
311
- | `id_stability` | string | `stable` or `conditional`. An untracked inventory entry reports `stable`, but its empty `id` remains unaddressable. |
312
- | `file` | union | Repository path of the changed file, as a byte-safe `{text\|bytes}` union (see below). |
313
- | `status` | string | One of `staged`, `unstaged`, `untracked`. |
314
- | `change_kind` | string | Git status letter: `A` added, `D` deleted, `M` modified, `T` typechange (`R`/`C` reserved and currently rejected). Always present. |
315
- | `a_mode` | string \| null | 6-digit octal git mode on the pre-image side; `null` when that side does not exist. |
316
- | `b_mode` | string \| null | 6-digit octal git mode on the post-image side; `null` when that side does not exist. |
317
- | `binary` | bool | Whether the change is binary. Always present. |
318
- | `header` | string \| null | The bare `@@ -a,b +c,d @@` range for a text hunk; `null` for a whole-file hunk (binary, mode-only, type, or empty tracked file change) or an `untracked` inventory entry. |
319
- | `context_before` | union \| null | The function/section name after a text hunk's `@@` header, as a `{text\|bytes}` union; `null` for a text hunk without a heading, a whole-file hunk, or an `untracked` inventory entry. |
320
- | `additions` | int | Number of added lines. |
321
- | `deletions` | int | Number of removed lines. |
322
- | `lines` | array | `show --json` only. The structured body; `[]` for a whole-file hunk. See below. |
319
+ | `schema_version` | int | Envelope version; bumped on any incompatible change to the shape below. |
320
+ | `hunks` | array | The hunks (empty array when there are no changes). |
321
+ | `id` | string | Full canonical SHA-256 Hunk ID; empty for an `untracked` entry, which no command can address. Human output uses a unique prefix of at least seven characters. |
322
+ | `id_stability` | string | `stable` or `conditional`. An untracked inventory entry reports `stable`, but its empty `id` remains unaddressable. |
323
+ | `file` | union | Repository path of the changed file, as a byte-safe `{text\|bytes}` union (see below). |
324
+ | `status` | string | One of `staged`, `unstaged`, `untracked`. |
325
+ | `change_kind` | string | Git status letter: `A` added, `D` deleted, `M` modified, `T` typechange (`R`/`C` reserved and currently rejected). Always present. |
326
+ | `a_mode` | string | null | 6-digit octal git mode on the pre-image side; `null` when that side does not exist. |
327
+ | `b_mode` | string | null | 6-digit octal git mode on the post-image side; `null` when that side does not exist. |
328
+ | `binary` | bool | Whether the change is binary. Always present. |
329
+ | `header` | string | null | The bare `@@ -a,b +c,d @@` range for a text hunk; `null` for a whole-file hunk (binary, mode-only, type, or empty tracked file change) or an `untracked` inventory entry. |
330
+ | `context_before` | union | null | The function/section name after a text hunk's `@@` header, as a `{text\|bytes}` union; `null` for a text hunk without a heading, a whole-file hunk, or an `untracked` inventory entry. |
331
+ | `additions` | int | Number of added lines. |
332
+ | `deletions` | int | Number of removed lines. |
333
+ | `lines` | array | `show --json` only. The structured body; `[]` for a whole-file hunk. See below. |
323
334
 
324
335
  A `lines` entry is `{ "n", "op", "content", "no_newline"? }`:
325
336
 
326
- | Field | Type | Description |
337
+ | Field | Type | Description |
327
338
  | ------------ | ------ | --------------------------------------------------------------------------------------------------- |
328
- | `n` | int | 1-based position within the hunk body — the index `-l` line selection uses. Counts every body line. |
329
- | `op` | string | `" "` context, `"+"` addition, `"-"` deletion. |
330
- | `content` | union | The line text **without** its leading op character, as a `{text\|bytes}` union. |
331
- | `no_newline` | bool | Present and `true` only when the line has no trailing newline; consumes no `n`. |
339
+ | `n` | int | 1-based position within the hunk body — the index `-l` line selection uses. Counts every body line. |
340
+ | `op` | string | `" "` context, `"+"` addition, `"-"` deletion. |
341
+ | `content` | union | The line text **without** its leading op character, as a `{text\|bytes}` union. |
342
+ | `no_newline` | bool | Present and `true` only when the line has no trailing newline; consumes no `n`. |
332
343
 
333
344
  Any field carrying arbitrary git/source bytes (`file`, `context_before`,
334
345
  `lines[].content`) is a byte-safe `{text | bytes}` union: `{"text": "..."}` for
@@ -339,14 +350,6 @@ Adding a new field is backward-compatible and does not change `schema_version`;
339
350
  renaming, removing, or changing the type of an existing field bumps it. (Before
340
351
  `schema_version` existed, `list --json` returned a bare array.)
341
352
 
342
- ## Comparison
343
-
344
- | | Interactive | Programmatic | Hunk IDs | Line-level control | JSON output |
345
- | ---------------- | ----------- | ------------ | -------- | ------------------ | ----------- |
346
- | `git add -p` | Yes | No | No | Yes | No |
347
- | `git add <file>` | No | Yes | No | No | No |
348
- | **`git-hunk`** | **No** | **Yes** | **Yes** | **Yes** | **Yes** |
349
-
350
353
  ## How it works
351
354
 
352
355
  1. Rejects detected rename, copy, and unmerged states.
@@ -13,6 +13,9 @@ Conditional IDs.
13
13
 
14
14
  <img src="assets/teaser.png" alt="git-hunk teaser" width="800">
15
15
 
16
+ [Comparison](#comparison) • [Install](#install) • [For AI agents](#for-ai-agents) •
17
+ [Quick start](#quick-start) • [Usage](#usage) • [JSON output](#json-output)
18
+
16
19
  ## Why?
17
20
 
18
21
  `git add -p` requires interactive input. That makes it unusable for:
@@ -24,38 +27,46 @@ Conditional IDs.
24
27
  `git-hunk` solves this by assigning each staged or unstaged Hunk a durable ID
25
28
  and exposing simple stage/unstage/discard commands.
26
29
 
27
- ## Eval
30
+ ## Comparison
31
+
32
+ | | Interactive | Programmatic | Hunk IDs | Line-level control | JSON output |
33
+ | ---------------- | ----------- | ------------ | -------- | ------------------ | ----------- |
34
+ | `git add -p` | Yes | No | No | Yes | No |
35
+ | `git add <file>` | No | Yes | No | No | No |
36
+ | **`git-hunk`** | **No** | **Yes** | **Yes** | **Yes** | **Yes** |
37
+
38
+ ### Eval
28
39
 
29
40
  One agent (Claude Code 2.1.226, `claude-sonnet-5`, reasoning effort `high`)
30
- attempted the same eight tasks from identical repository state, three times per
41
+ attempted the same eight tasks from identical repository state, five times per
31
42
  variant: organize a dirty working tree into correct, focused commits, once
32
43
  following git-hunk's bundled skills and once restricted to bare Git. The
33
- [checked-in eval harness](https://github.com/wkentaro/git-hunk/tree/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval)
44
+ [checked-in eval harness](https://github.com/wkentaro/git-hunk/tree/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval)
34
45
  grades the exact resulting repository state — commit partition and order, final
35
46
  tree, index, and leftovers. This table records the qualifying run; `make eval`
36
47
  reruns the protocol and prints a table in the same format.
37
48
 
38
- | Task | git-hunk | bare Git |
49
+ | Task | git-hunk | bare Git |
39
50
  | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------- |
40
- | [split_refactor_vs_feature](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/split_refactor_vs_feature.py) | PASS 3/3 · 3c · 4t | PASS 3/3 · 3c [2-3] · 4t [3-4] |
41
- | [separate_mixed_hunks](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/separate_mixed_hunks.py) | PASS 3/3 · 3c [3-4] · 4t [4-5] | MIXED 1/3 partition · 15c [11-20] · 16t [12-21] |
42
- | [drop_debug_lines](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/drop_debug_lines.py) | PASS 3/3 · 3c · 4t | MIXED 2/3 partition · 16c [8-19] · 17t [9-20] |
43
- | [protect_unrelated_work](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/protect_unrelated_work.py) | PASS 3/3 · 3c · 4t | PASS 3/3 · 2c [2-3] · 3t [3-4] |
44
- | [split_single_hunk](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/split_single_hunk.py) | PASS 3/3 · 5c [3-5] · 6t [4-6] | PASS 3/3 · 14c [9-15] · 15t [10-16] |
45
- | [separate_formatter_noise](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/separate_formatter_noise.py) | PASS 3/3 · 4c [3-4] · 5t [4-5] | PASS 3/3 · 16c [9-30] · 17t [10-31] |
46
- | [pick_duplicate_hunk](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/pick_duplicate_hunk.py) | PASS 3/3 · 3c · 4t | PASS 3/3 · 10c [9-17] · 11t [10-18] |
47
- | [commit_parseable_subset](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/commit_parseable_subset.py) | PASS 3/3 · 5c [3-6] · 6t [4-7] | PASS 3/3 · 14c [12-15] · 15t [13-16] |
48
- | **total** | **8/8 · 29c [24-31] · 37t [32-39]** | **6/8 (2 mixed) · 90c [62-122] · 98t [70-130]** |
49
-
50
- `c` = tool calls, `t` = turns; a cell reports the median of its three repeats
51
+ | [split_refactor_vs_feature](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/split_refactor_vs_feature.py) | PASS 5/5 · 3c · 4t | PASS 5/5 · 3c [2-5] · 4t [3-6] |
52
+ | [separate_mixed_hunks](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/separate_mixed_hunks.py) | PASS 5/5 · 3c · 4t | PASS 5/5 · 8c [7-12] · 9t [8-13] |
53
+ | [drop_debug_lines](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/drop_debug_lines.py) | PASS 5/5 · 3c [3-4] · 4t [4-5] | MIXED 4/5 partition · 8c [7-27] · 9t [8-28] |
54
+ | [protect_unrelated_work](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/protect_unrelated_work.py) | PASS 5/5 · 3c · 4t | PASS 5/5 · 3c [3-4] · 4t [4-5] |
55
+ | [split_single_hunk](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/split_single_hunk.py) | PASS 5/5 · 4c [3-4] · 5t [4-5] | PASS 5/5 · 11c [9-19] · 12t [10-20] |
56
+ | [separate_formatter_noise](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/separate_formatter_noise.py) | PASS 5/5 · 4c [4-6] · 5t [5-7] | PASS 5/5 · 13c [12-16] · 14t [13-17] |
57
+ | [pick_duplicate_hunk](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/pick_duplicate_hunk.py) | PASS 5/5 · 3c · 4t | PASS 5/5 · 8c [7-25] · 9t [8-26] |
58
+ | [commit_parseable_subset](https://github.com/wkentaro/git-hunk/blob/0ef14bef657e187b5ab7283b0dbd64751a02736f/eval/tasks/commit_parseable_subset.py) | PASS 5/5 · 4c [3-5] · 5t [4-6] | MIXED 4/5 partition · 13c [10-23] · 14t [11-24] |
59
+ | **total** | **8/8 · 27c [25-31] · 35t [33-39]** | **6/8 (2 mixed) · 67c [57-131] · 75t [65-139]** |
60
+
61
+ `c` = tool calls, `t` = turns; a cell reports the median of its five repeats
51
62
  with the observed range in brackets, dropped where every repeat agreed, and the
52
- pass column counts passing repeats. `MIXED j/3` means the variant passed j of its
53
- three repeats, and `partition` names the failure: the commits made do not match
63
+ pass column counts passing repeats. `MIXED j/5` means the variant passed j of its
64
+ five repeats, and `partition` names the failure: the commits made do not match
54
65
  the required change groups. The cost column is omitted: bare Git runs second in
55
66
  each pair and partly reads the prompt cache the git-hunk run warmed, so raw costs
56
67
  are not order-neutral
57
- ([#224](https://github.com/wkentaro/git-hunk/issues/224)). Three samples per task
58
- variant, dated 2026-08-09 at commit `9829b7a`.
68
+ ([#224](https://github.com/wkentaro/git-hunk/issues/224)). Five samples per task
69
+ variant, dated 2026-08-10 at commit `0ef14be`.
59
70
 
60
71
  ## Install
61
72
 
@@ -275,32 +286,32 @@ body; `show --json` adds a structured `lines` array. A `show --json` hunk
275
286
  }
276
287
  ```
277
288
 
278
- | Field | Type | Description |
289
+ | Field | Type | Description |
279
290
  | ---------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
280
- | `schema_version` | int | Envelope version; bumped on any incompatible change to the shape below. |
281
- | `hunks` | array | The hunks (empty array when there are no changes). |
282
- | `id` | string | Full canonical SHA-256 Hunk ID; empty for an `untracked` entry, which no command can address. Human output uses a unique prefix of at least seven characters. |
283
- | `id_stability` | string | `stable` or `conditional`. An untracked inventory entry reports `stable`, but its empty `id` remains unaddressable. |
284
- | `file` | union | Repository path of the changed file, as a byte-safe `{text\|bytes}` union (see below). |
285
- | `status` | string | One of `staged`, `unstaged`, `untracked`. |
286
- | `change_kind` | string | Git status letter: `A` added, `D` deleted, `M` modified, `T` typechange (`R`/`C` reserved and currently rejected). Always present. |
287
- | `a_mode` | string \| null | 6-digit octal git mode on the pre-image side; `null` when that side does not exist. |
288
- | `b_mode` | string \| null | 6-digit octal git mode on the post-image side; `null` when that side does not exist. |
289
- | `binary` | bool | Whether the change is binary. Always present. |
290
- | `header` | string \| null | The bare `@@ -a,b +c,d @@` range for a text hunk; `null` for a whole-file hunk (binary, mode-only, type, or empty tracked file change) or an `untracked` inventory entry. |
291
- | `context_before` | union \| null | The function/section name after a text hunk's `@@` header, as a `{text\|bytes}` union; `null` for a text hunk without a heading, a whole-file hunk, or an `untracked` inventory entry. |
292
- | `additions` | int | Number of added lines. |
293
- | `deletions` | int | Number of removed lines. |
294
- | `lines` | array | `show --json` only. The structured body; `[]` for a whole-file hunk. See below. |
291
+ | `schema_version` | int | Envelope version; bumped on any incompatible change to the shape below. |
292
+ | `hunks` | array | The hunks (empty array when there are no changes). |
293
+ | `id` | string | Full canonical SHA-256 Hunk ID; empty for an `untracked` entry, which no command can address. Human output uses a unique prefix of at least seven characters. |
294
+ | `id_stability` | string | `stable` or `conditional`. An untracked inventory entry reports `stable`, but its empty `id` remains unaddressable. |
295
+ | `file` | union | Repository path of the changed file, as a byte-safe `{text\|bytes}` union (see below). |
296
+ | `status` | string | One of `staged`, `unstaged`, `untracked`. |
297
+ | `change_kind` | string | Git status letter: `A` added, `D` deleted, `M` modified, `T` typechange (`R`/`C` reserved and currently rejected). Always present. |
298
+ | `a_mode` | string | null | 6-digit octal git mode on the pre-image side; `null` when that side does not exist. |
299
+ | `b_mode` | string | null | 6-digit octal git mode on the post-image side; `null` when that side does not exist. |
300
+ | `binary` | bool | Whether the change is binary. Always present. |
301
+ | `header` | string | null | The bare `@@ -a,b +c,d @@` range for a text hunk; `null` for a whole-file hunk (binary, mode-only, type, or empty tracked file change) or an `untracked` inventory entry. |
302
+ | `context_before` | union | null | The function/section name after a text hunk's `@@` header, as a `{text\|bytes}` union; `null` for a text hunk without a heading, a whole-file hunk, or an `untracked` inventory entry. |
303
+ | `additions` | int | Number of added lines. |
304
+ | `deletions` | int | Number of removed lines. |
305
+ | `lines` | array | `show --json` only. The structured body; `[]` for a whole-file hunk. See below. |
295
306
 
296
307
  A `lines` entry is `{ "n", "op", "content", "no_newline"? }`:
297
308
 
298
- | Field | Type | Description |
309
+ | Field | Type | Description |
299
310
  | ------------ | ------ | --------------------------------------------------------------------------------------------------- |
300
- | `n` | int | 1-based position within the hunk body — the index `-l` line selection uses. Counts every body line. |
301
- | `op` | string | `" "` context, `"+"` addition, `"-"` deletion. |
302
- | `content` | union | The line text **without** its leading op character, as a `{text\|bytes}` union. |
303
- | `no_newline` | bool | Present and `true` only when the line has no trailing newline; consumes no `n`. |
311
+ | `n` | int | 1-based position within the hunk body — the index `-l` line selection uses. Counts every body line. |
312
+ | `op` | string | `" "` context, `"+"` addition, `"-"` deletion. |
313
+ | `content` | union | The line text **without** its leading op character, as a `{text\|bytes}` union. |
314
+ | `no_newline` | bool | Present and `true` only when the line has no trailing newline; consumes no `n`. |
304
315
 
305
316
  Any field carrying arbitrary git/source bytes (`file`, `context_before`,
306
317
  `lines[].content`) is a byte-safe `{text | bytes}` union: `{"text": "..."}` for
@@ -311,14 +322,6 @@ Adding a new field is backward-compatible and does not change `schema_version`;
311
322
  renaming, removing, or changing the type of an existing field bumps it. (Before
312
323
  `schema_version` existed, `list --json` returned a bare array.)
313
324
 
314
- ## Comparison
315
-
316
- | | Interactive | Programmatic | Hunk IDs | Line-level control | JSON output |
317
- | ---------------- | ----------- | ------------ | -------- | ------------------ | ----------- |
318
- | `git add -p` | Yes | No | No | Yes | No |
319
- | `git add <file>` | No | Yes | No | No | No |
320
- | **`git-hunk`** | **No** | **Yes** | **Yes** | **Yes** | **Yes** |
321
-
322
325
  ## How it works
323
326
 
324
327
  1. Rejects detected rename, copy, and unmerged states.
@@ -1,3 +1,5 @@
1
1
  import importlib.metadata
2
2
 
3
+ __all__ = ["__version__"]
4
+
3
5
  __version__ = importlib.metadata.version("git-hunk")