Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,14 @@ hunk session reload --repo /path/to/worktree -- diff
hunk session reload --session-path /path/to/live-window --source /path/to/other-checkout -- diff
hunk session reload --repo . -- show HEAD~1 -- README.md
hunk session comment add --repo . --file README.md --new-line 103 --summary "Tighten this wording"
hunk session comment add --repo . --file README.md --new-line 103 --summary "Tighten this wording" --focus
hunk session comment list --repo .
hunk session comment rm --repo . <comment-id>
hunk session comment clear --repo . --file README.md --yes
```

`hunk session reload ... -- <hunk command>` swaps what a live session is showing without opening a new TUI window.
Pass `--focus` to jump the live session to the new note.

- `--repo <path>` selects the live session by its current loaded repo root.
- `--source <path>` is reload-only: it changes where the nested `diff` / `show` command runs, but does not select the session.
Expand Down
5 changes: 3 additions & 2 deletions skills/hunk-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ hunk session reload --session-path /path/to/live-window --source /path/to/other-
### Comments

```bash
hunk session comment add --repo . --file README.md --new-line 103 --summary "Tighten this wording" [--rationale "..."] [--author "agent"] [--no-reveal]
hunk session comment add --repo . --file README.md --new-line 103 --summary "Tighten this wording" [--rationale "..."] [--author "agent"] [--focus]
hunk session comment list --repo . [--file README.md]
hunk session comment rm --repo . <comment-id>
hunk session comment clear --repo . --yes [--file README.md]
```

- `comment add` requires `--file`, `--summary`, and exactly one of `--old-line` or `--new-line`
- `comment add` reveals the note by default; pass `--no-reveal` to keep the current focus
- Pass `--focus` when you want to jump to the new note
- `comment list` and `comment clear` accept optional `--file`
- Quote `--summary` and `--rationale` defensively in the shell

Expand Down Expand Up @@ -126,6 +126,7 @@ Guidelines:

- Work in the order that tells the clearest story, not necessarily file order
- Navigate before commenting so the user sees the code you're discussing
- Use `--focus` sparingly when the note itself should actively steer the review
- Keep comments focused: intent, structure, risks, or follow-ups
- Don't comment on every hunk -- highlight what the user wouldn't spot themselves

Expand Down
13 changes: 6 additions & 7 deletions src/core/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ async function parseSessionCommand(tokens: string[]): Promise<ParsedCliInput> {
" hunk session navigate (<session-id> | --repo <path>) (--next-comment | --prev-comment)",
" hunk session reload (<session-id> | --repo <path> | --session-path <path>) [--source <path>] -- diff [ref] [-- <pathspec...>]",
" hunk session reload (<session-id> | --repo <path> | --session-path <path>) [--source <path>] -- show [ref] [-- <pathspec...>]",
" hunk session comment add (<session-id> | --repo <path>) --file <path> (--old-line <n> | --new-line <n>) --summary <text>",
" hunk session comment add (<session-id> | --repo <path>) --file <path> (--old-line <n> | --new-line <n>) --summary <text> [--focus]",
" hunk session comment list (<session-id> | --repo <path>)",
" hunk session comment rm (<session-id> | --repo <path>) <comment-id>",
" hunk session comment clear (<session-id> | --repo <path>) --yes",
Expand Down Expand Up @@ -728,7 +728,7 @@ async function parseSessionCommand(tokens: string[]): Promise<ParsedCliInput> {
text:
[
"Usage:",
" hunk session comment add (<session-id> | --repo <path>) --file <path> (--old-line <n> | --new-line <n>) --summary <text>",
" hunk session comment add (<session-id> | --repo <path>) --file <path> (--old-line <n> | --new-line <n>) --summary <text> [--focus]",
" hunk session comment list (<session-id> | --repo <path>) [--file <path>]",
" hunk session comment rm (<session-id> | --repo <path>) <comment-id>",
" hunk session comment clear (<session-id> | --repo <path>) [--file <path>] --yes",
Expand All @@ -747,8 +747,7 @@ async function parseSessionCommand(tokens: string[]): Promise<ParsedCliInput> {
.option("--new-line <n>", "1-based line number on the new side", parsePositiveInt)
.option("--rationale <text>", "optional longer explanation")
.option("--author <name>", "optional author label")
.option("--reveal", "jump to and reveal the note")
.option("--no-reveal", "add the note without moving focus")
.option("--focus", "add the note and focus the viewport on it")
.option("--json", "emit structured JSON");

let parsedSessionId: string | undefined;
Expand All @@ -760,7 +759,7 @@ async function parseSessionCommand(tokens: string[]): Promise<ParsedCliInput> {
newLine?: number;
rationale?: string;
author?: string;
reveal?: boolean;
focus?: boolean;
json?: boolean;
} = {
file: "",
Expand All @@ -778,7 +777,7 @@ async function parseSessionCommand(tokens: string[]): Promise<ParsedCliInput> {
newLine?: number;
rationale?: string;
author?: string;
reveal?: boolean;
focus?: boolean;
json?: boolean;
},
) => {
Expand Down Expand Up @@ -812,7 +811,7 @@ async function parseSessionCommand(tokens: string[]): Promise<ParsedCliInput> {
summary: parsedOptions.summary,
rationale: parsedOptions.rationale,
author: parsedOptions.author,
reveal: parsedOptions.reveal ?? true,
reveal: parsedOptions.focus ?? false,
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/ui/hooks/useHunkSessionBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export function useHunkSessionBridge({
[file.id]: [...(current[file.id] ?? []), liveComment],
}));

if (message.input.reveal ?? true) {
if (message.input.reveal ?? false) {
jumpToFile(file.id, hunkIndex);
openAgentNotes();
}
Expand Down
33 changes: 31 additions & 2 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ describe("parseCli", () => {
).rejects.toThrow("Pass the replacement Hunk command after `--`");
});

test("parses session comment add", async () => {
test("parses session comment add without focusing by default", async () => {
const parsed = await parseCli([
"bun",
"hunk",
Expand All @@ -324,7 +324,6 @@ describe("parseCli", () => {
"Live review is the main value.",
"--author",
"Pi",
"--no-reveal",
]);

expect(parsed).toEqual({
Expand All @@ -342,6 +341,36 @@ describe("parseCli", () => {
});
});

test("parses session comment add with --focus", async () => {
const parsed = await parseCli([
"bun",
"hunk",
"session",
"comment",
"add",
"session-1",
"--file",
"README.md",
"--new-line",
"103",
"--summary",
"Frame this as MCP-first",
"--focus",
]);

expect(parsed).toEqual({
kind: "session",
action: "comment-add",
selector: { sessionId: "session-1" },
filePath: "README.md",
side: "new",
line: 103,
summary: "Frame this as MCP-first",
reveal: true,
output: "text",
});
});

test("parses session comment list with file filter", async () => {
const parsed = await parseCli([
"bun",
Expand Down
3 changes: 3 additions & 0 deletions test/mcp-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ describe("live session end-to-end", () => {
"Injected after the Hunk session auto-started the local daemon.",
"--author",
"Pi",
"--focus",
"--json",
],
port,
Expand Down Expand Up @@ -428,6 +429,7 @@ describe("live session end-to-end", () => {
"Alpha note",
"--rationale",
"Delivered only to the alpha Hunk session.",
"--focus",
],
port,
);
Expand All @@ -446,6 +448,7 @@ describe("live session end-to-end", () => {
"Beta note",
"--rationale",
"Delivered only to the beta Hunk session.",
"--focus",
],
port,
);
Expand Down
90 changes: 88 additions & 2 deletions test/session-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ describe("session CLI", () => {
}
}, 20_000);

test("navigate and comment add control a live Hunk session", async () => {
test("navigate works, and comment add only focuses the session when --focus is passed", async () => {
if (!ttyToolsAvailable) {
return;
}
Expand Down Expand Up @@ -340,6 +340,27 @@ describe("session CLI", () => {
return parsed.context?.selectedHunk?.index === 1 ? parsed : null;
});

const resetSelection = runSessionCli(
["navigate", sessionId, "--file", fixture.afterName, "--hunk", "1", "--json"],
port,
);
expect(resetSelection.proc.exitCode).toBe(0);
expect(resetSelection.stderr).toBe("");

await waitUntil("reset session context", () => {
const context = runSessionCli(["context", sessionId, "--json"], port);
if (context.proc.exitCode !== 0) {
return null;
}

const parsed = JSON.parse(context.stdout) as {
context?: { selectedHunk?: { index: number }; showAgentNotes?: boolean };
};
return parsed.context?.selectedHunk?.index === 0 && parsed.context?.showAgentNotes === false
? parsed
: null;
});

const comment = runSessionCli(
[
"comment",
Expand Down Expand Up @@ -378,8 +399,73 @@ describe("session CLI", () => {
line: 10,
},
});

expect(typeof addedComment.result?.commentId).toBe("string");

await waitUntil("comment registered without focus", () => {
const listedComments = runSessionCli(["comment", "list", sessionId, "--json"], port);
if (listedComments.proc.exitCode !== 0) {
return null;
}

const parsed = JSON.parse(listedComments.stdout) as {
comments?: Array<{ summary?: string }>;
};
return parsed.comments?.some((comment) => comment.summary === "Second hunk note")
? parsed
: null;
});

const unchangedContext = runSessionCli(["context", sessionId, "--json"], port);
expect(unchangedContext.proc.exitCode).toBe(0);
expect(JSON.parse(unchangedContext.stdout)).toMatchObject({
context: {
selectedHunk: {
index: 0,
},
showAgentNotes: false,
},
});

const focusedComment = runSessionCli(
[
"comment",
"add",
sessionId,
"--file",
fixture.afterName,
"--new-line",
"10",
"--summary",
"Second hunk focused note",
"--focus",
"--json",
],
port,
);
expect(focusedComment.proc.exitCode).toBe(0);
expect(focusedComment.stderr).toBe("");
expect(JSON.parse(focusedComment.stdout)).toMatchObject({
result: {
filePath: fixture.afterName,
hunkIndex: 1,
side: "new",
line: 10,
},
});

await waitUntil("focused session context", () => {
const context = runSessionCli(["context", sessionId, "--json"], port);
if (context.proc.exitCode !== 0) {
return null;
}

const parsed = JSON.parse(context.stdout) as {
context?: { selectedHunk?: { index: number }; showAgentNotes?: boolean };
};
return parsed.context?.selectedHunk?.index === 1 && parsed.context?.showAgentNotes === true
? parsed
: null;
});
} finally {
session.kill();
await session.exited;
Expand Down
Loading