pi-hunk 0.0.0 → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-07-18
4
+
5
+ Initial public release of pi-hunk:
6
+
7
+ - Review agent-authored changes in a persistent Hunk overlay inside Pi.
8
+ - Open reviews automatically after successful changes or follow them live.
9
+ - Return fresh inline comments to Pi through the read-only `hunk_review` handoff.
10
+ - Review working-copy changes, commits, staged changes, and stashes across supported version-control
11
+ systems.
12
+ - Configure review policy, layout, and shortcuts per trusted project.
13
+
14
+ [0.1.0]: https://github.com/igshehata/pi-hunk/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Islam Shehata
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1 +1,201 @@
1
- # pi-hunk package
1
+ <div align="center">
2
+
3
+ # pi-hunk
4
+
5
+ **A native Hunk review loop for the Pi coding agent.**
6
+
7
+ Review agent-authored changes, leave precise inline notes, and send them back to Pi—without leaving
8
+ your terminal session or managing an external pane.
9
+
10
+ [![CI](https://github.com/igshehata/pi-hunk/actions/workflows/ci.yml/badge.svg)](https://github.com/igshehata/pi-hunk/actions/workflows/ci.yml)
11
+ [![npm](https://img.shields.io/npm/v/pi-hunk?logo=npm&color=cb3837)](https://www.npmjs.com/package/pi-hunk)
12
+ [![Node](https://img.shields.io/node/v/pi-hunk?logo=nodedotjs&color=339933)](package.json)
13
+ [![License: MIT](https://img.shields.io/badge/license-MIT-3b82f6.svg)](LICENSE)
14
+
15
+ [Install](#installation) · [Quick start](#quick-start) · [Commands](#commands) ·
16
+ [Configuration](#configuration) · [Support](#support)
17
+
18
+ </div>
19
+
20
+ ## Why pi-hunk?
21
+
22
+ Coding agents are fast; reviewing their output should not break the loop. Pi-hunk embeds
23
+ [Hunk](https://github.com/modem-dev/hunk) as a persistent, Pi-owned overlay and turns review
24
+ comments into structured feedback for [Pi](https://github.com/earendil-works/pi).
25
+
26
+ ```text
27
+ Pi changes code → Hunk opens → You annotate → Notes return to Pi → Pi fixes
28
+ ```
29
+
30
+ | Capability | What it gives you |
31
+ | ----------------------------- | --------------------------------------------------------------------------------- |
32
+ | **Native overlay** | Hunk lives inside Pi—no tmux, pane manager, or takeover mode. |
33
+ | **Persistent review** | Hide and restore a review without losing position, selection, or comments. |
34
+ | **Human-in-the-loop handoff** | The read-only `hunk_review` tool returns your notes to the active agent run. |
35
+ | **Automatic or live review** | Open after successful mutations, or watch live from the first mutation attempt. |
36
+ | **Real terminal behavior** | Mouse, scrolling, resize, colors, comments, and keyboard input survive embedding. |
37
+ | **VCS-neutral launch** | Hunk detects Git, Jujutsu, or Sapling; pi-hunk does not second-guess it. |
38
+
39
+ > [!IMPORTANT] Pi-hunk reads Hunk comments but never creates, edits, applies, resolves, or deletes
40
+ > them. You remain in control of the review.
41
+
42
+ ## Installation
43
+
44
+ ### Requirements
45
+
46
+ - [Pi](https://github.com/earendil-works/pi) **0.80+**
47
+ - [Hunk](https://github.com/modem-dev/hunk) **0.17+** available on `PATH`
48
+ - Node.js **22.19+**
49
+ - macOS arm64, or glibc Linux x64/arm64
50
+
51
+ Install the published Pi package:
52
+
53
+ ```bash
54
+ pi install npm:pi-hunk
55
+ ```
56
+
57
+ Then reload Pi:
58
+
59
+ ```text
60
+ /reload
61
+ ```
62
+
63
+ ## Quick start
64
+
65
+ 1. Ask Pi to make a code change.
66
+ 2. Pi-hunk opens after the run using the default `after-run` policy.
67
+ 3. Review the diff in Hunk and leave inline comments.
68
+ 4. Press <kbd>Ctrl</kbd>+<kbd>Space</kbd>, then <kbd>H</kbd> to hide Hunk and submit fresh notes (or
69
+ approve when there are no new notes).
70
+ 5. Pi receives each unseen note through `hunk_review`, addresses them, and reopens review when
71
+ needed.
72
+
73
+ ### Default shortcuts
74
+
75
+ | Chord | Action |
76
+ | --------------------------------------------------- | ---------------------------------------------------- |
77
+ | <kbd>Ctrl</kbd>+<kbd>Space</kbd>, then <kbd>H</kbd> | Open, hide, or restore the persistent review overlay |
78
+ | <kbd>Ctrl</kbd>+<kbd>Space</kbd>, then <kbd>S</kbd> | Open, hide, or restore the `hunk show` review |
79
+
80
+ Pi-hunk registers only its dedicated prefix with Pi, then captures the configured action hotkey. The
81
+ same chords work while Pi has focus and while Hunk owns the overlay. Change the prefix or either
82
+ hotkey from `/hunk config` by pressing the actual key—identifiers are never entered as free text.
83
+
84
+ ## Review workflow
85
+
86
+ Pi-hunk opens one managed Hunk review at a time. Hiding it preserves the review position, selection,
87
+ and comments. `/hunk close`, Hunk exit, or a Pi session boundary ends the review.
88
+
89
+ The agent-facing `hunk_review` tool blocks until review finishes:
90
+
91
+ - **Hide with new comments:** submit only notes not previously returned in this loaded Pi extension.
92
+ - **Hide with no new comments:** return `approved`; this is the human's no-new-findings signal.
93
+ - **Close Hunk or press `Q`:** cancel the wait rather than report approval.
94
+
95
+ Automatic review is deliberately mutation-driven. Conversation-only turns, read-only tools, and
96
+ out-of-band workspace changes do not open Hunk. `live` opens at the first mutation preflight so you
97
+ can watch the tool, but only successful tool completions count as review evidence. If no mutation
98
+ succeeds, pi-hunk closes only the early surface it created for that run and leaves pre-existing or
99
+ manual Hunk sessions alone. `approved`, `/hunk close`, and a clean Hunk exit suppress same-run
100
+ auto-open; a non-zero Hunk crash can still be recovered by the live policy.
101
+
102
+ ## Commands
103
+
104
+ | Command | Purpose |
105
+ | ----------------------------------- | -------------------------------------------------------- |
106
+ | `/hunk` | Open the configured watched working-copy diff |
107
+ | `/hunk <target>` | Review a Git ref or jj/Sapling revset with `hunk diff` |
108
+ | `/hunk show [target]` | Review the last commit or a specific revision |
109
+ | `/hunk staged` | Review Git staged changes |
110
+ | `/hunk stash show [ref]` | Review a Git stash |
111
+ | `/hunk toggle` | Show or hide the persistent overlay |
112
+ | `/hunk close` | Terminate the managed Hunk process |
113
+ | `/hunk status` | Report policy, layout, session, notes, and diagnostics |
114
+ | `/hunk feedback` | Review, then send fresh notes to the agent as a new turn |
115
+ | `/hunk review off\|after-run\|live` | Set the trusted project's automatic-review policy |
116
+ | `/hunk config` | Open the auto-saving project configuration UI |
117
+ | `/hunk config restore` | Remove project overrides and restore inherited defaults |
118
+
119
+ Hunk's `patch`, `pager`, and `difftool` entrypoints require external stdin or file-pair integration.
120
+ Pi-hunk intentionally rejects them; run those commands directly in a terminal.
121
+
122
+ ## Review policies
123
+
124
+ | Policy | Behavior |
125
+ | ----------- | ---------------------------------------------------------------------------- |
126
+ | `off` | Never open automatically; commands and shortcuts remain available. |
127
+ | `after-run` | Open after a successful coding mutation when the agent settles. **Default.** |
128
+ | `live` | Open on the first coding mutation preflight and follow successful edits. |
129
+
130
+ Change policy interactively with `/hunk config` or directly:
131
+
132
+ ```text
133
+ /hunk review live
134
+ ```
135
+
136
+ ## Git, Jujutsu, and Sapling
137
+
138
+ Pi-hunk launches Hunk from Pi's current workspace and passes supported arguments through unchanged.
139
+ Hunk remains responsible for repository detection, revision semantics, and diff loading.
140
+
141
+ ```text
142
+ /hunk # detected VCS working-copy changes
143
+ /hunk show HEAD~1 # Git revision
144
+ /hunk show @- # jj revision
145
+ /hunk "trunk()..@" # jj/Sapling revset
146
+ /hunk staged # Git only
147
+ /hunk stash show stash@{0} # Git only
148
+ ```
149
+
150
+ Hunk's own `vcs = "git" | "jj" | "sl"` setting can override auto-detection. Future VCS adapters in
151
+ Hunk require no pi-hunk configuration change.
152
+
153
+ ## Configuration
154
+
155
+ Run `/hunk config` in a trusted project. Every selection saves immediately to `.pi/hunk.json`; there
156
+ is no Save step or scope picker. **Restore defaults** removes the project file after confirmation so
157
+ global and shipped values apply again.
158
+
159
+ A typical sparse project configuration:
160
+
161
+ ```json
162
+ {
163
+ "review": "live",
164
+ "overlay": {
165
+ "layout": "right",
166
+ "experimentalPiWrap": false
167
+ },
168
+ "bindings": {
169
+ "prefix": "ctrl+space",
170
+ "toggle": "h",
171
+ "show": "s"
172
+ }
173
+ }
174
+ ```
175
+
176
+ Configuration precedence, from lowest to highest:
177
+
178
+ ```text
179
+ shipped defaults → ~/.pi/agent/hunk.json → trusted .pi/hunk.json → PI_HUNK_REVIEW
180
+ ```
181
+
182
+ Available layouts are `full`, `left`, `right`, and `float`. Optional Pi wrapping keeps Pi visible in
183
+ the remaining half of a left or right layout.
184
+
185
+ Pi-hunk does not own Hunk's theme, transparency, presentation, or keybindings. Configure those in
186
+ Hunk's `~/.config/hunk/config.toml` or repository-local `.hunk/config.toml`.
187
+
188
+ ## Support
189
+
190
+ - Reproducible bug:
191
+ [open a bug report](https://github.com/igshehata/pi-hunk/issues/new?template=bug.yml)
192
+ - Product idea:
193
+ [request a feature](https://github.com/igshehata/pi-hunk/issues/new?template=feature.yml)
194
+ - Security issue: follow the private process in [SECURITY.md](SECURITY.md)
195
+
196
+ When reporting terminal behavior, include your platform, terminal, Pi version, Hunk version, and
197
+ VCS.
198
+
199
+ ## License
200
+
201
+ Released under the [MIT License](LICENSE).
@@ -0,0 +1,6 @@
1
+ // Generated by npm run build. Do not edit dist/index.js directly.
2
+ import{createTerminal as Y}from"@coder/libghostty-vt-node";import{isKeyRelease as k,matchesKey as g}from"@earendil-works/pi-tui";import{decodeKittyPrintable as V,isKeyRelease as F,parseKey as U}from"@earendil-works/pi-tui";var S={escape:"\x1B",enter:"\r",return:"\r",tab:" ","shift+tab":"\x1B[Z",space:" ",backspace:"\x7F",delete:"\x1B[3~",insert:"\x1B[2~",home:"\x1B[H",end:"\x1B[F",pageUp:"\x1B[5~",pageDown:"\x1B[6~",up:"\x1B[A",down:"\x1B[B",right:"\x1B[C",left:"\x1B[D",f1:"\x1BOP",f2:"\x1BOQ",f3:"\x1BOR",f4:"\x1BOS",f5:"\x1B[15~",f6:"\x1B[17~",f7:"\x1B[18~",f8:"\x1B[19~",f9:"\x1B[20~",f10:"\x1B[21~",f11:"\x1B[23~",f12:"\x1B[24~"},x={up:"A",down:"B",right:"C",left:"D",home:"H",end:"F",f1:"P",f2:"Q",f3:"R",f4:"S"},T=/^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/,E=/^\x1b\[M[\s\S]{3}$/;function R(r,e){let t=T.exec(r);if(t){let i=Number.parseInt(t[2],10)-e.column,s=Number.parseInt(t[3],10)-e.row;return i<1||i>e.width||s<1||s>e.height?"":`\x1B[<${t[1]};${i};${s}${t[4]}`}if(E.test(r)){let i=r.charCodeAt(4)-32-e.column,s=r.charCodeAt(5)-32-e.row;return i<1||i>e.width||s<1||s>e.height?"":`${r.slice(0,4)}${String.fromCharCode(i+32)}${String.fromCharCode(s+32)}`}return r}var w={insert:2,delete:3,pageUp:5,pageDown:6,f5:15,f6:17,f7:18,f8:19,f9:20,f10:21,f11:23,f12:24};function q(r){let e=r.split("+"),t=e.pop();if(!t||e.includes("super"))return;let i=new Set(e);if(i.has("ctrl")&&/^[a-z]$/.test(t)){let n=String.fromCharCode(t.charCodeAt(0)-96);return i.has("alt")?`\x1B${n}`:n}if(i.size===1&&i.has("alt")&&t.length===1)return`\x1B${t}`;if((t==="enter"||t==="return")&&i.has("alt"))return"\x1B\r";if((t==="enter"||t==="return")&&i.has("shift"))return"\r";if(t==="space"&&i.has("ctrl"))return i.has("alt")?"\x1B\0":"\0";let s=1+(i.has("shift")?1:0)+(i.has("alt")?2:0)+(i.has("ctrl")?4:0);if(s!==1){if(x[t])return`\x1B[1;${s}${x[t]}`;if(w[t])return`\x1B[${w[t]};${s}~`}}function P(r){if(F(r))return"";if(!r.startsWith("\x1B[")||T.test(r)||E.test(r))return r;let e=V(r);if(e!==void 0)return e;let t=U(r);return t?S[t]!==void 0?S[t]:q(t)??r:r}import{hasNative as G,spawn as H}from"zigpty";var z={hasNative:G,spawn:H},B=500;function M(r,e=z){if(!e.hasNative)throw new Error("Hunk overlay requires zigpty native PTY bindings, but none are available for this platform/architecture. Install pi-hunk on a supported macOS/Linux target, or review zigpty platform support and build prerequisites.");let t=e.spawn(r.command,r.args,{name:"xterm-256color",cols:r.columns,rows:r.rows,cwd:r.cwd,env:r.env,encoding:null}),i=t.pid,s=Math.max(0,r.shutdownGraceMs??B),n=new Set,l=!1,c=!1,p,o,d=()=>c||t.exitCode!==void 0&&t.exitCode!==null,h=()=>{o&&(clearTimeout(o),o=void 0)},f=(a,u)=>{if(a.active)try{a.listener(u)}catch{}},y=a=>{if(c)return;let u=a.signal===void 0?{exitCode:a.exitCode}:{exitCode:a.exitCode,signal:a.signal};c=!0,p=u,h();let m=[...n];n.clear();for(let N of m)f(N,u)};try{t.onExit(y);let a=t.exitCode;a!=null&&y({exitCode:a,signal:0})}catch(a){try{t.kill("SIGKILL")}catch{}try{t.close?.()}catch{}throw a}let v=()=>{if(o=void 0,!d()){try{t.kill("SIGKILL")}catch{}try{t.close?.()}catch{}}},$=()=>{if(s<=0){v();return}o=setTimeout(v,s),o.unref?.()};return{pid:i!==void 0&&Number.isInteger(i)&&i>0?i:void 0,write:a=>t.write(a),resize:(a,u)=>t.resize(a,u),onData:a=>t.onData(u=>{a(typeof u=="string"?u:new Uint8Array(u.buffer,u.byteOffset,u.byteLength))}),onExit:a=>{let u={active:!0,listener:a},m=p;return m?queueMicrotask(()=>f(u,m)):n.add(u),{dispose:()=>{u.active=!1,n.delete(u)}}},dispose:()=>{if(!l&&(l=!0,!d())){if(process.platform==="win32"){t.kill();return}try{t.kill("SIGTERM")}catch{if(d())return}d()||$()}}}}import{truncateToWidth as Q,visibleWidth as C}from"@earendil-works/pi-tui";var b="\x1B[0m",W="font-family: monospace; white-space: pre;";function Z(r){if(r==="&amp;")return"&";if(r==="&lt;")return"<";if(r==="&gt;")return">";if(r==="&quot;")return'"';if(r==="&apos;")return"'";let e=/^&#x([0-9a-f]+);$/i.exec(r);if(e)return String.fromCodePoint(Number.parseInt(e[1],16));let t=/^&#(\d+);$/.exec(r);return t?String.fromCodePoint(Number.parseInt(t[1],10)):r}function I(r){return r.replace(/&(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-f]+);/gi,Z)}function D(r,e){let t=/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/.exec(r);if(t)return`${e?38:48};2;${t[1]};${t[2]};${t[3]}`;let i=/^var\(--vt-palette-(\d+)\)$/.exec(r);if(i)return`${e?38:48};5;${i[1]}`}function j(r){if(!r||r===W)return"";let e=new Map;for(let p of r.split(";")){let o=p.indexOf(":");o!==-1&&e.set(p.slice(0,o).trim(),p.slice(o+1).trim())}let t=[];e.get("font-weight")==="bold"&&t.push("1"),e.get("opacity")==="0.5"&&t.push("2"),e.get("font-style")==="italic"&&t.push("3");let i=e.get("text-decoration-line")??"";/\bunderline\b/.test(i)&&t.push("4"),/\bblink\b/.test(i)&&t.push("5"),e.get("filter")==="invert(100%)"&&t.push("7"),e.get("visibility")==="hidden"&&t.push("8"),/\bline-through\b/.test(i)&&t.push("9"),/\boverline\b/.test(i)&&t.push("53");let s=e.get("color"),n=s&&D(s,!0);n&&t.push(n);let l=e.get("background-color"),c=l&&D(l,!1);return c&&t.push(c),t.length===0?"":`\x1B[${t.join(";")}m`}function X(r){let e=/\bstyle="([^"]*)"/.exec(r);return e?I(e[1]):""}function L(r,e,t){let i=Math.max(1,e),s=Math.max(1,t),n=[""],l=[],c=o=>{let d=o.split(/\r\n|\r|\n/);n[n.length-1]+=d[0]??"";for(let h=1;h<d.length;h++){n.push("");let f=l.join("");f&&(n[n.length-1]+=f),n[n.length-1]+=d[h]??""}};for(let o of r.match(/<[^>]*>|[^<]+/g)??[]){if(o.startsWith("</")){l.length>0&&l.pop();let d=l.join("");(d||n[n.length-1])&&c(b+d);continue}if(o.startsWith("<")){if(/^<br\b/i.test(o)){c(`
3
+ `);continue}let d=j(X(o));l.push(d),d&&c(b+l.join(""));continue}c(I(o))}let p=n.slice(0,s);for(;p.length<s;)p.push("");return p.map(o=>{let d=C(o);if(d<=i)return o+b+" ".repeat(i-d);let h=Q(o+b,i,"",!0);return h+" ".repeat(Math.max(0,i-C(h)))})}var J="\x1B[?1003h\x1B[?1006h",ee="\x1B[?1006l\x1B[?1003l\x1B[?1002l\x1B[?1000l",A="\x1B[?2026h",te=1e3,ie=12e3,re=124,O=2e3,se=12,ne={running:{complete:"completed",dispose:"disposed"},completed:{complete:"completed",dispose:"disposed"},disposed:{complete:"disposed",dispose:"disposed"}},oe={waiting:{output:"fallback",ready:"ready",fail:"failed",dispose:"disposed"},fallback:{output:"fallback",ready:"ready",fail:"failed",dispose:"disposed"},ready:{output:"ready",ready:"ready",fail:"ready",dispose:"disposed"},failed:{output:"failed",ready:"failed",fail:"failed",dispose:"disposed"},disposed:{output:"disposed",ready:"disposed",fail:"disposed",dispose:"disposed"}},_={"visible-unfocused":{show:"visible-unfocused",hide:"hidden-unfocused",focus:"visible-focused",blur:"visible-unfocused"},"visible-focused":{show:"visible-focused",hide:"hidden-focused",focus:"visible-focused",blur:"visible-unfocused"},"hidden-unfocused":{show:"visible-unfocused",hide:"hidden-unfocused",focus:"hidden-focused",blur:"hidden-unfocused"},"hidden-focused":{show:"visible-focused",hide:"hidden-focused",focus:"hidden-focused",blur:"hidden-unfocused"}},ae={running:{exit:"exited",dispose:"disposed"},exited:{exit:"exited",dispose:"exited"},disposed:{exit:"disposed",dispose:"disposed"}},K=class{tui;terminal;formatTerminalHtml;pty;subscriptions=[];done;resolveRows;resolveMouseViewport;prefixKey;toggleKey;onToggleRequest;showKey;onShowRequest;startupFrameDeadlineMs;prefixPending=!1;columns;rows;lifecycleState="running";startupState="waiting";presentationState="visible-unfocused";ptyState="running";startupProbeTail="";startupTimer;startupDeadlineTimer;mouseState="disabled";generation=0;renderQueued=!1;contentGeneration=0;renderedGeneration=-1;renderedColumns=0;renderedRows=0;renderedLines;get pid(){return this.pty.pid}get focused(){return this.isFocused()}set focused(e){let t=e?"focus":"blur",i=_[this.presentationState][t];i!==this.presentationState&&(this.presentationState=i,e||(this.prefixPending=!1),this.updateMouseMode())}isRunning(){return this.lifecycleState==="running"}isDisposed(){return this.lifecycleState==="disposed"}isVisibleState(){return this.presentationState==="visible-unfocused"||this.presentationState==="visible-focused"}isFocused(){return this.presentationState==="visible-focused"||this.presentationState==="hidden-focused"}transitionLifecycle(e){this.lifecycleState=ne[this.lifecycleState][e]}transitionStartup(e){this.startupState=oe[this.startupState][e]}transitionPty(e){this.ptyState=ae[this.ptyState][e]}constructor(e){if(this.tui=e.tui,this.done=e.done,this.resolveRows=e.resolveRows,this.resolveMouseViewport=e.resolveMouseViewport,this.prefixKey=e.prefixKey,this.toggleKey=e.toggleKey,this.onToggleRequest=e.onToggleRequest,this.showKey=e.showKey,this.onShowRequest=e.onShowRequest,this.startupFrameDeadlineMs=Math.max(0,e.startupFrameDeadlineMs??ie),this.columns=Math.max(1,e.tui.terminal.columns),this.rows=Math.max(1,e.initialRows??e.tui.terminal.rows),this.terminal=Y({cols:this.columns,rows:this.rows,scrollbackLimit:0}),typeof this.terminal.formatHtml!="function")throw this.terminal.dispose(),new Error("The installed libghostty binding does not expose formatHtml().");this.formatTerminalHtml=this.terminal.formatHtml.bind(this.terminal);try{this.pty=M({command:e.command,args:e.args,columns:this.columns,rows:this.rows,cwd:e.cwd,env:{...process.env,TERM:"xterm-256color",COLORTERM:"truecolor",TERM_PROGRAM:"pi-hunk",FORCE_COLOR:"3"}})}catch(i){throw this.terminal.dispose(),i}let t=this.generation;this.subscriptions.push(this.pty.onData(i=>{!this.isRunning()||t!==this.generation||(this.startupState!=="ready"&&this.observeStartupOutput(i),this.terminal.feed(i),this.contentGeneration+=1,this.renderedLines=void 0,this.isVisibleState()&&this.startupState==="ready"&&this.scheduleRender())}),this.pty.onExit(i=>{if(!this.isRunning()||t!==this.generation)return;this.transitionPty("exit");let s=this.captureExitDetail();this.complete(s?{...i,detail:s}:i)})),this.armStartupDeadline(),this.updateMouseMode()}setVisible(e){let t=e?"show":"hide",i=_[this.presentationState][t];i!==this.presentationState&&(this.presentationState=i,e||(this.prefixPending=!1),this.updateMouseMode(),e&&this.tui.requestRender())}isVisible(){return this.isVisibleState()}handleInput(e){if(!this.isVisibleState()||!this.isRunning())return;if(!k(e)&&this.prefixKey&&g(e,this.prefixKey)){this.prefixPending=!0;return}if(!k(e)&&this.prefixPending){this.prefixPending=!1,this.toggleKey&&g(e,this.toggleKey)?this.onToggleRequest?.():this.showKey&&g(e,this.showKey)&&this.onShowRequest?.();return}let t=P(e);if(t&&this.resolveMouseViewport){let i=this.resolveMouseViewport(this.tui.terminal.columns,this.tui.terminal.rows,this.columns,this.rows);t=R(t,i)}t&&this.pty.write(t)}render(e){let t=Math.max(1,e),i=Math.max(1,this.resolveRows?.(this.tui.terminal.rows)??this.rows);if(this.resize(t,i),this.startupState!=="ready")return["Starting Hunk\u2026",...Array.from({length:Math.max(0,i-1)},()=>"")];if(this.renderedLines&&this.renderedGeneration===this.contentGeneration&&this.renderedColumns===this.columns&&this.renderedRows===this.rows)return this.renderedLines;let s=this.formatTerminalHtml();return this.renderedLines=L(s,this.columns,this.rows),this.renderedGeneration=this.contentGeneration,this.renderedColumns=this.columns,this.renderedRows=this.rows,this.renderedLines}setTargetRows(e){let t=Math.max(1,e);t!==this.rows&&this.resize(this.columns,t)}invalidate(){this.renderedLines=void 0,this.tui.requestRender()}dispose(){if(!this.isDisposed()){if(this.transitionLifecycle("dispose"),this.transitionStartup("dispose"),this.generation+=1,this.clearStartupTimers(),this.setMouseEnabled(!1),this.ptyState==="running"){this.transitionPty("dispose");try{this.pty.dispose()}catch{}}this.renderQueued=!1;for(let e of this.subscriptions)e.dispose();this.subscriptions.length=0,this.terminal.dispose()}}observeStartupOutput(e){let t=typeof e=="string"?e:new TextDecoder().decode(e),i=this.startupProbeTail+t;if(this.startupProbeTail=i.slice(-A.length),i.includes(A)){this.markStartupReady();return}this.startupState==="waiting"&&(this.transitionStartup("output"),this.clearStartupDeadlineTimer(),this.startupTimer=setTimeout(()=>{this.startupTimer=void 0,this.isRunning()&&(this.markStartupReady(),this.isVisibleState()&&this.tui.requestRender())},te),this.startupTimer.unref?.())}armStartupDeadline(){this.startupFrameDeadlineMs<=0||this.startupState!=="waiting"||(this.clearStartupDeadlineTimer(),this.startupDeadlineTimer=setTimeout(()=>{this.startupDeadlineTimer=void 0,this.failStartupFrameDeadline()},this.startupFrameDeadlineMs),this.startupDeadlineTimer.unref?.())}clearStartupTimer(){this.startupTimer&&(clearTimeout(this.startupTimer),this.startupTimer=void 0)}clearStartupDeadlineTimer(){this.startupDeadlineTimer&&(clearTimeout(this.startupDeadlineTimer),this.startupDeadlineTimer=void 0)}clearStartupTimers(){this.clearStartupTimer(),this.clearStartupDeadlineTimer()}markStartupReady(){this.startupState!=="waiting"&&this.startupState!=="fallback"||(this.transitionStartup("ready"),this.startupProbeTail="",this.clearStartupTimers())}failStartupFrameDeadline(){!this.isRunning()||this.startupState!=="waiting"||(this.transitionStartup("fail"),this.complete({exitCode:re,signal:0,detail:`Hunk startup failed: no terminal frame became ready within ${this.startupFrameDeadlineMs}ms.`},{disposePty:!0}))}complete(e,t={}){if(this.isRunning()){this.transitionLifecycle("complete"),this.renderQueued=!1,this.clearStartupTimers(),this.setMouseEnabled(!1);try{this.done(e)}finally{t.disposePty&&this.dispose()}}}captureExitDetail(){let e;try{e=this.terminal.getVisibleText()}catch{e=void 0}if(!e?.trim()&&this.terminal.formatPlain)try{e=this.terminal.formatPlain()}catch{}return de(e)}scheduleRender(){this.renderQueued||!this.isVisibleState()||!this.isRunning()||(this.renderQueued=!0,queueMicrotask(()=>{this.renderQueued=!1,this.isVisibleState()&&this.isRunning()&&this.tui.requestRender()}))}updateMouseMode(){this.setMouseEnabled(this.presentationState==="visible-focused"&&this.lifecycleState==="running")}setMouseEnabled(e){let t=e?"enabled":"disabled";if(t!==this.mouseState){this.mouseState=t;try{this.tui.terminal.write(e?J:ee)}catch{}}}resize(e,t){this.isRunning()&&(e===this.columns&&t===this.rows||(this.columns=e,this.rows=t,this.terminal.resize(e,t),this.contentGeneration+=1,this.renderedLines=void 0,this.pty.resize(e,t)))}};function de(r){if(!r)return;let t=ue(r.replace(/\r\n?/g,`
4
+ `)).split(`
5
+ `).map(s=>s.trimEnd());for(;t.length>0&&!t[0]?.trim();)t.shift();for(;t.length>0&&!t.at(-1)?.trim();)t.pop();if(t.length===0)return;let i=t.slice(-se).join(`
6
+ `).trim();return i.length>O&&(i=`\u2026${i.slice(i.length-O+1)}`),i||void 0}function ue(r){let e="";for(let t of r){let i=t.charCodeAt(0);i<32&&i!==9&&i!==10||i===127||(e+=t)}return e}export{K as EmbeddedHunk};
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // Generated by npm run build. Do not edit dist/index.js directly.
2
+ import{matchesKey as Be,truncateToWidth as nn}from"@earendil-works/pi-tui";import{isAbsolute as J,relative as he,resolve as E,sep as Je}from"node:path";var pe=/(^|[._:/-])(edit|write|patch|apply[_-]?patch)([._:/-]|$)/i,Ge=/(?:^|[;&|\n])\s*(?:apply_patch\b|git\s+apply\b|jj\s+(?:abandon|commit|describe|duplicate|edit|new|rebase|restore|squash|undo)\b|sl\s+(?:amend|commit|goto|rebase|revert)\b|sed\s+-i\b|perl\s+-pi\b|tee\b|mv\b|cp\b|rm\b|touch\b|mkdir\b|truncate\b|npm\s+(?:install|uninstall|update)\b|(?:cat|echo|printf)\b[^;&|]*>)/i,Ve=["path","file_path","filePath","file"];function me(t,e){if(pe.test(t))return!0;if(!/(^|[._:/-])bash([._:/-]|$)/i.test(t)||!e||typeof e!="object")return!1;let n=e.command;return typeof n=="string"&&Ge.test(Ye(n))}function Ye(t){let e="",n,i=!1;for(let o of t){if(i){e+=" ",i=!1;continue}if(o==="\\"&&n!=="'"){e+=" ",i=!0;continue}if(n){o===n&&(n=void 0),e+=" ";continue}if(o==="'"||o==='"'){n=o,e+=" ";continue}e+=o}return e}function I(t,e,n){if(!me(t,e))return!1;if(!pe.test(t))return!0;let i=ve(e);return i.length===0?!0:i.some(o=>Y(o,n))}function G(t,e){let n=ve(t);return e?n.find(i=>Y(i,e)):n[0]}function V(t,e){if(!Y(t,e))return t;let n=E(e),i=J(t)?E(t):E(n,t);return he(n,i)||"."}function ve(t){if(!t||typeof t!="object")return[];let e=t,n=[];if(ge(e,n),Array.isArray(e.edits))for(let i of e.edits)!i||typeof i!="object"||ge(i,n);return n}function ge(t,e){for(let n of Ve){let i=t[n];typeof i=="string"&&i.trim()&&e.push(i.trim())}}function Y(t,e){let n=E(e),i=J(t)?E(t):E(n,t),o=he(n,i);return o===""||!ze(o)&&!J(o)}function ze(t){return t===".."||t.startsWith(`..${Je}`)}var H=class{changedSinceReview=!1;toolArgs=new Map;markChanged(){this.changedSinceReview=!0}rememberToolArgs(e,n){this.toolArgs.set(e,n)}takeToolArgs(e){let n=this.toolArgs.get(e);return this.toolArgs.delete(e),n}clearToolArgs(){this.toolArgs.clear()}consumeSettled(){let e=this.changedSinceReview;return this.changedSinceReview=!1,this.clearToolArgs(),{mutation:e}}reset(){this.changedSinceReview=!1,this.toolArgs.clear()}};var ye="ctrl+space";var we={layout:"full",experimentalPiWrap:!1},Qe={full:{anchor:"center",width:"100%",maxHeight:"100%"},left:{anchor:"left-center",width:"50%",maxHeight:"100%"},right:{anchor:"right-center",width:"50%",maxHeight:"100%"},float:{anchor:"center",width:"75%",maxHeight:"75%"}};function R(t){return t==="full"||t==="left"||t==="right"||t==="float"}function z(t){return{...Qe[t]}}var T={prefix:ye,toggle:"h",show:"s"},O={review:"after-run",followEdits:!0,hunk:{command:"hunk",args:["diff","--watch"]},overlay:{...we},bindings:{...T}};function p(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function b(t){return t==="off"||t==="after-run"||t==="live"}var Xe=new Set(["ctrl","shift","alt","super"]),Ze=new Set(["escape","esc","enter","return","tab","space","backspace","delete","insert","clear","home","end","pageUp","pageDown","up","down","left","right",...Array.from({length:12},(t,e)=>`f${e+1}`)]),et=new Set(["insert","clear",...Array.from({length:12},(t,e)=>`f${e+1}`)]),tt=new Set("`-=[]\\;',./!@#$%^&*()_+|~{}:<>?");function ke(t){if(typeof t!="string"||t.length===0)return;let e,n;if(t==="+")e="+",n=[];else if(t.endsWith("++"))e="+",n=t.slice(0,-2).split("+");else{let o=t.split("+");e=o.pop()??"",n=o}if(!(n.some(o=>!Xe.has(o))||new Set(n).size!==n.length||!(/^[a-z0-9]$/.test(e)||tt.has(e))&&!Ze.has(e)))return{base:e,modifiers:n}}function C(t){let e=ke(t);return e?et.has(e.base)?!0:e.modifiers.some(n=>n==="ctrl"||n==="alt"||n==="super"):!1}function k(t){let e=ke(t);return e!==void 0&&e.base!=="escape"&&e.base!=="esc"}function nt(t,e){if(!p(e))return t;let n={...t,args:[...t.args]};return typeof e.command=="string"&&e.command.trim()&&(n.command=e.command.trim()),Array.isArray(e.args)&&e.args.every(i=>typeof i=="string")&&(n.args=[...e.args]),n}function it(t,e){if(!p(e))return t;let n={...t};return R(e.layout)&&(n.layout=e.layout),typeof e.experimentalPiWrap=="boolean"&&(n.experimentalPiWrap=e.experimentalPiWrap),n}function A(t,e){if(!p(e))return t;let n=y(t);if(typeof e.review=="string"&&b(e.review)&&(n.review=e.review),typeof e.followEdits=="boolean"&&(n.followEdits=e.followEdits),"hunk"in e&&(n.hunk=nt(n.hunk,e.hunk)),"overlay"in e&&(n.overlay=it(n.overlay,e.overlay)),p(e.bindings)){C(e.bindings.prefix)&&(n.bindings.prefix=e.bindings.prefix);for(let i of["toggle","show"]){let o=e.bindings[i];k(o)&&(n.bindings[i]=o)}new Set(Object.values(n.bindings)).size!==3&&(n.bindings={...t.bindings})}return n}function y(t){return{review:t.review,followEdits:t.followEdits,hunk:{command:t.hunk.command,args:[...t.hunk.args]},overlay:{...t.overlay},bindings:{...t.bindings}}}import{mkdir as ot,readFile as rt,rename as st,rm as be,writeFile as at}from"node:fs/promises";import{homedir as ut}from"node:os";import{basename as lt,dirname as ct,join as X}from"node:path";import{CONFIG_DIR_NAME as dt}from"@earendil-works/pi-coding-agent";async function Q(t){try{return JSON.parse(await rt(t,"utf8"))}catch(e){if(e.code==="ENOENT")return;throw new Error(`Invalid Hunk config at ${t}: ${e instanceof Error?e.message:String(e)}`)}}var ft=0;async function gt(t,e){let n=ct(t);await ot(n,{recursive:!0});let i=X(n,`.${lt(t)}.${process.pid}.${ft++}.tmp`);try{await at(i,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),await st(i,t)}catch(o){throw await be(i,{force:!0}).catch(()=>{}),o}}function Z(){return process.env.PI_HUNK_CONFIG??X(ut(),".pi","agent","hunk.json")}function $(t){return X(t,dt,"hunk.json")}function ht(t,e,n){if(!(!n||t===void 0)){if(!p(t)){n(`Ignoring invalid Hunk config root in ${e}; expected a JSON object.`);return}if(t.review!==void 0&&(typeof t.review!="string"||!b(t.review))&&n(`Ignoring invalid review ${JSON.stringify(t.review)} in ${e}; expected "off", "after-run", or "live".`),t.followEdits!==void 0&&typeof t.followEdits!="boolean"&&n(`Ignoring invalid followEdits ${JSON.stringify(t.followEdits)} in ${e}; expected true or false.`),t.hunk!==void 0){if(!p(t.hunk)){n(`Ignoring invalid hunk configuration in ${e}; expected an object.`);return}t.hunk.command!==void 0&&(typeof t.hunk.command!="string"||!t.hunk.command.trim())&&n(`Ignoring invalid hunk.command ${JSON.stringify(t.hunk.command)} in ${e}; expected a non-empty string.`),t.hunk.args!==void 0&&(!Array.isArray(t.hunk.args)||!t.hunk.args.every(i=>typeof i=="string"))&&n(`Ignoring invalid hunk.args in ${e}; expected an array of strings.`)}}}function pt(t,e,n,i){if(!i||!p(t)||!p(t.bindings))return;let o={prefix:C,toggle:k,show:k};for(let s of["prefix","toggle","show"]){let u=t.bindings[s];u===void 0||o[s](u)||i(`Ignoring invalid bindings.${s} ${JSON.stringify(u)} in ${e}. Use a pi-tui key id like "${T[s]}".`+(s==="prefix"?" Plain typing and navigation keys cannot be prefixes.":""))}let r={prefix:C(t.bindings.prefix)?t.bindings.prefix:n.prefix,toggle:k(t.bindings.toggle)?t.bindings.toggle:n.toggle,show:k(t.bindings.show)?t.bindings.show:n.show};new Set(Object.values(r)).size!==3&&i(`Ignoring colliding Hunk bindings in ${e}; prefix, toggle, and show must use distinct keys.`)}function mt(t,e,n){if(!n||!p(t)||!p(t.overlay))return;let{layout:i,experimentalPiWrap:o}=t.overlay;i!==void 0&&!R(i)&&n(`Ignoring invalid overlay.layout ${JSON.stringify(i)} in ${e}. Use "full", "left", "right", or "float".`),o!==void 0&&typeof o!="boolean"&&n(`Ignoring invalid overlay.experimentalPiWrap ${JSON.stringify(o)} in ${e}; expected true or false.`)}function vt(t,e,n){if(!n||!p(t))return;let i=new Set(["review","followEdits","hunk","overlay","bindings"]),o=Object.keys(t).filter(s=>!i.has(s)).map(s=>s),r=[["hunk",new Set(["command","args"])],["overlay",new Set(["layout","experimentalPiWrap"])],["bindings",new Set(["prefix","toggle","show"])]];for(let[s,u]of r){let a=t[s];p(a)&&o.push(...Object.keys(a).filter(l=>!u.has(l)).map(l=>`${s}.${l}`))}o.length>0&&n(`Ignoring unknown Hunk config key${o.length>1?"s":""} in ${e}: ${o.join(", ")}.`)}function Se(t,e,n,i){return ht(e,n,i),vt(e,n,i),pt(e,n,t.bindings,i),mt(e,n,i),A(t,e)}async function Ce(t,e){let n=y(O),i=Z(),o=await Q(i);if(n=Se(n,o,i,e),t.isProjectTrusted()){let s=$(t.cwd),u=await Q(s);n=Se(n,u,s,e)}let r=process.env.PI_HUNK_REVIEW;return r&&(b(r)?n.review=r:e?.(`Ignoring invalid PI_HUNK_REVIEW ${JSON.stringify(r)}; expected "off", "after-run", or "live".`)),n}function Ee(t,e){let n={...t};for(let[i,o]of Object.entries(e)){let r=n[i];n[i]=p(o)&&p(r)?Ee(r,o):o}return n}var x=class{config=y(O);get(){return y(this.config)}setSession(e){return this.config=y(e),this.get()}patchSession(e){return this.config=A(this.config,e),this.get()}async reload(e,n){return this.config=await Ce(e,n),this.get()}async resetProject(e){if(!e.isProjectTrusted())throw new Error("Project config requires a trusted project.");return await be($(e.cwd),{force:!0}),this.reload(e)}async persist(e,n,i){if(n==="project"&&!e.isProjectTrusted())throw new Error("Project config requires a trusted project.");let o=n==="global"?Z():$(e.cwd),r=await Q(o),s=p(r)?r:{},u=p(i)?i:{},a=Ee(s,u);return await gt(o,a),this.reload(e)}};function N(t){return t.review==="live"&&t.uiMode==="tui"&&!t.alreadyOpenedForRun&&!t.activeBlocking}function _(t){return t.autoOpenSuppression||!t.shouldReview||t.review==="off"||t.activeBlocking||t.uiMode!=="tui"?"skip":t.review==="live"?t.hasLiveSurface?"skip":"recover":"launch"}function F(t){return t.action==="skip"?t.review==="off"?{action:"skipped",reason:"review-off"}:t.uiMode!=="tui"?{action:"skipped",reason:"not-tui"}:t.activeBlocking?{action:"skipped",reason:"blocking"}:t.autoOpenSuppression?{action:"skipped",reason:t.autoOpenSuppression}:t.review==="live"&&(t.activeLive??t.activeVisible)?{action:"skipped",reason:"already-open"}:{action:"skipped",reason:"no-change"}:t.action==="recover"?{action:"opened",reason:"recover"}:t.activeVisible?{action:"skipped",reason:"already-open"}:{action:"opened",reason:"mutation"}}function Re(t){let e=[],n="",i,o=!1,r=!1;for(let s of t)o?(n+=s,o=!1,r=!0):s==="\\"&&i!=="'"?(o=!0,r=!0):i?s===i?i=void 0:n+=s:s==="'"||s==='"'?(i=s,r=!0):/\s/.test(s)?r&&(e.push(n),n="",r=!1):(n+=s,r=!0);if(o&&(n+="\\"),i)throw new Error(`Unclosed ${i} quote`);return r&&e.push(n),e}var ee=new Set(["diff","show","stash"]),yt=new Set(["patch","pager","difftool"]),xe=new Set(["close","toggle","status","feedback","review","config"]);function wt(t,e,n){if(t==="review")return e===1?["off","after-run","live"]:void 0;if(t==="config")return e===1?["restore","full","left","right","float"]:n[1]==="restore"?void 0:["experimental-wrap","no-wrap"]}function te(t){let e=/\s$/.test(t),n=t.split(/\s+/).filter(Boolean);if(n.length===0||n.length===1&&!e){let c=n[0]??"",d=[...xe,...ee].filter(f=>f.startsWith(c)).map(f=>({value:f,label:f}));return d.length>0?d:null}let o=n[0],r=e?n.length:n.length-1,s=wt(o,r,n);if(!s)return null;let u=e?"":n[n.length-1],a=t.slice(0,t.length-u.length),l=s.filter(c=>c.startsWith(u)).map(c=>({value:`${a}${c}`,label:c}));return l.length>0?l:null}function M(t,e){let n=Re(t);if(n.length===0)return[...e];if(yt.has(n[0]))throw new Error(`Hunk ${n[0]} is not supported through /hunk; run it directly in a terminal.`);return n[0]==="staged"?["diff","--staged",...n.slice(1)]:n[0].startsWith("-")||!ee.has(n[0])?["diff",...n]:n}import{execFile as kt}from"node:child_process";import{realpath as St}from"node:fs/promises";import{isAbsolute as ie,relative as oe,resolve as P,sep as bt}from"node:path";import{promisify as Ct}from"node:util";var Et=Ct(kt);async function W(t,e={}){let n=e.hunkBinary??"hunk",i=t.filter(o=>!o.startsWith("-")).slice(0,2).join(" ")||n;if(e.run){let o=await e.run([n,...t]);if(o.code!==0)throw new Error(o.stderr.trim()||o.stdout.trim()||`hunk ${i} failed (${o.code})`);return o.stdout}try{let{stdout:o}=await Et(n,t,{cwd:e.cwd,encoding:"utf8",timeout:8e3,maxBuffer:262144});return o}catch(o){if(o&&typeof o=="object"&&"stderr"in o){let r=String(o.stderr??"").trim(),s=String(o.stdout??"").trim();throw new Error(r||s||(o instanceof Error?o.message:String(o)))}throw o instanceof Error?o:new Error(String(o))}}function Rt(t){let e=t!==null&&typeof t=="object"&&!Array.isArray(t)?t.sessions:void 0;if(!Array.isArray(e))throw new Error("Hunk session JSON drift: expected a sessions array.");let n=new Set;return e.map((i,o)=>{if(!i||typeof i!="object")throw new Error(`Hunk session JSON drift: sessions[${o}] must be an object.`);let{sessionId:r,pid:s,cwd:u,repoRoot:a,launchedAt:l}=i;if(typeof r!="string"||r.length===0)throw new Error(`Hunk session JSON drift: sessions[${o}] requires non-empty sessionId.`);if(n.has(r))throw new Error(`Hunk session JSON drift: sessions[${o}].sessionId duplicates ${JSON.stringify(r)}.`);if(n.add(r),!Number.isInteger(s)||s<=0)throw new Error(`Hunk session JSON drift: sessions[${o}].pid must be a positive integer.`);if(typeof u!="string"||u.length===0)throw new Error(`Hunk session JSON drift: sessions[${o}] requires non-empty cwd.`);if(a!==void 0&&(typeof a!="string"||a.length===0))throw new Error(`Hunk session JSON drift: sessions[${o}].repoRoot must be a non-empty string when present.`);if(typeof l!="string"||l.length===0||!Number.isFinite(Date.parse(l)))throw new Error(`Hunk session JSON drift: sessions[${o}].launchedAt must be a valid timestamp string.`);let c={sessionId:r,pid:s,cwd:u,launchedAt:l};return a!==void 0&&(c.repoRoot=a),c})}function xt(t,e){if(e.sessionId)return t.find(o=>o.sessionId===e.sessionId);let n=Pe(e.managedPid);if(n!==void 0){let o=t.filter(r=>r.pid===n);if(o.length>1)throw new Error(`Hunk session JSON drift: multiple live sessions advertise pid ${n}.`);if(o.length===1)return o[0]}let i=t.filter(o=>o.repoRoot!==void 0&&Ht(e.cwd,o.repoRoot));if(i.length!==0){if(i.length===1)return i[0];throw new Error(`Ambiguous live Hunk sessions for repository ${e.cwd}${n!==void 0?`; no session has managed pid ${n}`:""}. Matching sessions: ${i.map(Ot).join(", ")}.`)}}async function Pt(t={}){return Rt(JSON.parse(await W(["session","list","--json"],t)))}async function se(t){return xt(await Pt(t),t)}function Pe(t){return t!==void 0&&Number.isInteger(t)&&t>0?t:void 0}function Ht(t,e){let n=oe(P(e),P(t));return n===""||!re(n)&&!ie(n)}function re(t){return t===".."||t.startsWith(`..${bt}`)}function Ot(t){return`${t.sessionId} pid=${t.pid}${t.repoRoot?` repoRoot=${t.repoRoot}`:` cwd=${t.cwd}`}`}function Lt(t){if(t.sessionId)return`No live Hunk session found with id ${t.sessionId}.`;let e=Pe(t.managedPid);return`No live Hunk session found for repository ${t.cwd}${e!==void 0?` with managed pid ${e}`:""}.`}async function ne(t){try{return await St(t)}catch{return t}}async function It(t,e,n){let i=P(e),o=P(i,t),r=await ne(P(n.repoRoot??n.cwd)),s=await ne(i),u=oe(i,o),a=await ne(o),l=a===o&&!re(u)&&!ie(u)?P(s,u):a,c=oe(r,l);if(re(c)||ie(c))throw new Error(`Cannot navigate Hunk session ${n.sessionId}: target ${l} is outside selected repository ${r}.`);return c||"."}async function He(t){let e=await se(t);if(!e)throw new Error(Lt(t));let n=Math.max(1,t.hunk??1);await W(["session","navigate",e.sessionId,"--file",await It(t.filePath,t.cwd,e),"--hunk",String(n)],t)}function B(t,e){return JSON.stringify([t,...e])}function ae(t,e){let n=Math.max(1,e);if(typeof t=="number")return Math.max(1,Math.min(n,Math.floor(t)));let i=Number.parseFloat(t.slice(0,-1));return Number.isFinite(i)&&i>0?Math.max(1,Math.min(n,Math.floor(n*i/100))):n}function Oe(t,e,n){if(!n||e!=="left"&&e!=="right")return;let i=t.render,o=!0,r=!1,s=function(l){if(!o||r)return i.call(t,l);let c=Math.floor(l/2),g=Math.max(1,l-c),d=i.call(t,g);if(e==="right")return d;let f=" ".repeat(c);return d.map(m=>`${f}${m}`)};t.render=s;let u=()=>{t.invalidate(),t.requestRender()};try{u()}catch(a){throw r=!0,o=!1,t.render===s&&(t.render=i),a}return{setVisible(a){r||o===a||(o=a,u())},dispose(){r||(r=!0,o=!1,t.render===s&&(t.render=i),u())}}}var D=null;function Tt(){return D||(D=import("./chunks/embedded-V4FOKNFK.js").then(t=>t,t=>{throw D=null,t})),D}var At=5e3,Le=500,$t=4,Nt={closed:{ensure:{same:"open",different:"open"},toggle:{same:"open",different:"open"}},starting:{ensure:{same:"wait-start",different:"wait-start"},toggle:{same:"wait-start",different:"wait-start"}},visible:{ensure:{same:"focus",different:"replace"},toggle:{same:"hide",different:"replace"}},hidden:{ensure:{same:"show",different:"replace"},toggle:{same:"show",different:"replace"}},closing:{ensure:{same:"wait-close",different:"wait-close"},toggle:{same:"wait-close",different:"wait-close"}}},_t={closed:["starting"],starting:["visible","hidden","closing","closed"],visible:["hidden","closing"],hidden:["visible","closing"],closing:["closed"]},j=class{state="closed";generation=0;createComponent;loadEmbedded;startTimeoutMs;startupFrameDeadlineMs;stateListener;childExitListener;transitionScheduler;handle;component;currentPid;experimentalPiWrap;currentArgsKey;startPromise=null;closePromise=null;settleStart=null;settleClose=null;startTimer=null;constructor(e,n={}){this.createComponent=e,this.loadEmbedded=n.loadEmbedded??Tt,this.startTimeoutMs=n.startTimeoutMs??At,this.startupFrameDeadlineMs=n.startupFrameDeadlineMs,this.stateListener=n.onStateChange,this.childExitListener=n.onChildExit}setStateListener(e){this.stateListener=e}setChildExitListener(e){this.childExitListener=e}setTransitionScheduler(e){this.transitionScheduler=e}scheduleTransition(e){return this.transitionScheduler?this.transitionScheduler(e):e()}transitionState(e){if(e!==this.state){if(!_t[this.state].includes(e))throw new Error(`Invalid Hunk overlay state transition: ${this.state} -> ${e}.`);this.state=e}}sessionRelation(e){return this.currentArgsKey===B(e.command,e.args)?"same":"different"}async dispatchRequest(e,n,i,o){switch(Nt[this.state][e][this.sessionRelation(i)]){case"wait-start":{if(!this.startPromise)throw new Error("Hunk overlay entered starting without a start transition.");return await this.startPromise,this.dispatchRequest(e,n,i,o)}case"wait-close":{if(!this.closePromise)throw new Error("Hunk overlay entered closing without a close transition.");return await this.closePromise,this.dispatchRequest(e,n,i,o)}case"open":await this.open(n,i,o);return;case"replace":await this.close(),await this.open(n,i,o);return;case"focus":await this.focus();return;case"show":await this.show();return;case"hide":await this.hide();return}}emitStateChange(){try{this.stateListener?.()}catch{}}emitChildExit(e){try{this.childExitListener?.(e)}catch{}}getState(){return this.state}isLive(){return this.state==="visible"||this.state==="hidden"||this.state==="starting"}getInfo(){return!this.isLive()&&this.state!=="closing"?null:{state:this.state,argsKey:this.currentArgsKey??"",pid:this.currentPid,detail:this.state}}async ensure(e,n,i){await this.dispatchRequest(n.source==="shortcut"?"toggle":"ensure",e,n,i)}async open(e,n,i){if(this.startPromise){if(await this.startPromise,this.isLive()&&this.currentArgsKey===B(n.command,n.args)){this.state==="hidden"&&await this.show();return}this.isLive()&&await this.close()}this.state!=="closed"&&await this.close();let o=this.createComponent;if(!o){let a=await this.loadEmbedded();o=l=>new a.EmbeddedHunk(l),this.createComponent=o}this.transitionState("starting");let r=++this.generation;this.currentArgsKey=B(n.command,n.args);let s=!1;this.startPromise=new Promise((a,l)=>{this.settleStart=c=>{s||(s=!0,this.settleStart=null,this.clearStartTimer(),c!==void 0?l(c):a())}}),this.armStartTimer(r);let u=!1;this.closePromise=new Promise(a=>{this.settleClose=()=>{u||(u=!0,this.settleClose=null,a())}});try{let a=i.overlay,l=z(a.layout);if(e.ui.custom((c,g,d,f)=>{if(r!==this.generation)return{render:()=>[],invalidate:()=>{},dispose:()=>{}};try{this.experimentalPiWrap=Oe(c,a.layout,a.experimentalPiWrap)}catch(h){this.experimentalPiWrap=void 0,e.ui.notify(`Experimental Pi word wrap is unavailable: ${h instanceof Error?h.message:String(h)}`,"warning")}let m=ae(l.maxHeight,c.terminal.rows),v=o({command:n.command,args:n.args,cwd:n.cwd,tui:c,initialRows:m,resolveRows:h=>ae(l.maxHeight,h),resolveMouseViewport:(h,Ue,q,de)=>{let Ke=l.anchor==="right-center"?Math.max(0,h-q):l.anchor==="center"?Math.max(0,Math.floor((h-q)/2)):0,fe=Math.max(0,Math.floor((Ue-de)/2)),qe=fe>0?fe-1:0;return{column:Ke,row:qe,width:q,height:de}},startupFrameDeadlineMs:this.startupFrameDeadlineMs,prefixKey:i.bindings.prefix,toggleKey:i.bindings.toggle,onToggleRequest:()=>{r===this.generation&&this.scheduleTransition(async()=>{r===this.generation&&await this.ensure(e,{...n,args:i.hunk.args,source:"shortcut"},i)}).catch(h=>{e.ui.notify(`Hunk toggle failed: ${h instanceof Error?h.message:String(h)}`,"error")})},showKey:i.bindings.show,onShowRequest:()=>{r===this.generation&&this.scheduleTransition(async()=>{r===this.generation&&await this.ensure(e,{...n,args:["show"],source:"shortcut"},i)}).catch(h=>{e.ui.notify(`Hunk show failed: ${h instanceof Error?h.message:String(h)}`,"error")})},done:h=>{r===this.generation&&this.onChildDone(r,h,e)}});this.component=v;let S=v.pid;return this.currentPid=S!==void 0&&Number.isInteger(S)&&S>0?S:void 0,v},{overlay:!0,overlayOptions:{anchor:l.anchor,width:l.width,maxHeight:l.maxHeight,margin:0},onHandle:c=>{if(r!==this.generation){try{c.hide()}catch{}return}this.handle=c,this.component?.setVisible(!0),c.isHidden()&&c.setHidden(!1),this.transitionState("visible"),this.settleStart?.(),this.emitStateChange()}}).catch(c=>{r===this.generation&&this.state==="starting"&&this.settleStart?.(c)}),await this.startPromise,r!==this.generation||!this.isLive())return}catch(a){throw r===this.generation&&this.forceReset(r),a}finally{r===this.generation&&(this.startPromise=null)}}async show(){if(this.state==="visible"){await this.focus();return}this.state!=="hidden"||!this.handle||(this.experimentalPiWrap?.setVisible(!0),this.handle.setHidden(!1),this.component?.setVisible(!0),this.transitionState("visible"),this.emitStateChange())}async hide(){this.state!=="visible"||!this.handle||(this.component?.setVisible(!1),this.experimentalPiWrap?.setVisible(!1),this.handle.setHidden(!0),this.transitionState("hidden"),this.emitStateChange())}async toggle(e,n,i){await this.dispatchRequest("toggle",e,n,i)}async focus(){if(!(!this.handle||this.state==="closed"||this.state==="closing")){if(this.state==="hidden"){await this.show();return}try{this.handle.focus()}catch{}}}async release(){return this.state==="closed"?!1:(await this.close(),!0)}async close(){if(this.state==="closed")return;if(this.state==="closing"){if(this.closePromise)try{await this.closePromise}catch{}return}let e=this.generation;this.transitionState("closing");try{this.component?.setVisible(!1)}catch{}if(this.handle){this.removeOwned(e);return}this.cancelBeforeMount(e)}onChildDone(e,n,i){if(e!==this.generation||this.state==="closed"||this.state==="closing")return;if(!(this.state==="visible"||this.state==="hidden")){this.transitionState("closing"),this.cancelBeforeMount(e,new Error(Mt(n)));return}let r=Ft(n);if(this.emitChildExit(n),this.transitionState("closing"),r)try{i.ui.notify(r,"error")}catch{}if(this.handle){this.removeOwned(e);return}this.cancelBeforeMount(e,r?new Error(r):void 0)}removeOwned(e){if(e!==this.generation)return;let n=this.handle,i=this.component;if(this.handle=void 0,this.component=void 0,n)try{n.hide()}catch{}try{i?.dispose?.()}catch{}this.finishClosed(e)}cancelBeforeMount(e,n){if(e!==this.generation)return;this.generation+=1;let i=this.component,o=this.handle;this.component=void 0,this.handle=void 0;try{o?.hide()}catch{}try{i?.dispose?.()}catch{}this.settleStart?.(n),this.finishClosed(this.generation)}armStartTimer(e){this.clearStartTimer(),!(this.startTimeoutMs<=0)&&(this.startTimer=setTimeout(()=>{this.startTimer=null,this.failStartTimeout(e)},this.startTimeoutMs),this.startTimer.unref?.())}clearStartTimer(){this.startTimer&&(clearTimeout(this.startTimer),this.startTimer=null)}failStartTimeout(e){if(e!==this.generation||this.state!=="starting")return;this.generation+=1;let n=this.generation,i=this.component;this.component=void 0,this.handle=void 0;try{i?.dispose?.()}catch{}this.settleStart?.(new Error(`Hunk overlay did not start within ${this.startTimeoutMs}ms`)),this.finishClosed(n)}forceReset(e){if(e!==this.generation)return;let n=this.component,i=this.handle;this.component=void 0,this.handle=void 0;try{i?.hide()}catch{}try{n?.dispose?.()}catch{}this.finishClosed(e)}finishClosed(e){if(e!==this.generation){this.settleClose?.();return}this.clearStartTimer(),this.transitionState("closed"),this.handle=void 0,this.component=void 0,this.currentPid=void 0;let n=this.experimentalPiWrap;this.experimentalPiWrap=void 0;try{n?.dispose()}catch{}this.currentArgsKey=void 0,this.startPromise=null,this.settleStart=null;let i=this.settleClose;this.closePromise=null,this.settleClose=null,i?.(),this.emitStateChange()}};function Ft(t){if(t.exitCode===0&&(t.signal??0)===0)return;let e=Ie(t),n=Te(t.detail);return n?.startsWith("Hunk startup failed:")?`${n} (${e}).`:`Hunk exited unexpectedly (${e}).${n?`
4
+ ${n}`:""}`}function Mt(t){let e=Ie(t),n=Te(t.detail);return`Hunk exited before startup (${e}).${n?`
5
+ ${n}`:""}`}function Ie(t){let e=[`code ${t.exitCode}`];return(t.signal??0)!==0&&e.push(`signal ${t.signal}`),e.join(", ")}function Te(t){if(!t)return;let e=Wt(t.replace(/\r\n?/g,`
6
+ `)).split(`
7
+ `).map(i=>i.trimEnd()).filter(i=>i.trim().length>0).slice(-$t);if(e.length===0)return;let n=e.join(`
8
+ `).trim();return n.length>Le&&(n=`${n.slice(0,Le-1)}\u2026`),n||void 0}function Wt(t){let e="";for(let n of t){let i=n.charCodeAt(0);i<32&&i!==9&&i!==10||i===127||(e+=n)}return e}var Bt={active:{activate:"activating",shutdown:"shutting-down"},activating:{activate:"activating",shutdown:"shutting-down"},"shutting-down":{activate:"activating",shutdown:"shutting-down"},shutdown:{activate:"activating",shutdown:"shutting-down"}},Dt={active:"active",activating:"active","shutting-down":"shutting-down",shutdown:"active"};function jt(t,e){return{phase:Bt[t.phase][e],revision:t.revision+1}}var Ut={none:{opened:"owned",adopt:"none",release:"none"},owned:{opened:"owned",adopt:"adopted",release:"none"},adopted:{opened:"adopted",adopt:"adopted",release:"none"}};function Ae(){return{openAttempt:"available",earlyOpen:{phase:"none"},earlySurface:"none",suppression:null}}function Kt(t,e){switch(e.type){case"reset":return Ae();case"mark-open-attempt":return{...t,openAttempt:"attempted"};case"set-early-open":return{...t,earlyOpen:e.promise?{phase:"pending",promise:e.promise}:{phase:"none"}};case"early-surface":return{...t,earlySurface:Ut[t.earlySurface][e.event]};case"suppress":return t.suppression==="review-complete"&&e.reason!=="review-complete"?t:{...t,suppression:e.reason}}}var L=class{overlay;navigateHunk;active=null;transitionQueue=Promise.resolve();generation=0;followRevision=0;followNavigationQueue=Promise.resolve();followTimer=null;pendingFollowPath;runState=Ae();lifecycle={phase:"active",revision:0};stateListeners=new Set;cancellationListeners=new Set;blockingState="idle";constructor(e={}){this.overlay=e.overlay??new j,this.navigateHunk=e.navigateHunk??He,this.overlay.setStateListener(()=>this.notifyStateChange()),this.overlay.setChildExitListener?.(n=>this.onChildExit(n)),this.overlay.setTransitionScheduler?.(n=>(this.transitionRun({type:"early-surface",event:"adopt"}),this.exclusive(n)))}onStateChange(e){return this.stateListeners.add(e),()=>this.stateListeners.delete(e)}notifyStateChange(){for(let e of[...this.stateListeners])try{e()}catch{}}exclusive(e){let n=this.transitionQueue.then(e);return this.transitionQueue=n.then(()=>{},()=>{}),n}transitionRun(e){this.runState=Kt(this.runState,e)}requestLifecycle(e){return this.lifecycle=jt(this.lifecycle,e),this.lifecycle.revision}completeLifecycle(e,n,i){if(e===this.lifecycle.revision){if(this.lifecycle.phase!==n)throw new Error(`Invalid Hunk coordinator lifecycle completion: ${this.lifecycle.phase} -> ${i}.`);this.lifecycle={phase:i,revision:e}}}isActiveLifecycle(){return this.lifecycle.phase==="active"}resetRunFlags(){this.transitionRun({type:"reset"})}hasOpenedForRun(){return this.runState.openAttempt==="attempted"}markOpenedForRun(){this.transitionRun({type:"mark-open-attempt"})}hasEarlySurfaceOpenedForRun(){return this.runState.earlySurface!=="none"}getAutoOpenSuppressionReason(){return this.runState.suppression}markReviewCompleteForRun(){this.transitionRun({type:"suppress",reason:"review-complete"})}suppressAutoOpenForRun(e="dismissed"){this.transitionRun({type:"suppress",reason:e})}isBlocking(){return this.blockingState==="blocking"}setBlockingReview(e){this.blockingState=e?"blocking":"idle"}onReviewCancellation(e){return this.cancellationListeners.add(e),()=>this.cancellationListeners.delete(e)}notifyReviewCancellation(e){for(let n of[...this.cancellationListeners])try{n(e)}catch{}}hasLiveSurface(){return!!this.active?.isLive()}getActiveInfo(){return this.active?.getInfo()??null}getEarlyOpenPromise(){return this.runState.earlyOpen.phase==="pending"?this.runState.earlyOpen.promise:null}setEarlyOpenPromise(e){this.transitionRun({type:"set-early-open",promise:e})}async ensureOpen(e,n,i,o){await this.exclusive(async()=>{this.assertAlive();let r=this.overlay.isLive();if(await this.overlay.ensure(e,this.buildRequest(e,n,i,o),n),!this.overlay.isLive()){if(this.overlay.getState()==="closed")return;throw new Error("Hunk overlay did not become live.")}this.active=this.overlay,o==="live"&&!r?this.transitionRun({type:"early-surface",event:"opened"}):o!=="live"&&this.transitionRun({type:"early-surface",event:"adopt"})}),this.notifyStateChange()}async enterReviewGate(e,n){await this.exclusive(async()=>{if(this.assertAlive(),this.active?.isLive()){await this.active.show(),this.transitionRun({type:"early-surface",event:"adopt"});return}if(await this.overlay.ensure(e,this.buildRequest(e,n,n.hunk.args,"manual"),n),!this.overlay.isLive()){if(this.overlay.getState()==="closed")return;throw new Error("Hunk overlay did not become live.")}this.active=this.overlay,this.transitionRun({type:"early-surface",event:"adopt"})}),this.notifyStateChange()}async toggleOverlay(e,n,i,o="shortcut"){await this.exclusive(async()=>{this.assertAlive();let r=this.buildRequest(e,n,i,o);if(this.active?.isLive()){await this.active.toggle(e,r,n),this.transitionRun({type:"early-surface",event:"adopt"});return}if(await this.overlay.toggle(e,r,n),!this.overlay.isLive()){if(this.overlay.getState()==="closed")return;throw new Error("Hunk overlay did not become live.")}this.active=this.overlay,this.transitionRun({type:"early-surface",event:"adopt"})}),this.notifyStateChange()}async closeActive(){this.suppressAutoOpenForRun("dismissed"),this.notifyReviewCancellation("close");let e=await this.exclusive(()=>this.closeActiveUnlocked());return this.notifyStateChange(),e}async closeEarlySurfaceOpenedForRun(){let e=await this.exclusive(async()=>{if(this.runState.earlySurface!=="owned")return!1;let n=this.active;if(!n||!n.isLive()&&n.getState()==="closed")return this.active=null,this.transitionRun({type:"early-surface",event:"release"}),!1;let i=await n.release();return this.active=null,this.transitionRun({type:"early-surface",event:"release"}),i});return e&&this.notifyStateChange(),e}scheduleFollowEdit(e,n,i){if(!this.hasLiveSurface()&&!this.getEarlyOpenPromise())return;this.pendingFollowPath=i;let o=++this.followRevision;this.followTimer&&clearTimeout(this.followTimer);let r=this.generation;this.followTimer=setTimeout(()=>{this.followTimer=null;let s=this.pendingFollowPath;this.pendingFollowPath=void 0,!(!s||!this.isActiveLifecycle()||r!==this.generation)&&this.runFollow(e,n,s,r,o)},150)}async shutdown(){let e=this.requestLifecycle("shutdown");await this.exclusive(()=>this.cleanupAll()),this.completeLifecycle(e,"shutting-down","shutdown"),this.notifyStateChange()}async activateSession(){let e=this.requestLifecycle("activate");await this.exclusive(async()=>{(this.overlay.isLive()||this.active)&&await this.cleanupAll(),this.generation+=1,this.active=null,this.transitionRun({type:"reset"})}),this.completeLifecycle(e,"activating","active"),this.notifyStateChange()}revive(){let e=Dt[this.lifecycle.phase];e==="active"&&(this.lifecycle={phase:e,revision:this.lifecycle.revision+1},this.generation+=1,this.active=null,this.transitionRun({type:"reset"}))}async cleanupAll(){this.notifyReviewCancellation("session-boundary"),this.blockingState="idle",this.generation+=1,this.followTimer&&clearTimeout(this.followTimer),this.followTimer=null,this.pendingFollowPath=void 0,this.transitionRun({type:"reset"});try{await this.overlay.close()}catch{}this.active=null}assertAlive(){if(!this.isActiveLifecycle())throw new Error(`Hunk coordinator is shut down or transitioning (${this.lifecycle.phase}).`)}async closeActiveUnlocked(){let e=this.active;if(!e||!e.isLive()&&e.getState()==="closed")return this.active=null,this.transitionRun({type:"early-surface",event:"adopt"}),!1;let n=await e.release();return this.active=null,this.transitionRun({type:"early-surface",event:"adopt"}),n}onChildExit(e){this.notifyReviewCancellation("hunk-died"),this.transitionRun({type:"early-surface",event:"adopt"}),e.exitCode===0&&(e.signal??0)===0&&this.suppressAutoOpenForRun("dismissed")}buildRequest(e,n,i,o){return{cwd:e.cwd,command:n.hunk.command,args:i,source:o,focus:o==="manual"||o==="shortcut"}}isCurrentFollow(e,n){return this.isActiveLifecycle()&&e===this.generation&&n===this.followRevision}async runFollow(e,n,i,o,r){if(!this.isCurrentFollow(o,r))return;let s=this.getEarlyOpenPromise();if(s)try{await s}catch{return}this.hasLiveSurface()&&(await new Promise(u=>setTimeout(u,200)),this.isCurrentFollow(o,r)&&await this.queueFollowNavigation(async()=>{if(!this.isCurrentFollow(o,r))return;let u=()=>this.navigateHunk({cwd:e.cwd,filePath:i,hunkBinary:n.hunk.command,managedPid:this.getActiveInfo()?.pid});try{await u()}catch{try{if(await new Promise(a=>setTimeout(a,400)),!this.isCurrentFollow(o,r))return;await u()}catch(a){if(!this.isCurrentFollow(o,r))return;let l=a instanceof Error?a.message:String(a);try{e.ui.notify(`Hunk follow-edit navigation failed: ${l}`,"warning")}catch{}}}}))}queueFollowNavigation(e){let n=this.followNavigationQueue.then(e);return this.followNavigationQueue=n.then(()=>{},()=>{}),n}};import{matchesKey as qt,parseKey as $e,truncateToWidth as Jt}from"@earendil-works/pi-tui";var le=[{value:"full",label:"Full \u2014 100% terminal"},{value:"left",label:"Left \u2014 50% split pane"},{value:"right",label:"Right \u2014 50% split pane"},{value:"float",label:"Float \u2014 centered 75% pane"}];function Gt(t,e){let n=t.trim().split(/\s+/).filter(Boolean);if(n.length===0||!R(n[0]))return;let i=e,o=!1;for(let r of n.slice(1))if(r==="experimental-wrap"||r==="wrap")i=!0,o=!0;else if(r==="no-experimental-wrap"||r==="no-wrap")i=!1;else return;if(n[0]==="full"||n[0]==="float"){if(o)return;i=!1}return{layout:n[0],experimentalPiWrap:i}}function Vt(t){return le.find(e=>e.value===t)?.label??t}function Yt(t){let e=$e(t);return C(e)?e:void 0}function zt(t){let e=$e(t);return k(e)?e:void 0}async function ue(t,e,n,i,o=[]){return t.ui.custom((r,s,u,a)=>{let l;return{render(c){let g=[s.fg("accent",s.bold(`Set Pi-hunk ${e}`)),`Current: ${n}`,`Press the ${e} you want to use.`,s.fg("dim",i==="prefix"?"Esc cancels. Plain typing and navigation keys are not allowed as prefixes.":"Esc cancels. The hotkey is combined with the Pi-hunk prefix.")];return l&&g.push(s.fg("warning",l)),g.map(d=>Jt(d,c))},handleInput(c){if(qt(c,"escape")){a(void 0);return}let g=i==="prefix"?Yt(c):zt(c);if(g&&!o.includes(g)){a(g);return}l=g&&o.includes(g)?`That key is already assigned in the Hunk chord (${g}).`:i==="prefix"?"That key would interfere with normal typing. Press a modified shortcut or a function key.":"That key cannot be used as a Hunk hotkey.",r.requestRender()},invalidate(){l=void 0}}})}function Ne(t,e){let n={};t.review!==e.review&&(n.review=e.review),t.followEdits!==e.followEdits&&(n.followEdits=e.followEdits);let i={};t.overlay.layout!==e.overlay.layout&&(i.layout=e.overlay.layout),t.overlay.experimentalPiWrap!==e.overlay.experimentalPiWrap&&(i.experimentalPiWrap=e.overlay.experimentalPiWrap),Object.keys(i).length>0&&(n.overlay=i);let o={};for(let r of["prefix","toggle","show"])t.bindings[r]!==e.bindings[r]&&(o[r]=e.bindings[r]);return Object.keys(o).length>0&&(n.bindings=o),n}async function _e(t,e,n,i,o,r,s){let u=Ne(i,o);if(Object.keys(u).length===0)return!0;try{await e.persist(t,"project",u)}catch(d){return t.ui.notify(`Could not update project Hunk config: ${d instanceof Error?d.message:String(d)}`,"error"),!1}let a=["prefix","toggle","show"].filter(d=>i.bindings[d]!==o.bindings[d]),l=e.get().bindings;["prefix","toggle","show"].some(d=>l[d]!==r[d])&&e.patchSession({bindings:r});let c=i.overlay.layout!==o.overlay.layout||i.overlay.experimentalPiWrap!==o.overlay.experimentalPiWrap,g=[];if(s&&g.push("Hunk configuration updated in .pi/hunk.json."),c&&n.hasLiveSurface()&&g.push("Close and reopen the current Hunk review to apply the new layout."),a.length>0){let d=`${o.bindings.prefix} then ${o.bindings.toggle}/${o.bindings.show}`;g.push(`Run /reload to activate the Pi-hunk chord ${d}.`)}return s&&c&&g.push(`Layout: ${Vt(o.overlay.layout)}${o.overlay.experimentalPiWrap?", Pi wrapping experimental":""}.`),g.length>0&&t.ui.notify(g.join(" "),"info"),!0}async function Qt(t,e,n){if(t.mode!=="tui"){t.ui.notify("Interactive Hunk configuration requires TUI mode. Usage: /hunk config restore | full|left|right|float [experimental-wrap|no-wrap]","warning");return}let i=e.get(),o={...i.bindings};for(;;){let r=i.overlay.experimentalPiWrap?"on (experimental)":"off",s=await t.ui.select("Pi-hunk configuration \u2014 changes auto-save to .pi/hunk.json",[`Review behavior: ${i.review}`,`Follow edits: ${i.followEdits?"on":"off"}`,`Overlay layout: ${i.overlay.layout}`,`Pi word wrap: ${r}`,`Hunk prefix: ${i.bindings.prefix}`,`Toggle hotkey: ${i.bindings.toggle}`,`Show hotkey: ${i.bindings.show}`,"Restore defaults\u2026","Done"]);if(!s||s==="Done")return;if(s==="Restore defaults\u2026"){if(!(await t.ui.select("Restore default Hunk configuration?",["Restore \u2014 remove project overrides","Cancel"]))?.startsWith("Restore"))continue;try{let l=await e.resetProject(t);["prefix","toggle","show"].some(d=>l.bindings[d]!==o[d])&&e.patchSession({bindings:o});let c=["prefix","toggle","show"].some(d=>i.bindings[d]!==l.bindings[d])?" Run /reload to activate the restored Hunk chord; the current chord remains active until then.":"",g=n.hasLiveSurface()&&(i.overlay.layout!==l.overlay.layout||i.overlay.experimentalPiWrap!==l.overlay.experimentalPiWrap)?" Close and reopen the current Hunk review to apply the restored layout.":"";t.ui.notify(`Project Hunk configuration removed; inherited/default settings restored.${c}${g}`,"info"),i=l}catch(l){t.ui.notify(`Could not restore Hunk defaults: ${l instanceof Error?l.message:String(l)}`,"error")}continue}let u=y(i);if(s.startsWith("Review behavior:")){let a=await t.ui.select("Automatic Hunk review",["off","after-run","live"]);if(a!=="off"&&a!=="after-run"&&a!=="live")continue;u.review=a}else if(s.startsWith("Follow edits:")){let a=await t.ui.select("Follow successful edits in Hunk",["on","off"]);if(!a)continue;u.followEdits=a==="on"}else if(s.startsWith("Overlay layout:")){let a=await t.ui.select("Hunk overlay layout",le.map(c=>c.label)),l=le.find(c=>c.label===a);if(!l)continue;u.overlay.layout=l.value,(l.value==="full"||l.value==="float")&&(u.overlay.experimentalPiWrap=!1)}else if(s.startsWith("Pi word wrap:")){if(i.overlay.layout!=="left"&&i.overlay.layout!=="right"){t.ui.notify("Experimental Pi word wrap only applies to left and right layouts.","info");continue}let a=await t.ui.select("Pi word wrapping",["Off \u2014 overlay only (stable)","On \u2014 wrap Pi beside Hunk (experimental)"]);if(!a)continue;u.overlay.experimentalPiWrap=a.startsWith("On")}else if(s.startsWith("Hunk prefix:")){let a=await ue(t,"prefix",i.bindings.prefix,"prefix",[i.bindings.toggle,i.bindings.show]);if(!a)continue;u.bindings.prefix=a}else if(s.startsWith("Toggle hotkey:")){let a=await ue(t,"toggle hotkey",i.bindings.toggle,"hotkey",[i.bindings.prefix,i.bindings.show]);if(!a)continue;u.bindings.toggle=a}else if(s.startsWith("Show hotkey:")){let a=await ue(t,"show hotkey",i.bindings.show,"hotkey",[i.bindings.prefix,i.bindings.toggle]);if(!a)continue;u.bindings.show=a}else continue;await _e(t,e,n,i,u,o,!1)&&(i=u)}}async function Fe(t,e,n,i){if(!e.isProjectTrusted()){e.ui.notify("Hunk configuration requires a trusted project so it can update .pi/hunk.json.","warning");return}if(!t.trim()){await Qt(e,n,i);return}let o=n.get();if(t.trim()==="restore"){try{let u=await n.resetProject(e),a=["prefix","toggle","show"].some(c=>u.bindings[c]!==o.bindings[c]);a&&n.patchSession({bindings:o.bindings});let l=o.overlay.layout!==u.overlay.layout||o.overlay.experimentalPiWrap!==u.overlay.experimentalPiWrap;e.ui.notify(`Project Hunk configuration removed; inherited/default settings restored.${a?" Run /reload to activate the restored Hunk chord; the current chord remains active until then.":""}${l&&i.hasLiveSurface()?" Close and reopen the current Hunk review to apply the restored layout.":""}`,"info")}catch(u){e.ui.notify(`Could not restore Hunk defaults: ${u instanceof Error?u.message:String(u)}`,"error")}return}let r=Gt(t,o.overlay.experimentalPiWrap);if(!r){let u=t.trim().split(/\s+/),a=u.slice(1).some(c=>c==="experimental-wrap"||c==="wrap"),l=u[0]==="full"||u[0]==="float";e.ui.notify(a&&l?"Experimental Pi word wrap only applies to left and right layouts.":"Usage: /hunk config restore | full|left|right|float [experimental-wrap|no-wrap]","warning");return}let s=y(o);if(s.overlay.layout=r.layout,s.overlay.experimentalPiWrap=r.experimentalPiWrap,Object.keys(Ne(o,s)).length===0){e.ui.notify("Hunk configuration is unchanged.","info");return}await _e(e,n,i,o,s,o.bindings,!0)}function Me(t,e){if(t===void 0)return;if(!Array.isArray(t)||t.length!==2)throw new Error(`Hunk comment JSON drift: ${e} must be a two-number range.`);let[n,i]=t;if(typeof n!="number"||typeof i!="number"||!Number.isInteger(n)||!Number.isInteger(i)||n<1||i<1||i<n)throw new Error(`Hunk comment JSON drift: ${e} must be an ordered positive range.`);return[n,i]}function Xt(t){let e=JSON.parse(t);if(!e||typeof e!="object"||!Array.isArray(e.comments))throw new Error("Hunk comment JSON drift: expected a comments array.");let n=new Set;return e.comments.map((i,o)=>{if(!i||typeof i!="object")throw new Error(`Hunk comment JSON drift: comments[${o}] must be an object.`);let r=i;if(r.source!=="user"||typeof r.filePath!="string"||typeof r.body!="string")throw new Error(`Hunk comment JSON drift: comments[${o}] requires source=user, filePath, and body.`);if(typeof r.noteId!="string"||r.noteId.length===0)throw new Error(`Hunk comment JSON drift: comments[${o}].noteId must be a non-empty string.`);if(n.has(r.noteId))throw new Error(`Hunk comment JSON drift: comments[${o}].noteId duplicates ${JSON.stringify(r.noteId)}.`);n.add(r.noteId);let s=Me(r.oldRange,`comments[${o}].oldRange`),u=Me(r.newRange,`comments[${o}].newRange`);if(!s&&!u)throw new Error(`Hunk comment JSON drift: comments[${o}] has no oldRange or newRange.`);return{source:"user",noteId:r.noteId,filePath:r.filePath,body:r.body,oldRange:s,newRange:u}})}function Zt(t){let[e="",...n]=t.body.trim().split(/\r?\n/);return{noteId:t.noteId,file:t.filePath,oldLine:t.oldRange?.[0]??null,newLine:t.newRange?.[0]??null,oldRange:t.oldRange??null,newRange:t.newRange??null,summary:e,rationale:n.join(`
9
+ `).trim()}}async function ce(t){let e=await se(t);if(!e)return{status:"no-live-session",message:"No live Hunk review session exists for this repository.",notes:[]};let n=await W(["session","comment","list",e.sessionId,"--type","user","--json"],t),i=Xt(n).map(Zt);return{status:"live",sessionId:e.sessionId,pid:e.pid,message:i.length===0?"The live Hunk review has no open user notes.":`${i.length} open Hunk review note(s).`,notes:i}}var en=250,tn={opening:{absent:"stay",starting:"stay",visible:"visible",hidden:"hidden",closing:"cancel"},"awaiting-live":{absent:"cancel",starting:"cancel",visible:"visible",hidden:"hidden",closing:"cancel"},visible:{absent:"stay",starting:"stay",visible:"stay",hidden:"probe",closing:"stay"},hidden:{absent:"stay",starting:"stay",visible:"visible",hidden:"stay",closing:"stay"},probing:{absent:"cancel",starting:"cancel",visible:"stay",hidden:"stay",closing:"cancel"}},U=class{constructor(e,n,i){this.coordinator=e;this.getConfig=n;this.run=i;e.onReviewCancellation(o=>this.cancel(o))}coordinator;getConfig;run;waiter=null;submittedNoteKeys=new Set;isWaiting(){return this.waiter!==null}async wait(e,n){if(e.mode!=="tui")return this.unavailable("not-tui");if(this.waiter)return{status:"already-waiting",message:`A Hunk review is already waiting for ${this.waiter.cwd}.`,notes:[]};if(n?.aborted)return this.cancelled("abort-signal");let i,o=new Promise(u=>{i=u}),r=()=>this.cancel("abort-signal");n?.addEventListener("abort",r,{once:!0});let s={cwd:e.cwd,resolve:i,unsubscribe:()=>{},removeAbort:()=>n?.removeEventListener("abort",r),state:"opening"};return this.waiter=s,this.coordinator.setBlockingReview(!0),s.unsubscribe=this.coordinator.onStateChange(()=>this.observe(e,s)),this.coordinator.enterReviewGate(e,this.getConfig()).then(()=>{this.waiter===s&&(s.state==="opening"&&(s.state="awaiting-live"),this.observe(e,s))},u=>{this.waiter===s&&this.cancel("open-failed",u instanceof Error?u.message:String(u))}),o}cancel(e,n){let i=this.waiter;i&&this.finish(i,this.cancelled(e,n))}cancelled(e,n){return{status:"cancelled",reason:e,message:n?`Hunk review cancelled (${e}): ${n}`:`Hunk review cancelled (${e}).`,notes:[]}}unavailable(e){return{status:"unavailable",reason:e,message:"Hunk review is available only in Pi's interactive TUI mode.",notes:[]}}observe(e,n){if(this.waiter!==n)return;let i=this.coordinator.getActiveInfo(),o=i?.state,r=!i||o===void 0||o==="closed"?"absent":o,s=i?.pid;switch(!n.sessionId&&s!==void 0&&Number.isInteger(s)&&s>0&&(n.managedPid=s),r==="starting"||r==="visible"||r==="hidden"?this.clearReplacementTimer(n):(n.state==="visible"||n.state==="hidden")&&(r==="absent"||r==="closing")&&this.armReplacementTimer(n),tn[n.state][r]){case"stay":return;case"visible":n.state="visible";return;case"hidden":n.state="hidden";return;case"probe":n.state="probing",this.probe(e,n);return;case"cancel":this.cancel("hunk-closed");return}}armReplacementTimer(e){e.replacementTimer||(e.replacementTimer=setTimeout(()=>{if(e.replacementTimer=void 0,this.waiter!==e)return;let n=this.coordinator.getActiveInfo()?.state;(!n||n==="closed"||n==="closing")&&this.cancel("hunk-closed")},en),e.replacementTimer.unref?.())}clearReplacementTimer(e){e.replacementTimer&&(clearTimeout(e.replacementTimer),e.replacementTimer=void 0)}noteKey(e,n){return`${e}\0${n.noteId}`}async probe(e,n){if(!(this.waiter!==n||n.state!=="probing"))try{let i=await ce({cwd:n.cwd,sessionId:n.sessionId,managedPid:n.managedPid,hunkBinary:this.getConfig().hunk.command,run:this.run});if(this.waiter!==n)return;if(i.status==="no-live-session"){this.cancel("hunk-died");return}n.sessionId=i.sessionId;let o=i.notes.filter(r=>!this.submittedNoteKeys.has(this.noteKey(i.sessionId,r)));if(o.length>0){for(let r of o)this.submittedNoteKeys.add(this.noteKey(i.sessionId,r));this.finish(n,{status:"submitted",message:o.length===i.notes.length?i.message:`${o.length} new Hunk review note(s); ${i.notes.length-o.length} already submitted in this Pi extension.`,notes:o});return}this.coordinator.markReviewCompleteForRun(),this.finish(n,{status:"approved",message:i.notes.length===0?"No Hunk user notes were found; hiding Hunk is treated as approval.":"No new Hunk user notes were found; hiding Hunk is treated as approval.",notes:[]})}catch(i){this.cancel("comment-probe-failed",i instanceof Error?i.message:String(i))}}finish(e,n){this.waiter===e&&(this.waiter=null,this.clearReplacementTimer(e),e.unsubscribe(),e.removeAbort(),this.coordinator.setBlockingReview(!1),e.resolve(n))}};function We(t,e){t.registerTool({name:"hunk_review",label:"Hunk Review",description:"Open Hunk and wait for the human to hide it. Returns only previously unseen user notes; hiding with no new notes returns approved. Read-only: never create, edit, apply, resolve, or clear comments.",promptSnippet:"Wait for fresh human review notes in Hunk (read-only)",promptGuidelines:["Call hunk_review when review is requested and address every returned note comment-by-comment.","Treat status=approved as the human's no-new-findings approval; do not keep waiting or retry unless a new review is requested.","Never create, edit, apply, resolve, or clear Hunk comments; hunk_review is read-only."],parameters:{type:"object",properties:{},additionalProperties:!1},async execute(n,i,o,r,s){let u=await e.wait(s,o);return{content:[{type:"text",text:JSON.stringify(u,null,2)}],details:u}}})}function on(t,e={}){let n=e.store??new x,i=e.detector??new H,o=e.coordinator??new L,r=new U(o,()=>n.get(),e.reviewRun),s,u=new Set,a=d=>{let f=n.get().bindings.prefix;s!==f&&(s!==void 0&&d.ui.notify(`Pi-hunk prefix changed to ${f}; ${s} stays active until Pi reloads extensions.`,"warning"),u.has(f)||(t.registerShortcut(f,{description:`Pi-hunk prefix (then ${n.get().bindings.toggle} to toggle or ${n.get().bindings.show} to show)`,handler:m=>yn(m,n,o)}),u.add(f)),s=f)},l;o.onStateChange(()=>{l&&w(l,n.get(),o)});let c={decision:null},g={store:n,detector:i,coordinator:o,diagnostics:c,registerPrefix:a,setStatusContext:d=>{l=d}};t.on("session_start",(d,f)=>rn(f,g)),t.on("session_shutdown",(d,f)=>sn(f,g)),t.on("before_agent_start",(d,f)=>an(d,f,n)),t.on("agent_start",(d,f)=>un(f,g)),t.on("agent_settled",(d,f)=>fn(f,g)),t.on("tool_call",(d,f)=>ln(d,f,g)),t.on("tool_execution_start",(d,f)=>cn(d,f,g)),t.on("tool_execution_end",(d,f)=>dn(d,f,g)),We(t,r),t.registerCommand("hunk",{description:"Hunk review: /hunk [target] \xB7 feedback \xB7 close \xB7 toggle \xB7 status \xB7 review [policy] \xB7 config",getArgumentCompletions:d=>te(d),handler:(d,f)=>gn(d,f,n,o,c,e.reviewRun,r,m=>t.sendUserMessage(m))})}async function rn(t,e){let{store:n,detector:i,coordinator:o,diagnostics:r,registerPrefix:s,setStatusContext:u}=e;u(t);try{await o.activateSession()}catch{o.revive()}i.reset(),r.decision=null;try{await n.reload(t,a=>t.ui.notify(a,"warning"))}catch(a){t.ui.notify(a instanceof Error?a.message:String(a),"warning")}s(t),w(t,n.get(),o)}async function sn(t,e){let{detector:n,coordinator:i,setStatusContext:o}=e;n.reset();try{await i.shutdown()}catch{}o(void 0),t.ui.setStatus("hunk",void 0)}function an(t,e,n){let i=n.get().review;if(!(i==="off"||e.mode!=="tui"))return{systemPrompt:`${t.systemPrompt}
10
+
11
+ Pi-hunk automatic review policy is "${i}". If this run successfully changes code, you MUST call hunk_review after the changes and before finishing. The tool blocks until the human hides Hunk; it returns fresh notes, approved when there are no new notes, or cancelled when Hunk closes/Q. Address every returned note, then call hunk_review again after any fixes until it returns approved. Do not call hunk_review for conversation-only or read-only turns.`}}function un(t,e){e.coordinator.resetRunFlags()}function ln(t,e,n){De(t.toolName,t.input,e,n)}function De(t,e,n,i){let{store:o,coordinator:r}=i,s=o.get();if(!N({review:s.review,uiMode:n.mode,alreadyOpenedForRun:r.hasOpenedForRun(),activeBlocking:r.isBlocking()})||!I(t,e,n.cwd)||(r.markOpenedForRun(),r.hasLiveSurface()))return;let u=r.ensureOpen(n,s,s.hunk.args,"live").catch(a=>{n.ui.notify(`Early Hunk open failed: ${a instanceof Error?a.message:String(a)}`,"warning")});r.setEarlyOpenPromise(u)}function cn(t,e,n){n.detector.rememberToolArgs(t.toolCallId,t.args),De(t.toolName,t.args,e,n)}function dn(t,e,n){let{store:i,detector:o,coordinator:r}=n,s=o.takeToolArgs(t.toolCallId);if(t.isError||!I(t.toolName,s,e.cwd))return;o.markChanged();let u=i.get();if(e.mode!=="tui"||!u.followEdits||!r.hasLiveSurface()&&!r.getEarlyOpenPromise())return;let a=G(s,e.cwd);if(!a)return;let l=V(a,e.cwd);r.scheduleFollowEdit(e,u,l)}async function fn(t,e){let{store:n,detector:i,coordinator:o,diagnostics:r}=e,s=n.get(),u=o.getEarlyOpenPromise();if(u)try{await u}catch{}finally{o.setEarlyOpenPromise(null)}let a=i.consumeSettled(),l=o.getAutoOpenSuppressionReason(),c=o.hasEarlySurfaceOpenedForRun();if(!a.mutation&&c&&!l)try{await o.closeEarlySurfaceOpenedForRun(),w(t,n.get(),o)}catch(v){t.ui.notify(`Could not close unused early Hunk review: ${v instanceof Error?v.message:String(v)}`,"warning")}let g=a.mutation,d=_({review:s.review,uiMode:t.mode,activeBlocking:o.isBlocking(),shouldReview:g,hasLiveSurface:o.hasLiveSurface(),autoOpenSuppression:l}),f=o.getActiveInfo(),m=F({action:d,review:s.review,uiMode:t.mode,activeBlocking:o.isBlocking(),activeVisible:o.hasLiveSurface()&&f?.state==="visible",activeLive:o.hasLiveSurface(),autoOpenSuppression:l});r.decision=m;try{if(d==="skip")return;await o.ensureOpen(t,s,s.hunk.args,d==="recover"?"recover":"auto"),w(t,n.get(),o)}catch(v){let S=v instanceof Error?v.message:String(v);r.decision={action:"failed",reason:m.action==="opened"?m.reason:"mutation",error:S},t.ui.notify(`Auto Hunk review failed: ${S}`,"warning")}finally{o.resetRunFlags()}}async function gn(t,e,n,i,o,r,s,u){let a=t.trim(),l=a.split(/\s+/)[0]??"",c=a.slice(l.length).trim();switch(l){case"close":if(!K("close",c,e))return;await kn(e,n,i);return;case"toggle":if(!K("toggle",c,e))return;await je(e,n,i);return;case"status":if(!K("status",c,e))return;await bn(e,n,i,o,r);return;case"feedback":if(!K("feedback",c,e))return;await pn(e,s,u);return;case"review":await Cn(c,e,n),w(e,n.get(),i);return;case"config":await Fe(c,e,n,i),w(e,n.get(),i);return;default:await mn(t,e,n,i);return}}function K(t,e,n){return e?(n.ui.notify(`Usage: /hunk ${t}`,"warning"),!1):!0}function hn(t){return["Manual Hunk feedback was submitted. Address every note below comment-by-comment, then run the relevant checks.",JSON.stringify({status:"submitted",notes:t},null,2)].join(`
12
+
13
+ `)}async function pn(t,e,n){if(t.mode!=="tui"){t.ui.notify("Hunk feedback requires Pi's interactive TUI mode.","warning");return}await t.waitForIdle();let i;try{i=await e.wait(t)}catch(o){t.ui.notify(`Could not collect Hunk feedback: ${o instanceof Error?o.message:String(o)}`,"error");return}if(i.status==="submitted"){try{n(hn(i.notes)),t.ui.notify(`Sent ${i.notes.length} Hunk feedback note${i.notes.length===1?"":"s"} to the agent.`,"info")}catch(o){t.ui.notify(`Could not send Hunk feedback to the agent: ${o instanceof Error?o.message:String(o)}`,"error")}return}t.ui.notify(i.message,i.status==="approved"?"info":"warning")}async function mn(t,e,n,i){if(e.mode!=="tui"){e.ui.notify("Hunk requires Pi's interactive TUI mode.","warning");return}await e.waitForIdle();let o=n.get(),r;try{r=M(t,o.hunk.args)}catch(s){e.ui.notify(s instanceof Error?s.message:String(s),"error");return}try{await i.ensureOpen(e,o,r,"manual"),w(e,n.get(),i)}catch(s){e.ui.notify(`Hunk failed: ${s instanceof Error?s.message:String(s)}`,"error")}}async function vn(t,e){return t.ui.custom((n,i,o,r)=>({render(s){return[nn(`${i.fg("accent",i.bold("Pi-hunk"))} ${e.toggle} toggle \xB7 ${e.show} show last commit \xB7 esc cancel`,s)]},handleInput(s){Be(s,e.toggle)?r("toggle"):Be(s,e.show)?r("show"):r(void 0)},invalidate(){}}))}async function yn(t,e,n){if(t.mode!=="tui"){t.ui.notify("Pi-hunk shortcuts require Pi's interactive TUI mode.","warning");return}let i=await vn(t,e.get().bindings);i==="toggle"?await je(t,e,n):i==="show"&&await wn(t,e,n)}async function wn(t,e,n){if(t.mode!=="tui"){t.ui.notify("Hunk requires Pi's interactive TUI mode.","warning");return}let i=e.get();try{await n.ensureOpen(t,i,["show"],"shortcut"),w(t,e.get(),n)}catch(o){t.ui.notify(`Hunk show failed: ${o instanceof Error?o.message:String(o)}`,"error")}}async function je(t,e,n){if(t.mode!=="tui"){t.ui.notify("Hunk requires Pi's interactive TUI mode.","warning");return}let i=e.get();try{await n.toggleOverlay(t,i,i.hunk.args,"shortcut"),w(t,e.get(),n)}catch(o){t.ui.notify(`Hunk toggle failed: ${o instanceof Error?o.message:String(o)}`,"error")}}async function kn(t,e,n){try{let i=await n.closeActive();w(t,e.get(),n),t.ui.notify(i?"Closed Hunk review.":"No active Hunk review to close.","info")}catch(i){t.ui.notify(`Could not close Hunk: ${i instanceof Error?i.message:String(i)}`,"error")}}function Sn(t){return t?t.action==="failed"?`failed(${t.reason}: ${t.error})`:`${t.action}(${t.reason})`:"none (no agent run has settled yet)"}async function bn(t,e,n,i,o){let r=e.get(),s=n.getActiveInfo(),u=s?`overlay:${s.state}${s.detail?`(${s.detail})`:""}`:"none",a="no-live-session";try{let l=await ce({cwd:t.cwd,managedPid:s?.pid,hunkBinary:r.hunk.command,run:o});l.status==="live"&&(a=String(l.notes.length))}catch(l){a=`unavailable(${l instanceof Error?l.message:String(l)})`}t.ui.notify(`Hunk: review=${r.review}, layout=${r.overlay.layout}, experimental-pi-wrap=${r.overlay.experimentalPiWrap?"on":"off"}, active=${u}, command=${r.hunk.command}
14
+ open-notes=${a}, last-auto-open=${Sn(i.decision)}`,"info")}async function Cn(t,e,n){let i=t.trim().split(/\s+/).filter(Boolean);if(i.length===0){e.ui.notify(`Hunk review: ${n.get().review}`,"info");return}let o=i[0];if(i.length!==1||!o||!b(o)){e.ui.notify("Usage: /hunk review off|after-run|live","warning");return}if(!e.isProjectTrusted()){e.ui.notify("Hunk configuration requires a trusted project so it can update .pi/hunk.json.","warning");return}try{await n.persist(e,"project",{review:o})}catch(s){e.ui.notify(`Could not update project Hunk config: ${s instanceof Error?s.message:String(s)}`,"error");return}let r=n.get().review;if(r!==o){e.ui.notify(`Hunk review=${o} was saved to .pi/hunk.json, but PI_HUNK_REVIEW keeps review=${r}.`,"warning");return}e.ui.notify(`Hunk review set to ${o} in .pi/hunk.json.`,"info")}function w(t,e,n){let i=n.getActiveInfo(),o;i?.state==="visible"?o="hunk: visible":i?.state==="hidden"?o="hunk: hidden":i?.state==="starting"?o="hunk: starting":e.review!=="off"&&(o=`hunk: ${e.review}`),t.ui.setStatus("hunk",o?t.ui.theme.fg(i?.state==="visible"?"success":"dim",o):void 0)}export{H as ChangeDetector,x as ConfigStore,O as DEFAULT_CONFIG,L as ReviewCoordinator,on as default,Sn as describeSettledDecision,F as explainSettledDecision,hn as formatManualFeedback,pn as handleFeedback,me as isMutation,I as isWorkspaceMutation,G as mutationTargetPath,vn as readHunkPrefixAction,M as resolveHunkArgs,_ as settledAutoOpenAction,N as shouldEarlyOpenOnMutation,V as toWorkspaceRelative};
package/package.json CHANGED
@@ -1,10 +1,86 @@
1
- {
2
- "name": "pi-hunk",
3
- "version": "0.0.0",
4
- "description": "Package initialization placeholder",
5
- "license": "MIT",
6
- "files": ["README.md"],
7
- "publishConfig": {
8
- "access": "public"
9
- }
10
- }
1
+ {
2
+ "name": "pi-hunk",
3
+ "version": "0.1.0",
4
+ "description": "Review Pi coding-agent changes in a native, persistent Hunk overlay",
5
+ "keywords": [
6
+ "code-review",
7
+ "coding-agent",
8
+ "diff",
9
+ "hunk",
10
+ "overlay",
11
+ "pi-extension",
12
+ "pi-package",
13
+ "tui"
14
+ ],
15
+ "homepage": "https://github.com/igshehata/pi-hunk#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/igshehata/pi-hunk/issues"
18
+ },
19
+ "license": "MIT",
20
+ "author": "Islam Shehata",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/igshehata/pi-hunk.git"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "CHANGELOG.md",
29
+ "LICENSE"
30
+ ],
31
+ "type": "module",
32
+ "main": "./dist/index.js",
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "audit": "npm audit --audit-level=high",
38
+ "build": "node scripts/build.mjs",
39
+ "check": "npm run format:check && npm run lint && npm run typecheck && npm ls --depth=0 && npm run audit && npm test && npm run build && npm run verify:package",
40
+ "format": "oxfmt .",
41
+ "format:check": "oxfmt --check .",
42
+ "lint": "oxlint .",
43
+ "prepare": "npm run build",
44
+ "test": "vitest run",
45
+ "test:critical": "vitest run test/auto-review-integration.test.ts test/overlay-embedded-integration.test.ts test/experimental-pi-wrap.test.ts test/overlay-controller.test.ts test/embedded-visibility.test.ts test/config-command.test.ts test/extension-toggle.test.ts test/zigpty-adapter.test.ts",
46
+ "test:watch": "vitest",
47
+ "typecheck": "tsc --noEmit",
48
+ "verify:package": "node scripts/verify-package.mjs"
49
+ },
50
+ "dependencies": {
51
+ "@coder/libghostty-vt-node": "0.1.0-beta.1",
52
+ "zigpty": "0.2.1"
53
+ },
54
+ "devDependencies": {
55
+ "@changesets/cli": "2.31.1",
56
+ "@earendil-works/pi-coding-agent": "0.80.6",
57
+ "@earendil-works/pi-tui": "0.80.6",
58
+ "@types/node": "24.13.3",
59
+ "esbuild": "0.28.1",
60
+ "oxfmt": "0.59.0",
61
+ "oxlint": "1.74.0",
62
+ "typescript": "5.9.3",
63
+ "vitest": "4.1.10"
64
+ },
65
+ "peerDependencies": {
66
+ "@earendil-works/pi-coding-agent": "*",
67
+ "@earendil-works/pi-tui": "*"
68
+ },
69
+ "peerDependenciesMeta": {
70
+ "@earendil-works/pi-coding-agent": {
71
+ "optional": true
72
+ },
73
+ "@earendil-works/pi-tui": {
74
+ "optional": true
75
+ }
76
+ },
77
+ "engines": {
78
+ "node": ">=22.19.0"
79
+ },
80
+ "packageManager": "npm@11.17.0",
81
+ "pi": {
82
+ "extensions": [
83
+ "./dist/index.js"
84
+ ]
85
+ }
86
+ }