@xynogen/pix-hunk 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -8
- package/package.json +5 -5
- package/src/index.ts +35 -30
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pix-hunk
|
|
2
2
|
|
|
3
3
|
Pi tool for driving a live [Hunk](https://github.com/modem-dev/hunk) diff review.
|
|
4
4
|
One `hunk` call accepts ordered `ops[]`, so an agent can inspect, navigate,
|
|
@@ -7,7 +7,10 @@ quoting.
|
|
|
7
7
|
|
|
8
8
|
## Requirements
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Launch Hunk's interactive TUI in another terminal. If `hunk` isn't found
|
|
11
|
+
(`~/.pi/agent/binary.json` → `~/.pi/agent/bin` → `PATH`), pix downloads the
|
|
12
|
+
official `modem-dev/hunk` release on first use and verifies it against
|
|
13
|
+
`SHA256SUMS`, with a visible status. This extension
|
|
11
14
|
only calls `hunk session *`; it never starts `hunk diff`, `show`, `patch`, or
|
|
12
15
|
another interactive command.
|
|
13
16
|
|
|
@@ -54,20 +57,33 @@ Model-facing output defaults to 10,000 characters. Set `maxCharacters` between
|
|
|
54
57
|
quota, so a large patch cannot hide later results. Full structured results
|
|
55
58
|
remain available in tool details for inspection.
|
|
56
59
|
|
|
60
|
+
## Scope ceiling
|
|
61
|
+
|
|
62
|
+
`comment apply`, STML markup, and comment clearing are intentionally omitted.
|
|
63
|
+
Use Hunk's CLI directly until repeated demand justifies extending the schema.
|
|
64
|
+
|
|
57
65
|
## Install
|
|
58
66
|
|
|
59
67
|
```bash
|
|
60
68
|
pi install npm:@xynogen/pix-hunk
|
|
61
69
|
```
|
|
62
70
|
|
|
63
|
-
Standalone
|
|
64
|
-
package is not bundled by `@xynogen/pix-core`.
|
|
71
|
+
> Standalone and opt-in. [`@xynogen/pix-core`](https://www.npmjs.com/package/@xynogen/pix-core) does not bundle it. It needs the external Hunk CLI and a live review session.
|
|
65
72
|
|
|
66
|
-
##
|
|
73
|
+
## Full distro
|
|
67
74
|
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
This package is part of [Pix](https://github.com/xynogen/pix-mono). The installer sets up Pi and the full distro. See [Install](https://github.com/xynogen/pix-mono#install) for the notes for each OS.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Linux / macOS
|
|
79
|
+
curl -fsSL https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.sh | sh
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```powershell
|
|
83
|
+
# Windows
|
|
84
|
+
irm https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.ps1 | iex
|
|
85
|
+
```
|
|
70
86
|
|
|
71
87
|
## License
|
|
72
88
|
|
|
73
|
-
MIT
|
|
89
|
+
MIT. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-hunk",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Pi tool
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Pi tool \u2014 drive live Hunk diff reviews with ordered bulk operations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"exports": {
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@xynogen/pix-pretty": "^1.
|
|
48
|
-
"@xynogen/pix-runtime": "^0.
|
|
49
|
-
"typebox": "^1.1.38"
|
|
47
|
+
"@xynogen/pix-pretty": "^1.30.0",
|
|
48
|
+
"@xynogen/pix-runtime": "^0.13.0"
|
|
50
49
|
},
|
|
51
50
|
"peerDependencies": {
|
|
51
|
+
"typebox": "*",
|
|
52
52
|
"@earendil-works/pi-coding-agent": "*",
|
|
53
53
|
"@earendil-works/pi-tui": "*"
|
|
54
54
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import { reportToolStatus, type ToolStatusUI } from "@xynogen/pix-pretty/tool-status";
|
|
4
4
|
import {
|
|
5
5
|
dotJoin,
|
|
6
6
|
formatCollapsedToolRow,
|
|
7
|
+
formatToolCallTitle,
|
|
7
8
|
frameToolResult,
|
|
9
|
+
getErrorMessage,
|
|
8
10
|
hideCollapsedToolCall,
|
|
9
11
|
} from "@xynogen/pix-pretty/utils";
|
|
10
12
|
import { type CollapseState, tickCollapse } from "@xynogen/pix-runtime/collapse";
|
|
13
|
+
import { runTool } from "@xynogen/pix-runtime/exec";
|
|
11
14
|
import { Type } from "typebox";
|
|
12
15
|
import {
|
|
13
16
|
buildHunkArgs,
|
|
@@ -26,22 +29,29 @@ export interface HunkRunResult {
|
|
|
26
29
|
code: number;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
export type HunkRunner = (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
export type HunkRunner = (
|
|
33
|
+
args: string[],
|
|
34
|
+
signal?: AbortSignal,
|
|
35
|
+
ui?: ToolStatusUI,
|
|
36
|
+
) => Promise<HunkRunResult>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Default runner: resolve hunk via pix-runtime (binary.json → agent bin →
|
|
40
|
+
* PATH), downloading the official release on first use with a visible status.
|
|
41
|
+
* runTool also runs the npm `hunk.cmd` shim on Windows. A missing binary
|
|
42
|
+
* throws BinaryMissingError carrying the install hint.
|
|
43
|
+
*/
|
|
44
|
+
export const runHunk: HunkRunner = async (args, signal, ui) => {
|
|
45
|
+
const r = await runTool("hunk", args, {
|
|
46
|
+
signal,
|
|
47
|
+
onStatus: reportToolStatus(ui),
|
|
48
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
44
49
|
});
|
|
50
|
+
if (signal?.aborted) throw signal.reason ?? new Error("aborted");
|
|
51
|
+
if (r.truncated)
|
|
52
|
+
return { stdout: r.stdout, stderr: `hunk output exceeded ${MAX_OUTPUT_BYTES} bytes`, code: 1 };
|
|
53
|
+
return { stdout: r.stdout, stderr: r.stderr, code: r.code ?? 1 };
|
|
54
|
+
};
|
|
45
55
|
|
|
46
56
|
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
|
|
47
57
|
|
|
@@ -361,10 +371,7 @@ function modelText(results: HunkOperationResult[], ops: ToolOp[], maxCharacters:
|
|
|
361
371
|
}
|
|
362
372
|
|
|
363
373
|
function executionError(run: HunkRunResult): string {
|
|
364
|
-
|
|
365
|
-
return /(?:^|\s)(?:spawn\s+)?hunk\s+ENOENT(?:\s|$)/i.test(raw)
|
|
366
|
-
? "Hunk CLI not found. Install Hunk, launch a review session, then retry."
|
|
367
|
-
: raw;
|
|
374
|
+
return (run.stderr || run.stdout || `hunk exited ${run.code}`).trim();
|
|
368
375
|
}
|
|
369
376
|
|
|
370
377
|
function actionSummary(actions: HunkAction[]): string {
|
|
@@ -440,6 +447,7 @@ function renderExpanded(details: HunkResultDetails, theme: HunkTheme): string {
|
|
|
440
447
|
export default function registerHunk(pi: ExtensionAPI, runner: HunkRunner = runHunk): void {
|
|
441
448
|
pi.registerTool({
|
|
442
449
|
name: "hunk",
|
|
450
|
+
...({ exposure: "deferred" } as const),
|
|
443
451
|
label: "Hunk",
|
|
444
452
|
renderShell: "self",
|
|
445
453
|
description:
|
|
@@ -472,7 +480,7 @@ export default function registerHunk(pi: ExtensionAPI, runner: HunkRunner = runH
|
|
|
472
480
|
return text;
|
|
473
481
|
const actions = ((args as { ops?: ToolOp[] }).ops ?? []).map((op) => op.action);
|
|
474
482
|
text.setText(
|
|
475
|
-
`${theme
|
|
483
|
+
`${formatToolCallTitle(theme, "hunk", context)} ${theme.fg("dim", actionSummary(actions))}`,
|
|
476
484
|
);
|
|
477
485
|
return text;
|
|
478
486
|
},
|
|
@@ -483,14 +491,11 @@ export default function registerHunk(pi: ExtensionAPI, runner: HunkRunner = runH
|
|
|
483
491
|
text.setText(
|
|
484
492
|
result.content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join("\n"),
|
|
485
493
|
);
|
|
486
|
-
return text;
|
|
494
|
+
return options.isPartial ? text : frameToolResult(text, theme, context.isError);
|
|
487
495
|
}
|
|
488
|
-
const collapsed =
|
|
489
|
-
|
|
490
|
-
context.state as CollapseState,
|
|
491
|
-
context.invalidate,
|
|
492
|
-
options.expanded,
|
|
493
|
-
);
|
|
496
|
+
const collapsed =
|
|
497
|
+
!options.isPartial &&
|
|
498
|
+
tickCollapse("hunk", context.state as CollapseState, context.invalidate, options.expanded);
|
|
494
499
|
if (collapsed) {
|
|
495
500
|
text.setText(renderSummary(details, theme as HunkTheme));
|
|
496
501
|
return text;
|
|
@@ -505,7 +510,7 @@ export default function registerHunk(pi: ExtensionAPI, runner: HunkRunner = runH
|
|
|
505
510
|
const results: HunkOperationResult[] = [];
|
|
506
511
|
for (const op of ops) {
|
|
507
512
|
try {
|
|
508
|
-
const run = await runner(buildHunkArgs(op, ctx.cwd), signal);
|
|
513
|
+
const run = await runner(buildHunkArgs(op, ctx.cwd), signal, ctx.ui);
|
|
509
514
|
results.push(
|
|
510
515
|
run.code === 0
|
|
511
516
|
? { action: op.action, ok: true, data: parseOutput(run.stdout || run.stderr) }
|
|
@@ -516,7 +521,7 @@ export default function registerHunk(pi: ExtensionAPI, runner: HunkRunner = runH
|
|
|
516
521
|
results.push({
|
|
517
522
|
action: op.action,
|
|
518
523
|
ok: false,
|
|
519
|
-
error:
|
|
524
|
+
error: getErrorMessage(error),
|
|
520
525
|
});
|
|
521
526
|
}
|
|
522
527
|
}
|