diffninja 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/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/calltree.d.ts +47 -0
- package/dist/calltree.js +296 -0
- package/dist/cli.d.ts +57 -0
- package/dist/cli.js +340 -0
- package/dist/diff.d.ts +7 -0
- package/dist/diff.js +114 -0
- package/dist/extract.d.ts +26 -0
- package/dist/extract.js +152 -0
- package/dist/git.d.ts +40 -0
- package/dist/git.js +288 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/infer.d.ts +21 -0
- package/dist/infer.js +189 -0
- package/dist/languages/bash.d.ts +2 -0
- package/dist/languages/bash.js +208 -0
- package/dist/languages/c.d.ts +2 -0
- package/dist/languages/c.js +218 -0
- package/dist/languages/call-syntax.d.ts +125 -0
- package/dist/languages/call-syntax.js +997 -0
- package/dist/languages/cpp.d.ts +2 -0
- package/dist/languages/cpp.js +321 -0
- package/dist/languages/csharp.d.ts +2 -0
- package/dist/languages/csharp.js +324 -0
- package/dist/languages/elixir.d.ts +2 -0
- package/dist/languages/elixir.js +331 -0
- package/dist/languages/go.d.ts +2 -0
- package/dist/languages/go.js +299 -0
- package/dist/languages/grammars.d.ts +50 -0
- package/dist/languages/grammars.js +351 -0
- package/dist/languages/haskell.d.ts +2 -0
- package/dist/languages/haskell.js +250 -0
- package/dist/languages/java.d.ts +2 -0
- package/dist/languages/java.js +351 -0
- package/dist/languages/javascript.d.ts +4 -0
- package/dist/languages/javascript.js +648 -0
- package/dist/languages/kotlin.d.ts +2 -0
- package/dist/languages/kotlin.js +368 -0
- package/dist/languages/lua.d.ts +2 -0
- package/dist/languages/lua.js +212 -0
- package/dist/languages/ocaml.d.ts +2 -0
- package/dist/languages/ocaml.js +291 -0
- package/dist/languages/perl.d.ts +2 -0
- package/dist/languages/perl.js +418 -0
- package/dist/languages/php.d.ts +2 -0
- package/dist/languages/php.js +397 -0
- package/dist/languages/python.d.ts +2 -0
- package/dist/languages/python.js +376 -0
- package/dist/languages/registry.d.ts +7 -0
- package/dist/languages/registry.js +69 -0
- package/dist/languages/ruby.d.ts +2 -0
- package/dist/languages/ruby.js +391 -0
- package/dist/languages/rust.d.ts +2 -0
- package/dist/languages/rust.js +261 -0
- package/dist/languages/scala.d.ts +2 -0
- package/dist/languages/scala.js +307 -0
- package/dist/languages/solidity.d.ts +2 -0
- package/dist/languages/solidity.js +240 -0
- package/dist/languages/swift.d.ts +2 -0
- package/dist/languages/swift.js +268 -0
- package/dist/languages/types.d.ts +36 -0
- package/dist/languages/types.js +74 -0
- package/dist/languages/typescript-contracts.d.ts +57 -0
- package/dist/languages/typescript-contracts.js +528 -0
- package/dist/languages/typescript-dispatch.d.ts +68 -0
- package/dist/languages/typescript-dispatch.js +710 -0
- package/dist/languages/typescript.d.ts +4 -0
- package/dist/languages/typescript.js +722 -0
- package/dist/languages/zig.d.ts +2 -0
- package/dist/languages/zig.js +243 -0
- package/dist/loc.d.ts +17 -0
- package/dist/loc.js +34 -0
- package/dist/reach.d.ts +17 -0
- package/dist/reach.js +65 -0
- package/dist/render.d.ts +18 -0
- package/dist/render.js +83 -0
- package/dist/review/brand.d.ts +8 -0
- package/dist/review/brand.js +25 -0
- package/dist/review/call-context.d.ts +27 -0
- package/dist/review/call-context.js +446 -0
- package/dist/review/call-flow-html.d.ts +32 -0
- package/dist/review/call-flow-html.js +1870 -0
- package/dist/review/call-flow-nav.d.ts +151 -0
- package/dist/review/call-flow-nav.js +317 -0
- package/dist/review/call-flow.d.ts +47 -0
- package/dist/review/call-flow.js +229 -0
- package/dist/review/change-facts.d.ts +69 -0
- package/dist/review/change-facts.js +729 -0
- package/dist/review/cli.d.ts +2 -0
- package/dist/review/cli.js +50 -0
- package/dist/review/connected-analysis.d.ts +100 -0
- package/dist/review/connected-analysis.js +163 -0
- package/dist/review/connected-html.d.ts +17 -0
- package/dist/review/connected-html.js +2853 -0
- package/dist/review/connected.d.ts +23 -0
- package/dist/review/connected.js +141 -0
- package/dist/review/escape-html.d.ts +2 -0
- package/dist/review/escape-html.js +9 -0
- package/dist/review/evidence-html.d.ts +21 -0
- package/dist/review/evidence-html.js +521 -0
- package/dist/review/evidence-syntax.d.ts +132 -0
- package/dist/review/evidence-syntax.js +478 -0
- package/dist/review/evidence-types.d.ts +62 -0
- package/dist/review/evidence-types.js +1 -0
- package/dist/review/evidence.d.ts +31 -0
- package/dist/review/evidence.js +1603 -0
- package/dist/review/file-role.d.ts +9 -0
- package/dist/review/file-role.js +29 -0
- package/dist/review/github.d.ts +204 -0
- package/dist/review/github.js +1245 -0
- package/dist/review/history.d.ts +101 -0
- package/dist/review/history.js +412 -0
- package/dist/review/html.d.ts +34 -0
- package/dist/review/html.js +1104 -0
- package/dist/review/input.d.ts +10 -0
- package/dist/review/input.js +113 -0
- package/dist/review/intent.d.ts +4 -0
- package/dist/review/intent.js +75 -0
- package/dist/review/mcp-cli.d.ts +2 -0
- package/dist/review/mcp-cli.js +25 -0
- package/dist/review/mcp.d.ts +12 -0
- package/dist/review/mcp.js +414 -0
- package/dist/review/module-resolution.d.ts +2 -0
- package/dist/review/module-resolution.js +86 -0
- package/dist/review/palette.d.ts +7 -0
- package/dist/review/palette.js +104 -0
- package/dist/review/pipeline.d.ts +77 -0
- package/dist/review/pipeline.js +227 -0
- package/dist/review/pr-input.d.ts +19 -0
- package/dist/review/pr-input.js +130 -0
- package/dist/review/questions.d.ts +201 -0
- package/dist/review/questions.js +174 -0
- package/dist/review/reference-check.d.ts +7 -0
- package/dist/review/reference-check.js +733 -0
- package/dist/review/report-pages.d.ts +109 -0
- package/dist/review/report-pages.js +328 -0
- package/dist/review/service.d.ts +23 -0
- package/dist/review/service.js +198 -0
- package/dist/review/setup.d.ts +112 -0
- package/dist/review/setup.js +549 -0
- package/dist/review/source.d.ts +26 -0
- package/dist/review/source.js +276 -0
- package/dist/review/toml.d.ts +38 -0
- package/dist/review/toml.js +565 -0
- package/dist/review/types.d.ts +179 -0
- package/dist/review/types.js +1 -0
- package/dist/run.d.ts +49 -0
- package/dist/run.js +311 -0
- package/dist/types.d.ts +366 -0
- package/dist/types.js +83 -0
- package/package.json +88 -0
- package/scripts/ensure-native-grammar.mjs +188 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReviewUnit } from "./types.js";
|
|
2
|
+
/** Reject incomplete patches rather than silently omitting review work. */
|
|
3
|
+
export declare function parseDiff(text: string): ReviewUnit[];
|
|
4
|
+
export declare function resolveCommit(cwd: string, ref: string): string;
|
|
5
|
+
export interface GitDiffInput {
|
|
6
|
+
diff: string;
|
|
7
|
+
from: string;
|
|
8
|
+
to: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function gitDiff(cwd: string, from: string, to: string): GitDiffInput;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
function pathName(raw) {
|
|
3
|
+
const value = raw.split("\t")[0];
|
|
4
|
+
if (value.startsWith('"')) {
|
|
5
|
+
// Keep Git's quoted path verbatim rather than guessing at octal escapes.
|
|
6
|
+
// A quoted path (or one containing " b/") therefore differs from the
|
|
7
|
+
// repo-relative path the engine reports, which downstream file matching and
|
|
8
|
+
// definition reads compare against.
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
return value.replace(/^[ab]\//, "");
|
|
12
|
+
}
|
|
13
|
+
/** Reject incomplete patches rather than silently omitting review work. */
|
|
14
|
+
export function parseDiff(text) {
|
|
15
|
+
if (!text.trim())
|
|
16
|
+
return [];
|
|
17
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
18
|
+
const units = [];
|
|
19
|
+
let file = "", metadata = [], found = false;
|
|
20
|
+
const add = (unit) => units.push({ ...unit, id: `hunk-${units.length + 1}` });
|
|
21
|
+
const flushMetadata = () => {
|
|
22
|
+
const important = metadata.filter(line => /^(old mode|new mode|rename |copy |similarity |dissimilarity |Binary files|GIT binary patch|Submodule|new file mode 120000|new file mode 160000)/.test(line));
|
|
23
|
+
if (important.length || (file && !found)) {
|
|
24
|
+
add({ file, header: "File metadata", diff: metadata.join("\n"), added: 0, removed: 0, oldStart: 0, newStart: 0,
|
|
25
|
+
special: "Binary, rename, mode, or metadata-only change needs manual review." });
|
|
26
|
+
}
|
|
27
|
+
metadata = [];
|
|
28
|
+
found = false;
|
|
29
|
+
};
|
|
30
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31
|
+
const line = lines[i];
|
|
32
|
+
if (line.startsWith("diff --git ")) {
|
|
33
|
+
flushMetadata();
|
|
34
|
+
file = / b\/(.*)$/.exec(line)?.[1] ?? line.slice(11);
|
|
35
|
+
metadata.push(line);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (line.startsWith("diff --cc ") || line.startsWith("diff --combined ") || line.startsWith("@@@")) {
|
|
39
|
+
throw new Error("Combined merge diffs are unsupported. Supply a two-commit unified diff.");
|
|
40
|
+
}
|
|
41
|
+
if (line.startsWith("--- ") && lines[i + 1]?.startsWith("+++ ")) {
|
|
42
|
+
if (found)
|
|
43
|
+
flushMetadata();
|
|
44
|
+
const oldPath = pathName(line.slice(4));
|
|
45
|
+
const newPath = pathName(lines[++i].slice(4));
|
|
46
|
+
file = newPath === "/dev/null" ? oldPath : newPath;
|
|
47
|
+
metadata.push(line, lines[i]);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (line.startsWith("@@")) {
|
|
51
|
+
if (!file)
|
|
52
|
+
throw new Error("Hunk has no file header.");
|
|
53
|
+
const match = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line);
|
|
54
|
+
if (!match)
|
|
55
|
+
throw new Error(`Malformed hunk header: ${line}`);
|
|
56
|
+
let oldLeft = Number(match[2] ?? 1), newLeft = Number(match[4] ?? 1);
|
|
57
|
+
const body = [line];
|
|
58
|
+
let added = 0, removed = 0;
|
|
59
|
+
while (oldLeft > 0 || newLeft > 0) {
|
|
60
|
+
const next = lines[++i];
|
|
61
|
+
if (next === undefined)
|
|
62
|
+
throw new Error(`Truncated hunk in ${file}`);
|
|
63
|
+
body.push(next);
|
|
64
|
+
if (next === "\")
|
|
65
|
+
continue;
|
|
66
|
+
if (next.startsWith("+")) {
|
|
67
|
+
newLeft--;
|
|
68
|
+
added++;
|
|
69
|
+
}
|
|
70
|
+
else if (next.startsWith("-")) {
|
|
71
|
+
oldLeft--;
|
|
72
|
+
removed++;
|
|
73
|
+
}
|
|
74
|
+
else if (next.startsWith(" ")) {
|
|
75
|
+
oldLeft--;
|
|
76
|
+
newLeft--;
|
|
77
|
+
}
|
|
78
|
+
else
|
|
79
|
+
throw new Error(`Invalid or truncated hunk in ${file}`);
|
|
80
|
+
if (oldLeft < 0 || newLeft < 0)
|
|
81
|
+
throw new Error(`Hunk line counts do not match in ${file}`);
|
|
82
|
+
}
|
|
83
|
+
if (lines[i + 1] === "\")
|
|
84
|
+
body.push(lines[++i]);
|
|
85
|
+
const unit = { file, header: line, diff: body.join("\n"), added, removed,
|
|
86
|
+
oldStart: Number(match[1]), newStart: Number(match[3]) };
|
|
87
|
+
if (/^(new file mode|index).*\b(120000|160000)\b/m.test(metadata.join("\n"))) {
|
|
88
|
+
unit.special = "Symbolic link or submodule change needs manual review.";
|
|
89
|
+
}
|
|
90
|
+
add(unit);
|
|
91
|
+
found = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (/^[ +-]/.test(line) && found && line.trim())
|
|
95
|
+
throw new Error(`Unexpected content outside hunk in ${file}`);
|
|
96
|
+
if (file)
|
|
97
|
+
metadata.push(line);
|
|
98
|
+
else if (line.trim())
|
|
99
|
+
throw new Error("Expected a unified diff with file headers.");
|
|
100
|
+
}
|
|
101
|
+
flushMetadata();
|
|
102
|
+
if (!units.length)
|
|
103
|
+
throw new Error("No reviewable changes found in input.");
|
|
104
|
+
return units;
|
|
105
|
+
}
|
|
106
|
+
export function resolveCommit(cwd, ref) {
|
|
107
|
+
return execFileSync("git", ["--no-replace-objects", "rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`], { cwd, encoding: "utf8" }).trim();
|
|
108
|
+
}
|
|
109
|
+
export function gitDiff(cwd, from, to) {
|
|
110
|
+
const base = resolveCommit(cwd, from), head = resolveCommit(cwd, to);
|
|
111
|
+
const diff = execFileSync("git", ["--no-replace-objects", "diff", "--no-ext-diff", "--no-textconv", "--no-color", "--unified=5", base, head, "--"], { cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
112
|
+
return { diff, from: base, to: head };
|
|
113
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IntentCrossCheck, PullRequestIntent, ReviewAgendaEntry, AutomaticFinding } from "./evidence-types.js";
|
|
2
|
+
import type { ReviewUnit } from "./types.js";
|
|
3
|
+
/** Links are discovery aids, never a semantic entailment or a passing test. */
|
|
4
|
+
export declare function crossCheckIntent(pr: PullRequestIntent | undefined, units: readonly ReviewUnit[], agenda: readonly ReviewAgendaEntry[], findings: readonly AutomaticFinding[]): IntentCrossCheck;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const STOP_WORDS = Object.fromEntries("a about after all also an and any are as at be because been before being both but by can code could did do does during each either for from had has have if in into is it may might more most must no nor not of on only or other our over pr same should so some such than that the their them then there these they this those through to too under until up use using very was we were what when where which while who why will with would you your added add implementing implement introduced introduce updated update functionality capabilities comprehensive test tests coverage".split(" ").map(word => [word, true]));
|
|
2
|
+
function terms(text) {
|
|
3
|
+
return new Set(text.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().match(/[a-z][a-z0-9]+/g)?.filter(word => !Object.hasOwn(STOP_WORDS, word)).map(word => word.replace(/(?:ing|ies|s)$/u, "")).filter(word => word.length > 2) ?? []);
|
|
4
|
+
}
|
|
5
|
+
/** Keep author claims separate from generated release notes; never execute PR text. */
|
|
6
|
+
function statements(pr) {
|
|
7
|
+
const claims = [];
|
|
8
|
+
if (pr.title.trim())
|
|
9
|
+
claims.push({ text: pr.title.trim(), origin: "title" });
|
|
10
|
+
let generatedComment = false;
|
|
11
|
+
let generatedHeadingDepth;
|
|
12
|
+
let comment = false;
|
|
13
|
+
let relevant = true;
|
|
14
|
+
for (const raw of pr.body.split(/\r?\n/u)) {
|
|
15
|
+
if (/<!--.*(?:auto-generated|release notes)/iu.test(raw))
|
|
16
|
+
generatedComment = true;
|
|
17
|
+
if (/<!--.*end of auto-generated/iu.test(raw)) {
|
|
18
|
+
generatedComment = false;
|
|
19
|
+
generatedHeadingDepth = undefined;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const heading = /^(#{1,6})\s/u.exec(raw);
|
|
23
|
+
if (heading) {
|
|
24
|
+
const depth = heading[1].length;
|
|
25
|
+
if (generatedHeadingDepth !== undefined && depth <= generatedHeadingDepth)
|
|
26
|
+
generatedHeadingDepth = undefined;
|
|
27
|
+
if (!generatedComment && /(?:ai[- ]generated|auto[- ]generated|summary by .*(?:bot|coderabbit|copilot))/iu.test(raw))
|
|
28
|
+
generatedHeadingDepth = depth;
|
|
29
|
+
relevant = generatedComment || generatedHeadingDepth !== undefined || !/(?:branch.*naming|pull request.*naming|what type of pr|what gif)/iu.test(raw);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (raw.includes("<!--"))
|
|
33
|
+
comment = true;
|
|
34
|
+
if (comment) {
|
|
35
|
+
if (raw.includes("-->"))
|
|
36
|
+
comment = false;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const text = raw.trim().replace(/^[-*]\s+(?:\[[ xX]\]\s*)?/u, "");
|
|
40
|
+
if (!relevant || !text || /^<|^!\[|^\*\*[^*]+\*\*$/u.test(text))
|
|
41
|
+
continue;
|
|
42
|
+
claims.push({ text, origin: generatedComment || generatedHeadingDepth !== undefined ? "generated-summary" : "author" });
|
|
43
|
+
}
|
|
44
|
+
return claims;
|
|
45
|
+
}
|
|
46
|
+
/** Links are discovery aids, never a semantic entailment or a passing test. */
|
|
47
|
+
export function crossCheckIntent(pr, units, agenda, findings) {
|
|
48
|
+
if (!pr || (!pr.title.trim() && !pr.body.trim()))
|
|
49
|
+
return {
|
|
50
|
+
verdict: "not-established",
|
|
51
|
+
summary: "Expected outcome not established: no PR title or description was supplied. Review the changed behavior and obtain explicit requirements before deciding whether this PR achieves its goal.",
|
|
52
|
+
claims: [],
|
|
53
|
+
obligations: ["Provide the expected behavior, including failure and boundary cases. No intended outcome was inferred from the diff."],
|
|
54
|
+
};
|
|
55
|
+
const indexed = units.map(unit => ({ unit, terms: terms(unit.file + "\n" + unit.diff.split("\n").filter(line => line.startsWith("+")).join("\n")) }));
|
|
56
|
+
const claims = statements(pr).map(claim => {
|
|
57
|
+
const wanted = [...terms(claim.text)];
|
|
58
|
+
const matches = indexed.map(({ unit, terms: actual }) => ({ unit, count: wanted.reduce((count, term) => count + (actual.has(term) ? 1 : 0), 0) }))
|
|
59
|
+
.filter(({ count }) => count >= Math.min(2, wanted.length) && wanted.length > 0)
|
|
60
|
+
.sort((a, b) => b.count - a.count || a.unit.file.localeCompare(b.unit.file) || a.unit.newStart - b.unit.newStart);
|
|
61
|
+
return { ...claim, status: matches.length ? "evidence-linked" : "not-established", unitIds: matches.slice(0, 8).map(({ unit }) => unit.id),
|
|
62
|
+
explanation: matches.length
|
|
63
|
+
? "Changed code shares the named concepts below. These are navigation matches, not proof that the requirement is implemented correctly; inspect behavior and executable acceptance evidence."
|
|
64
|
+
: "No direct changed-code match was found. This is missing evidence, not proof that the claim is false." };
|
|
65
|
+
});
|
|
66
|
+
const obligations = [
|
|
67
|
+
"Confirm the intended success, partial-failure, and boundary outcomes. The PR text and static code do not establish end-to-end fulfillment.",
|
|
68
|
+
...agenda.slice(0, 5).map(entry => entry.title),
|
|
69
|
+
];
|
|
70
|
+
if (claims.some(claim => claim.origin === "generated-summary"))
|
|
71
|
+
obligations.push("Generated release notes are claims, not author-confirmed requirements or test-execution evidence.");
|
|
72
|
+
if (findings.length)
|
|
73
|
+
obligations.push("Resolve the automatic findings below within their stated scope; an unused error field or matching body alone does not prove that the intended outcome fails.");
|
|
74
|
+
return { verdict: "not-established", summary: "Implementation evidence is linked below, but achievement of the expected outcome is not established. Resolve the review decisions and verify the explicit behavior before concluding that the PR delivers its goal.", claims, obligations };
|
|
75
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createReviewServer } from "./mcp.js";
|
|
4
|
+
try {
|
|
5
|
+
if (process.argv.length > 2)
|
|
6
|
+
throw new Error("diffninja-mcp accepts no arguments. Configure it as a stdio MCP server; pass inputs to review_diff.");
|
|
7
|
+
const server = createReviewServer();
|
|
8
|
+
// Closing the connection is what closes its connected review pages, and this
|
|
9
|
+
// transport cannot tell that the client hung up its end of the pipe, so EOF
|
|
10
|
+
// here is what ends the session. A signal still terminates the process the
|
|
11
|
+
// usual way, which takes the listener down with it.
|
|
12
|
+
let closing = false;
|
|
13
|
+
const shutdown = () => {
|
|
14
|
+
if (closing)
|
|
15
|
+
return;
|
|
16
|
+
closing = true;
|
|
17
|
+
void server.close().catch(() => { });
|
|
18
|
+
};
|
|
19
|
+
process.stdin.once("end", shutdown).once("close", shutdown);
|
|
20
|
+
await server.connect(new StdioServerTransport());
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error(`diffninja-mcp: ${error instanceof Error ? error.message : String(error)}`);
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export interface AnalysisScope {
|
|
3
|
+
readonly source: "repository" | "patch";
|
|
4
|
+
readonly note: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Rank a diff, or review one pull request. `mode` makes the caller's intent
|
|
8
|
+
* explicit: `auto` keeps the historical link detection, `connected` demands a
|
|
9
|
+
* link before anything is loaded, and `static` never navigates a link it finds
|
|
10
|
+
* inside a diff.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createReviewServer(): McpServer;
|