@shareworker/code-review-mcp 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/dist/git.js ADDED
@@ -0,0 +1,111 @@
1
+ import simpleGit from "simple-git";
2
+ /**
3
+ * Open a simple-git instance for a repo path.
4
+ * Falls back to cwd when repo is empty.
5
+ */
6
+ export function openRepo(repo) {
7
+ return simpleGit(repo || process.cwd());
8
+ }
9
+ /**
10
+ * Get the unified diff text for a ref.
11
+ * `ref` may be a range like "main..feature" or a single ref like "HEAD".
12
+ */
13
+ export async function getDiff(repo, ref) {
14
+ const git = openRepo(repo);
15
+ return git.diff([ref]);
16
+ }
17
+ /**
18
+ * Get the unified diff text for a single file at a ref.
19
+ * Uses `git diff <ref> -- <path>` to scope to one file.
20
+ */
21
+ export async function getDiffForFile(repo, ref, path) {
22
+ const git = openRepo(repo);
23
+ return git.diff([ref, "--", path]);
24
+ }
25
+ /**
26
+ * Get file-level diff metadata (insertions/deletions/binary) for a ref.
27
+ */
28
+ export async function getDiffSummary(repo, ref) {
29
+ const git = openRepo(repo);
30
+ const summary = await git.diffSummary([ref]);
31
+ return summary.files.map((f) => ({
32
+ file: f.file,
33
+ insertions: "insertions" in f ? f.insertions : 0,
34
+ deletions: "deletions" in f ? f.deletions : 0,
35
+ binary: f.binary,
36
+ }));
37
+ }
38
+ /**
39
+ * Get the content of a file at a given ref (e.g. "HEAD", "main").
40
+ * For untracked files, pass ref="WORKTREE" to read from the working directory.
41
+ */
42
+ export async function getFileContent(repo, ref, path) {
43
+ if (ref === "WORKTREE") {
44
+ const fs = await import("node:fs/promises");
45
+ const nodePath = await import("node:path");
46
+ const abs = nodePath.isAbsolute(path)
47
+ ? path
48
+ : nodePath.join(repo || process.cwd(), path);
49
+ return fs.readFile(abs, "utf8");
50
+ }
51
+ const git = openRepo(repo);
52
+ return git.show([`${ref}:${path}`]);
53
+ }
54
+ /**
55
+ * Get porcelain status for untracked file detection.
56
+ * Returns entries with status "untracked" for files git doesn't track.
57
+ */
58
+ export async function getStatus(repo) {
59
+ const git = openRepo(repo);
60
+ const status = await git.status();
61
+ const entries = [];
62
+ for (const f of status.files) {
63
+ // status.files entries have `path` and `index`/`working_dir` status codes.
64
+ // Untracked files have working_dir == "?".
65
+ const isUntracked = f.working_dir === "?" || f.index === "?";
66
+ entries.push({
67
+ path: f.path,
68
+ status: isUntracked ? "untracked" : "modified",
69
+ });
70
+ }
71
+ return entries;
72
+ }
73
+ /**
74
+ * List untracked files (not yet `git add`-ed).
75
+ */
76
+ export async function getUntrackedFiles(repo) {
77
+ const git = openRepo(repo);
78
+ const status = await git.status();
79
+ return status.not_added;
80
+ }
81
+ /**
82
+ * Synthesize a full-file-add diff for an untracked file,
83
+ * equivalent to `git diff --no-index /dev/null <file>`.
84
+ * Reads the file content and wraps it in a unified diff with all lines added.
85
+ */
86
+ export async function synthesizeUntrackedDiff(repo, path) {
87
+ let content;
88
+ try {
89
+ content = await getFileContent(repo, "WORKTREE", path);
90
+ }
91
+ catch {
92
+ // File unreadable: return empty diff.
93
+ return "";
94
+ }
95
+ const lines = content.split("\n");
96
+ // Drop trailing empty line from the final newline.
97
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
98
+ lines.pop();
99
+ }
100
+ const body = lines.map((l) => `+${l}`).join("\n");
101
+ const header = [
102
+ `diff --git a/${path} b/${path}`,
103
+ `new file mode 100644`,
104
+ `--- /dev/null`,
105
+ `+++ b/${path}`,
106
+ `@@ -0,0 +1,${lines.length} @@`,
107
+ ].join("\n");
108
+ const diff = lines.length > 0 ? `${header}\n${body}\n` : `${header}\n`;
109
+ return diff;
110
+ }
111
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { getDiff, getUntrackedFiles, synthesizeUntrackedDiff } from "./git.js";
6
+ import { parseFileDiffs } from "./diff-parser.js";
7
+ import { loadFilterConfig, filterFiles, isBinaryByExtension } from "./filter.js";
8
+ import { bundleFiles } from "./bundler.js";
9
+ import { matchRules } from "./rules.js";
10
+ import { positionComment } from "./position.js";
11
+ import { reflectComment } from "./reflect.js";
12
+ /**
13
+ * Create and start the MCP server with 5 tools.
14
+ */
15
+ async function main() {
16
+ const server = new Server({ name: "code-review-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
17
+ // --- Tool definitions ---
18
+ server.setRequestHandler(ListToolsRequestSchema, () => ({
19
+ tools: [
20
+ {
21
+ name: "get_review_targets",
22
+ description: "Determine which files need review from a git diff. Applies file filtering and returns a diff_ref to pass to downstream tools.",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ mode: {
27
+ type: "string",
28
+ enum: ["workspace", "range", "commit"],
29
+ description: "workspace: staged+unstaged+untracked; range: from..to; commit: commit^..commit",
30
+ },
31
+ from: { type: "string", description: "Required when mode=range" },
32
+ to: { type: "string", description: "Required when mode=range" },
33
+ commit: { type: "string", description: "Required when mode=commit" },
34
+ repo: { type: "string", description: "Repo path, default: cwd" },
35
+ },
36
+ required: ["mode"],
37
+ },
38
+ },
39
+ {
40
+ name: "get_file_bundle",
41
+ description: "Group related files into review bundles (test/source pairs, i18n variants) with a 20000 char cap. Pass the diff_ref from get_review_targets.",
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ files: {
46
+ type: "array",
47
+ items: { type: "string" },
48
+ description: "File paths from get_review_targets",
49
+ },
50
+ diff_ref: { type: "string", description: "Default HEAD; pass the diff_ref from get_review_targets" },
51
+ repo: { type: "string", description: "Repo path, default: cwd" },
52
+ },
53
+ required: ["files"],
54
+ },
55
+ },
56
+ {
57
+ name: "match_rules",
58
+ description: "Return applicable review rules for a file path, merged into a prompt_section for the host LLM.",
59
+ inputSchema: {
60
+ type: "object",
61
+ properties: {
62
+ path: { type: "string", description: "File path to match rules against" },
63
+ repo: { type: "string", description: "Repo path, default: cwd" },
64
+ },
65
+ required: ["path"],
66
+ },
67
+ },
68
+ {
69
+ name: "position_comment",
70
+ description: "Locate a comment to precise line numbers. Text matching primary (hunk new-side → old-side → full file), hunk alignment fallback. Pass diff_ref from get_review_targets.",
71
+ inputSchema: {
72
+ type: "object",
73
+ properties: {
74
+ path: { type: "string" },
75
+ content: { type: "string", description: "Comment text" },
76
+ existing_code: { type: "string", description: "Code snippet the comment references" },
77
+ suggestion_code: { type: "string", description: "Suggested fix code" },
78
+ hint_line: { type: "number", description: "Rough line number from host LLM" },
79
+ diff_ref: { type: "string", description: "Default HEAD; pass from get_review_targets" },
80
+ repo: { type: "string", description: "Repo path, default: cwd" },
81
+ },
82
+ required: ["path", "content"],
83
+ },
84
+ },
85
+ {
86
+ name: "reflect_comment",
87
+ description: "Deterministic validation of a positioned comment. Returns keep or drop. Does not call LLM. Three checks: line_in_hunk, existing_code_found, existing_code_in_diff.",
88
+ inputSchema: {
89
+ type: "object",
90
+ properties: {
91
+ path: { type: "string" },
92
+ content: { type: "string" },
93
+ start_line: { type: "number", description: "From position_comment" },
94
+ end_line: { type: "number", description: "From position_comment" },
95
+ existing_code: { type: "string", description: "Code snippet the comment references" },
96
+ diff_ref: { type: "string", description: "Default HEAD; pass from get_review_targets" },
97
+ repo: { type: "string", description: "Repo path, default: cwd" },
98
+ },
99
+ required: ["path", "content", "start_line", "end_line"],
100
+ },
101
+ },
102
+ ],
103
+ }));
104
+ // --- Tool handlers ---
105
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
106
+ const { name, arguments: args } = request.params;
107
+ const repo = args?.repo || process.cwd();
108
+ try {
109
+ switch (name) {
110
+ case "get_review_targets":
111
+ return await handleGetReviewTargets(args, repo);
112
+ case "get_file_bundle":
113
+ return await handleGetFileBundle(args, repo);
114
+ case "match_rules":
115
+ return await handleMatchRules(args, repo);
116
+ case "position_comment":
117
+ return await handlePositionComment(args, repo);
118
+ case "reflect_comment":
119
+ return await handleReflectComment(args, repo);
120
+ default:
121
+ return {
122
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
123
+ isError: true,
124
+ };
125
+ }
126
+ }
127
+ catch (err) {
128
+ return {
129
+ content: [{ type: "text", text: `Error: ${err?.message ?? err}` }],
130
+ isError: true,
131
+ };
132
+ }
133
+ });
134
+ const transport = new StdioServerTransport();
135
+ await server.connect(transport);
136
+ }
137
+ // --- Handler implementations ---
138
+ async function handleGetReviewTargets(args, repo) {
139
+ const mode = args?.mode;
140
+ let diffRef;
141
+ let diffText;
142
+ if (mode === "workspace") {
143
+ diffRef = "HEAD";
144
+ diffText = await getDiff(repo, "HEAD");
145
+ // Add untracked files as synthesized full-file-add diffs.
146
+ const untracked = await getUntrackedFiles(repo);
147
+ for (const file of untracked) {
148
+ if (isBinaryByExtension(file))
149
+ continue;
150
+ const synth = await synthesizeUntrackedDiff(repo, file);
151
+ if (synth)
152
+ diffText += "\n" + synth;
153
+ }
154
+ }
155
+ else if (mode === "range") {
156
+ const from = args?.from;
157
+ const to = args?.to;
158
+ if (!from || !to)
159
+ throw new Error("mode=range requires 'from' and 'to'");
160
+ diffRef = `${from}..${to}`;
161
+ diffText = await getDiff(repo, diffRef);
162
+ }
163
+ else if (mode === "commit") {
164
+ const commit = args?.commit;
165
+ if (!commit)
166
+ throw new Error("mode=commit requires 'commit'");
167
+ diffRef = `${commit}^..${commit}`;
168
+ diffText = await getDiff(repo, diffRef);
169
+ }
170
+ else {
171
+ throw new Error(`Invalid mode: ${mode}. Use workspace, range, or commit.`);
172
+ }
173
+ // Parse diffs to get file list.
174
+ const fileDiffs = parseFileDiffs(diffText);
175
+ const allPaths = fileDiffs
176
+ .map((d) => d.newPath || d.oldPath)
177
+ .filter((p) => p && !p.includes("/dev/null"));
178
+ // Apply filtering.
179
+ const filterConfig = await loadFilterConfig(repo);
180
+ const { kept, filtered } = filterFiles(allPaths, filterConfig);
181
+ // Build result with per-file diff.
182
+ const files = kept.map((path) => {
183
+ const fd = fileDiffs.find((d) => (d.newPath || d.oldPath) === path);
184
+ const status = fd?.isNew ? "added" : fd?.isDeleted ? "deleted" : fd?.isRenamed ? "renamed" : "modified";
185
+ return {
186
+ path,
187
+ diff: fd?.diff ?? "",
188
+ additions: fd?.insertions ?? 0,
189
+ deletions: fd?.deletions ?? 0,
190
+ status,
191
+ };
192
+ });
193
+ return {
194
+ content: [
195
+ {
196
+ type: "text",
197
+ text: JSON.stringify({
198
+ diff_ref: diffRef,
199
+ files,
200
+ total_files: files.length,
201
+ filtered_out: filtered,
202
+ }),
203
+ },
204
+ ],
205
+ };
206
+ }
207
+ async function handleGetFileBundle(args, repo) {
208
+ const files = args?.files ?? [];
209
+ const diffRef = args?.diff_ref ?? "HEAD";
210
+ const bundles = await bundleFiles(repo, files, diffRef);
211
+ return {
212
+ content: [
213
+ {
214
+ type: "text",
215
+ text: JSON.stringify({
216
+ bundles: bundles.map((b) => ({
217
+ id: b.id,
218
+ files: b.files,
219
+ total_chars: b.totalChars,
220
+ bundle_reason: b.bundleReason,
221
+ })),
222
+ total_bundles: bundles.length,
223
+ }),
224
+ },
225
+ ],
226
+ };
227
+ }
228
+ async function handleMatchRules(args, repo) {
229
+ const path = args?.path;
230
+ const result = await matchRules(repo, path);
231
+ return {
232
+ content: [
233
+ {
234
+ type: "text",
235
+ text: JSON.stringify({
236
+ path: result.path,
237
+ matched_rules: result.matchedRules,
238
+ prompt_section: result.promptSection,
239
+ used_default: result.usedDefault,
240
+ }),
241
+ },
242
+ ],
243
+ };
244
+ }
245
+ async function handlePositionComment(args, repo) {
246
+ const result = await positionComment(repo, {
247
+ path: args?.path,
248
+ content: args?.content,
249
+ existingCode: args?.existing_code,
250
+ suggestionCode: args?.suggestion_code,
251
+ hintLine: args?.hint_line,
252
+ diffRef: args?.diff_ref,
253
+ repo,
254
+ });
255
+ return {
256
+ content: [
257
+ {
258
+ type: "text",
259
+ text: JSON.stringify({
260
+ path: result.path,
261
+ start_line: result.startLine,
262
+ end_line: result.endLine,
263
+ located_by: result.locatedBy,
264
+ }),
265
+ },
266
+ ],
267
+ };
268
+ }
269
+ async function handleReflectComment(args, repo) {
270
+ const result = await reflectComment(repo, {
271
+ path: args?.path,
272
+ content: args?.content,
273
+ startLine: args?.start_line,
274
+ endLine: args?.end_line,
275
+ existingCode: args?.existing_code,
276
+ diffRef: args?.diff_ref,
277
+ repo,
278
+ });
279
+ return {
280
+ content: [
281
+ {
282
+ type: "text",
283
+ text: JSON.stringify({
284
+ verdict: result.verdict,
285
+ reason: result.reason,
286
+ checks: result.checks,
287
+ }),
288
+ },
289
+ ],
290
+ };
291
+ }
292
+ main().catch((err) => {
293
+ console.error("Fatal error starting server:", err);
294
+ process.exit(1);
295
+ });
296
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { PositionInput, PositionResult } from "./types.js";
2
+ /**
3
+ * Locate a comment to precise line numbers in the target file.
4
+ *
5
+ * Strategy (modeled after open-code-review's resolver.go):
6
+ * 1. Text matching (primary): extract code lines from existing_code/suggestion_code,
7
+ * normalize, search for consecutive matches. Try hunk new-side first, then
8
+ * old-side, then full file content.
9
+ * 2. Hunk alignment (fallback): if no code snippet but hint_line is provided,
10
+ * use hunk line-number mapping to align.
11
+ * 3. Fallback: return 0, 0, "failed".
12
+ */
13
+ export declare function positionComment(repo: string, input: PositionInput): Promise<PositionResult>;
14
+ //# sourceMappingURL=position.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position.d.ts","sourceRoot":"","sources":["../src/position.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAQ,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC,cAAc,CAAC,CAqBzB"}
@@ -0,0 +1,179 @@
1
+ import { getDiffForFile, getFileContent } from "./git.js";
2
+ import { parseFileDiffs, parseHunks, extractSideLines, matchConsecutive, splitAndNormalize, normalizeLine } from "./diff-parser.js";
3
+ /**
4
+ * Locate a comment to precise line numbers in the target file.
5
+ *
6
+ * Strategy (modeled after open-code-review's resolver.go):
7
+ * 1. Text matching (primary): extract code lines from existing_code/suggestion_code,
8
+ * normalize, search for consecutive matches. Try hunk new-side first, then
9
+ * old-side, then full file content.
10
+ * 2. Hunk alignment (fallback): if no code snippet but hint_line is provided,
11
+ * use hunk line-number mapping to align.
12
+ * 3. Fallback: return 0, 0, "failed".
13
+ */
14
+ export async function positionComment(repo, input) {
15
+ const path = input.path.replace(/\\/g, "/");
16
+ const diffRef = input.diffRef ?? "HEAD";
17
+ // Gather code lines from existing_code or suggestion_code.
18
+ const codeSource = input.existingCode || input.suggestionCode || "";
19
+ const targetLines = splitAndNormalize(codeSource);
20
+ // If we have code lines, try text matching.
21
+ if (targetLines.length > 0) {
22
+ const result = await tryTextMatch(repo, path, diffRef, targetLines);
23
+ if (result)
24
+ return result;
25
+ }
26
+ // Fallback: hunk alignment with hint_line.
27
+ if (input.hintLine && input.hintLine > 0) {
28
+ const result = await tryHunkAlign(repo, path, diffRef, input.hintLine);
29
+ if (result)
30
+ return result;
31
+ }
32
+ return { path, startLine: 0, endLine: 0, locatedBy: "failed" };
33
+ }
34
+ /**
35
+ * Try text matching: hunk new-side → old-side → full file content.
36
+ */
37
+ async function tryTextMatch(repo, path, diffRef, targetLines) {
38
+ let diffText;
39
+ try {
40
+ diffText = await getDiffForFile(repo, diffRef, path);
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ if (!diffText)
46
+ return null;
47
+ const fileDiffs = parseFileDiffs(diffText);
48
+ const fileDiff = fileDiffs[0];
49
+ if (!fileDiff)
50
+ return null;
51
+ // Try hunk new-side first (context + added → new-file line numbers).
52
+ for (const hunk of fileDiff.hunks) {
53
+ const newSide = extractSideLines(hunk, true);
54
+ const match = matchConsecutive(newSide, targetLines);
55
+ if (match) {
56
+ return { path, startLine: match.start, endLine: match.end, locatedBy: "text_match" };
57
+ }
58
+ }
59
+ // Try hunk old-side (context + deleted → old-file line numbers).
60
+ for (const hunk of fileDiff.hunks) {
61
+ const oldSide = extractSideLines(hunk, false);
62
+ const match = matchConsecutive(oldSide, targetLines);
63
+ if (match) {
64
+ return { path, startLine: match.start, endLine: match.end, locatedBy: "text_match" };
65
+ }
66
+ }
67
+ // Fallback: scan full file content.
68
+ const fileContent = await tryGetFileContent(repo, diffRef, path);
69
+ if (fileContent) {
70
+ const match = matchInFileContent(fileContent, targetLines);
71
+ if (match) {
72
+ return { path, startLine: match.start, endLine: match.end, locatedBy: "text_match" };
73
+ }
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * Try to get file content at a ref. Returns null on failure or empty result.
79
+ * Note: `git show <range>:<path>` returns empty string for range refs, so
80
+ * we treat empty as "not found" and fall back to worktree.
81
+ */
82
+ async function tryGetFileContent(repo, ref, path) {
83
+ try {
84
+ const content = await getFileContent(repo, ref, path);
85
+ if (content && content.length > 0)
86
+ return content;
87
+ }
88
+ catch {
89
+ // fall through to worktree
90
+ }
91
+ try {
92
+ const content = await getFileContent(repo, "WORKTREE", path);
93
+ if (content && content.length > 0)
94
+ return content;
95
+ }
96
+ catch {
97
+ // fall through
98
+ }
99
+ return null;
100
+ }
101
+ /**
102
+ * Scan file content line-by-line for consecutive matches of normalized target lines.
103
+ * Blank lines are skipped so they don't break the sliding-window match.
104
+ * Mirrors open-code-review's resolveFromFileContent.
105
+ */
106
+ function matchInFileContent(fileContent, targetLines) {
107
+ const fileLines = fileContent.split("\n");
108
+ const normalizedLines = [];
109
+ const lineNums = [];
110
+ for (let i = 0; i < fileLines.length; i++) {
111
+ const n = normalizeLine(fileLines[i]);
112
+ if (n === "")
113
+ continue;
114
+ normalizedLines.push(n);
115
+ lineNums.push(i + 1);
116
+ }
117
+ if (normalizedLines.length < targetLines.length)
118
+ return null;
119
+ for (let i = 0; i <= normalizedLines.length - targetLines.length; i++) {
120
+ let matched = true;
121
+ for (let j = 0; j < targetLines.length; j++) {
122
+ if (normalizedLines[i + j] !== targetLines[j]) {
123
+ matched = false;
124
+ break;
125
+ }
126
+ }
127
+ if (matched) {
128
+ return { start: lineNums[i], end: lineNums[i + targetLines.length - 1] };
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ /**
134
+ * Try hunk alignment: map hint_line to the closest hunk's line range.
135
+ * If hint_line falls within a hunk's new-side range, return it.
136
+ */
137
+ async function tryHunkAlign(repo, path, diffRef, hintLine) {
138
+ let diffText;
139
+ try {
140
+ diffText = await getDiffForFile(repo, diffRef, path);
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ if (!diffText)
146
+ return null;
147
+ const hunks = parseHunks(diffText);
148
+ if (hunks.length === 0)
149
+ return null;
150
+ // Find the hunk whose new-side range contains hint_line.
151
+ for (const hunk of hunks) {
152
+ const newEnd = hunk.newStart + hunk.newCount - 1;
153
+ if (hintLine >= hunk.newStart && hintLine <= newEnd) {
154
+ return { path, startLine: hintLine, endLine: hintLine, locatedBy: "hunk_align" };
155
+ }
156
+ }
157
+ // If hint_line is before the first hunk, clamp to the first hunk's new start.
158
+ if (hintLine < hunks[0].newStart) {
159
+ return { path, startLine: hunks[0].newStart, endLine: hunks[0].newStart, locatedBy: "hunk_align" };
160
+ }
161
+ // If after the last hunk, clamp to the last hunk's new end.
162
+ const last = hunks[hunks.length - 1];
163
+ const lastEnd = last.newStart + last.newCount - 1;
164
+ if (hintLine > lastEnd) {
165
+ return { path, startLine: lastEnd, endLine: lastEnd, locatedBy: "hunk_align" };
166
+ }
167
+ // Between hunks — pick the closest.
168
+ let closest = hunks[0];
169
+ let minDist = Math.abs(hintLine - closest.newStart);
170
+ for (const hunk of hunks) {
171
+ const dist = Math.abs(hintLine - hunk.newStart);
172
+ if (dist < minDist) {
173
+ minDist = dist;
174
+ closest = hunk;
175
+ }
176
+ }
177
+ return { path, startLine: closest.newStart, endLine: closest.newStart, locatedBy: "hunk_align" };
178
+ }
179
+ //# sourceMappingURL=position.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position.js","sourceRoot":"","sources":["../src/position.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { ReflectInput, ReflectResult } from "./types.js";
2
+ /**
3
+ * Deterministic validation of a positioned comment.
4
+ * Returns keep or drop. Does not call LLM.
5
+ *
6
+ * Three checks:
7
+ * 1. line_in_hunk: Are start_line/end_line within the diff hunk's changed line range?
8
+ * 2. existing_code_found: Does the existing_code snippet actually exist in the file?
9
+ * (not applicable when existing_code is empty/undefined — passes by default)
10
+ * 3. existing_code_in_diff: Is at least one line of existing_code within the diff's changed lines?
11
+ * (not applicable when existing_code is empty/undefined — passes by default)
12
+ *
13
+ * Verdict: any check fails → drop; all pass → keep.
14
+ * Semantic-level reflection is the host LLM's responsibility.
15
+ */
16
+ export declare function reflectComment(repo: string, input: ReflectInput): Promise<ReflectResult>;
17
+ //# sourceMappingURL=reflect.d.ts.map