Skip to content

Support hunk-targeted live comments#161

Open
benvinegar wants to merge 2 commits intomainfrom
pi/issue-158-hunk-comments
Open

Support hunk-targeted live comments#161
benvinegar wants to merge 2 commits intomainfrom
pi/issue-158-hunk-comments

Conversation

@benvinegar
Copy link
Copy Markdown
Member

Summary

  • allow hunk session comment add to target a hunk directly with --hunk <n>
  • resolve hunk-targeted comments to the first changed line on a stable side inside the live session
  • update the repo skill and docs so agents know they can annotate whole hunks without hand-picking a line number

Testing

  • bun run typecheck
  • bun test

Part of #158.

This PR description was generated by Pi using OpenAI o3

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 3, 2026

Greptile Summary

This PR adds --hunk <n> as a first-class targeting option for hunk session comment add, letting agents annotate an entire diff hunk without having to identify a specific line number. The resolution logic lives in a new resolveCommentTarget helper that either resolves a hunk index directly or falls through to the existing findHunkIndexForLine path, keeping the two flows unified.

Key changes:

  • New resolveCommentTarget in liveComments.ts centralises both hunk-targeted and line-targeted resolution, replacing the inline findHunkIndexForLine call in useHunkSessionBridge.ts.
  • firstCommentTargetForHunk picks a stable anchor line inside the hunk (preferring new-side additions) so every comment still has a concrete side/line even when none was specified.
  • 1-based --hunk <n> from the CLI is converted to 0-based hunkIndex at the server boundary (server.ts), matching the existing convention for navigate commands.
  • CommentToolInput, SessionCommentAddCommandInput, and the session protocol type all have side/line made optional and hunkIndex/hunkNumber added.
  • skills/hunk-review/SKILL.md and README.md updated to document the new flag.
  • Tests cover CLI parsing, the MCP server's 1→0-based conversion, and the resolveCommentTarget helper.

Confidence Score: 5/5

Safe to merge — the only finding is a P2 edge case in the "prefer new" heuristic inside firstCommentTargetForHunk.

All changes are well-scoped: protocol types, CLI parsing, server-side validation, and UI bridge are updated consistently. The 1-based→0-based index conversion is correctly applied and tested at the server boundary. The only concern is that firstCommentTargetForHunk's "prefer new" bias can silently fall back to "old" when pure-deletion chunks appear before pure-addition chunks in hunkContent, but this does not break functionality — it only affects which anchor line is chosen. All remaining findings are P2.

src/core/liveComments.ts — specifically the firstCommentTargetForHunk loop logic

Important Files Changed

Filename Overview
src/core/liveComments.ts Adds resolveCommentTarget and firstCommentTargetForHunk; the latter's "prefer new-side" bias is not fully honored when deletion chunks precede addition chunks in hunkContent.
src/mcp/types.ts Makes side and line optional on CommentToolInput, adds hunkIndex — consistent with protocol changes elsewhere.
src/mcp/server.ts Adds validation gate and 1-based→0-based hunkNumber→hunkIndex conversion for comment-add; mirrors the existing navigate pattern correctly.
src/ui/hooks/useHunkSessionBridge.ts Delegates hunk/line resolution to resolveCommentTarget, eliminating the inline findHunkIndexForLine call — correct and well-factored.
src/core/cli.ts Adds --hunk option parsed via parsePositiveInt and emits hunkNumber; mutex selector check correctly extended to three options.
src/session/protocol.ts Makes side/line optional and adds hunkNumber to the comment-add daemon request shape — consistent with other protocol updates.
test/live-comments.test.ts Adds createLateChangeDiffFile and a resolveCommentTarget test covering a single-line modification; happy path well covered.
test/mcp-server.test.ts New integration test verifies 1-based hunkNumber→0-based hunkIndex conversion through the HTTP session API.
test/session-cli.test.ts Updates e2e test to use --hunk 2 instead of --new-line 10 and adjusts expected resolved line; timeout bumped to accommodate slower CI runs.

Sequence Diagram

sequenceDiagram
    participant Agent
    participant CLI as hunk session CLI
    participant Server as MCP Daemon (server.ts)
    participant Bridge as useHunkSessionBridge
    participant Resolver as resolveCommentTarget

    Agent->>CLI: comment add --hunk 2 --file README.md --summary "..."
    CLI->>CLI: parse --hunk 2 → hunkNumber: 2
    CLI->>Server: POST /session-api {action:"comment-add", hunkNumber:2}
    Server->>Server: validate (hunkNumber OR side+line present)
    Server->>Server: convert hunkNumber 2 → hunkIndex 1
    Server->>Bridge: sendComment({hunkIndex:1, filePath, summary})
    Bridge->>Resolver: resolveCommentTarget(file, {hunkIndex:1})
    Resolver->>Resolver: file.metadata.hunks[1] → firstCommentTargetForHunk(hunk)
    Resolver-->>Bridge: {hunkIndex:1, side:"new", line:13}
    Bridge->>Bridge: buildLiveComment({side:"new", line:13}, ...)
    Bridge-->>Server: AppliedCommentResult {hunkIndex:1, side:"new", line:13}
    Server-->>Agent: 200 {result: {commentId, hunkIndex:1, side:"new", line:13}}
Loading

Reviews (1): Last reviewed commit: "Support hunk-targeted live comments" | Re-trigger Greptile

Comment on lines +43 to +73
export function firstCommentTargetForHunk(hunk: Hunk): Omit<ResolvedCommentTarget, "hunkIndex"> {
let deletionLineNumber = hunk.deletionStart;
let additionLineNumber = hunk.additionStart;

for (const content of hunk.hunkContent) {
if (content.type === "context") {
deletionLineNumber += content.lines;
additionLineNumber += content.lines;
continue;
}

if (content.additions > 0) {
return {
side: "new",
line: additionLineNumber,
};
}

if (content.deletions > 0) {
return {
side: "old",
line: deletionLineNumber,
};
}
}

const fallbackRange = hunkLineRange(hunk);
return hunk.additionLines > 0
? { side: "new", line: fallbackRange.newRange[0] }
: { side: "old", line: fallbackRange.oldRange[0] };
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 "prefer new" bias not fully enforced across separate chunks

The function's stated goal is to prefer the new (addition) side when present, but that preference only applies when the first non-context chunk already contains additions. If hunkContent has a pure-deletion block first followed by a pure-addition block later, the loop returns "old" at the deletion block and never reaches the addition block.

// example hunkContent sequence that triggers the issue:
// { type: "change", deletions: 2, additions: 0 }   ← loop returns "old" here
// { type: "change", deletions: 0, additions: 3 }   ← never checked

The fallback below the loop correctly inspects hunk.additionLines > 0 to prefer "new", but that fallback is only reachable when all chunks have zero additions and zero deletions — essentially impossible for a real diff hunk, so it will never fire in this situation.

A simple fix is to do a two-pass scan: first sweep for the earliest addition to get the "new" preference, then fall back to the earliest deletion, before reaching the hunkLineRange fallback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I changed firstCommentTargetForHunk() to keep scanning after deletion-only chunks, remember the first deletion as a fallback, and still prefer the earliest addition when one appears later in the same hunk. I also added a regression test for that mixed deletion/addition shape.

This comment was generated by Pi using OpenAI o3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant