@plannotator/opencode 0.19.15 → 0.19.16
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/index.js +346 -0
- package/package.json +2 -2
- package/plannotator.html +2 -2
- package/review-editor.html +143 -136
package/dist/index.js
CHANGED
|
@@ -66427,6 +66427,321 @@ ${prompt}` : prompt;
|
|
|
66427
66427
|
}
|
|
66428
66428
|
};
|
|
66429
66429
|
}
|
|
66430
|
+
// ../../packages/shared/code-nav.ts
|
|
66431
|
+
function validateFilePath2(filePath) {
|
|
66432
|
+
if (filePath.includes("..") || filePath.startsWith("/")) {
|
|
66433
|
+
throw new Error("Invalid file path");
|
|
66434
|
+
}
|
|
66435
|
+
}
|
|
66436
|
+
var CODE_NAV_IGNORED_GLOBS = [
|
|
66437
|
+
"node_modules",
|
|
66438
|
+
".git",
|
|
66439
|
+
"dist",
|
|
66440
|
+
"build",
|
|
66441
|
+
".next",
|
|
66442
|
+
"__pycache__",
|
|
66443
|
+
".turbo",
|
|
66444
|
+
".cache",
|
|
66445
|
+
"target",
|
|
66446
|
+
"vendor",
|
|
66447
|
+
"coverage",
|
|
66448
|
+
".venv",
|
|
66449
|
+
".pytest_cache"
|
|
66450
|
+
];
|
|
66451
|
+
var RG_TYPE_MAP = {
|
|
66452
|
+
typescript: "ts",
|
|
66453
|
+
javascript: "js",
|
|
66454
|
+
python: "py",
|
|
66455
|
+
go: "go",
|
|
66456
|
+
rust: "rust",
|
|
66457
|
+
java: "java",
|
|
66458
|
+
ruby: "ruby",
|
|
66459
|
+
cpp: "cpp",
|
|
66460
|
+
c: "c"
|
|
66461
|
+
};
|
|
66462
|
+
var DEFINITION_PATTERNS = [
|
|
66463
|
+
{
|
|
66464
|
+
languages: ["typescript", "javascript"],
|
|
66465
|
+
patterns: [
|
|
66466
|
+
String.raw`(?:export\s+)?(?:async\s+)?function\s+SYMBOL\b`,
|
|
66467
|
+
String.raw`(?:export\s+)?(?:const|let|var)\s+SYMBOL\s*[=:]`,
|
|
66468
|
+
String.raw`(?:export\s+)?class\s+SYMBOL\b`,
|
|
66469
|
+
String.raw`(?:export\s+)?(?:interface|type)\s+SYMBOL\b`,
|
|
66470
|
+
String.raw`(?:export\s+)?enum\s+SYMBOL\b`,
|
|
66471
|
+
String.raw`^\s+(?:(?:async|static|readonly|get|set|private|protected|public)\s+)+SYMBOL\s*[(<:]`
|
|
66472
|
+
]
|
|
66473
|
+
},
|
|
66474
|
+
{
|
|
66475
|
+
languages: ["python"],
|
|
66476
|
+
patterns: [
|
|
66477
|
+
String.raw`(?:^|\s)def\s+SYMBOL\s*\(`,
|
|
66478
|
+
String.raw`(?:^|\s)class\s+SYMBOL\b`,
|
|
66479
|
+
String.raw`^SYMBOL\s*=`
|
|
66480
|
+
]
|
|
66481
|
+
},
|
|
66482
|
+
{
|
|
66483
|
+
languages: ["go"],
|
|
66484
|
+
patterns: [
|
|
66485
|
+
String.raw`func\s+(?:\([^)]+\)\s+)?SYMBOL\s*\(`,
|
|
66486
|
+
String.raw`type\s+SYMBOL\s`,
|
|
66487
|
+
String.raw`var\s+SYMBOL\s`
|
|
66488
|
+
]
|
|
66489
|
+
},
|
|
66490
|
+
{
|
|
66491
|
+
languages: ["rust"],
|
|
66492
|
+
patterns: [
|
|
66493
|
+
String.raw`(?:pub(?:\([^)]*\))?\s+)?fn\s+SYMBOL\b`,
|
|
66494
|
+
String.raw`(?:pub(?:\([^)]*\))?\s+)?struct\s+SYMBOL\b`,
|
|
66495
|
+
String.raw`(?:pub(?:\([^)]*\))?\s+)?enum\s+SYMBOL\b`,
|
|
66496
|
+
String.raw`(?:pub(?:\([^)]*\))?\s+)?trait\s+SYMBOL\b`,
|
|
66497
|
+
String.raw`(?:pub(?:\([^)]*\))?\s+)?type\s+SYMBOL\b`,
|
|
66498
|
+
String.raw`(?:pub(?:\([^)]*\))?\s+)?mod\s+SYMBOL\b`
|
|
66499
|
+
]
|
|
66500
|
+
}
|
|
66501
|
+
];
|
|
66502
|
+
var GENERIC_DEFINITION_PATTERNS = [
|
|
66503
|
+
String.raw`(?:function|def|func|fn|class|struct|enum|trait|interface|type)\s+SYMBOL\b`,
|
|
66504
|
+
String.raw`(?:const|let|var|val)\s+SYMBOL\s*[=:]`
|
|
66505
|
+
];
|
|
66506
|
+
function escapeRegex(str) {
|
|
66507
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
66508
|
+
}
|
|
66509
|
+
function sameDirectory(a2, b3) {
|
|
66510
|
+
const dirA = a2.lastIndexOf("/");
|
|
66511
|
+
const dirB = b3.lastIndexOf("/");
|
|
66512
|
+
if (dirA === -1 && dirB === -1)
|
|
66513
|
+
return true;
|
|
66514
|
+
return a2.slice(0, dirA) === b3.slice(0, dirB);
|
|
66515
|
+
}
|
|
66516
|
+
function isTestFile(filePath) {
|
|
66517
|
+
return /(?:test|spec|__tests__|_test\.|\.test\.|\.spec\.)/i.test(filePath);
|
|
66518
|
+
}
|
|
66519
|
+
function buildRgArgs(symbol, language) {
|
|
66520
|
+
const args = [
|
|
66521
|
+
"--json",
|
|
66522
|
+
"--line-number",
|
|
66523
|
+
"--column",
|
|
66524
|
+
"--max-count",
|
|
66525
|
+
"50",
|
|
66526
|
+
"--max-filesize",
|
|
66527
|
+
"1M",
|
|
66528
|
+
"--no-messages"
|
|
66529
|
+
];
|
|
66530
|
+
for (const dir of CODE_NAV_IGNORED_GLOBS) {
|
|
66531
|
+
args.push("--glob", `!${dir}`);
|
|
66532
|
+
}
|
|
66533
|
+
if (language) {
|
|
66534
|
+
const rgType = RG_TYPE_MAP[language];
|
|
66535
|
+
if (rgType)
|
|
66536
|
+
args.push("--type", rgType);
|
|
66537
|
+
}
|
|
66538
|
+
args.push("--word-regexp", "--", escapeRegex(symbol), ".");
|
|
66539
|
+
return args;
|
|
66540
|
+
}
|
|
66541
|
+
var PARSE_CAP = 500;
|
|
66542
|
+
function parseRgJsonOutput(stdout, symbol, language) {
|
|
66543
|
+
const locations = [];
|
|
66544
|
+
const lines = stdout.split(`
|
|
66545
|
+
`);
|
|
66546
|
+
for (const line of lines) {
|
|
66547
|
+
if (locations.length >= PARSE_CAP)
|
|
66548
|
+
break;
|
|
66549
|
+
if (!line.trim())
|
|
66550
|
+
continue;
|
|
66551
|
+
let parsed;
|
|
66552
|
+
try {
|
|
66553
|
+
parsed = JSON.parse(line);
|
|
66554
|
+
} catch {
|
|
66555
|
+
continue;
|
|
66556
|
+
}
|
|
66557
|
+
if (parsed.type !== "match")
|
|
66558
|
+
continue;
|
|
66559
|
+
const d2 = parsed.data;
|
|
66560
|
+
const snippet = d2.lines.text.trimEnd();
|
|
66561
|
+
const column = d2.submatches?.[0]?.start ?? 0;
|
|
66562
|
+
const kind = classifyMatch(snippet, symbol, language);
|
|
66563
|
+
const filePath = d2.path.text.startsWith("./") ? d2.path.text.slice(2) : d2.path.text;
|
|
66564
|
+
locations.push({
|
|
66565
|
+
kind,
|
|
66566
|
+
confidence: kind === "definition" ? "likely" : "possible",
|
|
66567
|
+
filePath,
|
|
66568
|
+
line: d2.line_number,
|
|
66569
|
+
column,
|
|
66570
|
+
snippet: snippet.length > 200 ? snippet.slice(0, 200) + "\u2026" : snippet
|
|
66571
|
+
});
|
|
66572
|
+
}
|
|
66573
|
+
return locations;
|
|
66574
|
+
}
|
|
66575
|
+
function classifyMatch(snippet, symbol, language) {
|
|
66576
|
+
const escaped = escapeRegex(symbol);
|
|
66577
|
+
if (language) {
|
|
66578
|
+
const langPatterns = DEFINITION_PATTERNS.find((p2) => p2.languages.includes(language));
|
|
66579
|
+
if (langPatterns) {
|
|
66580
|
+
for (const pattern of langPatterns.patterns) {
|
|
66581
|
+
const re3 = new RegExp(pattern.replace("SYMBOL", escaped));
|
|
66582
|
+
if (re3.test(snippet))
|
|
66583
|
+
return "definition";
|
|
66584
|
+
}
|
|
66585
|
+
}
|
|
66586
|
+
}
|
|
66587
|
+
for (const pattern of GENERIC_DEFINITION_PATTERNS) {
|
|
66588
|
+
const re3 = new RegExp(pattern.replace("SYMBOL", escaped));
|
|
66589
|
+
if (re3.test(snippet))
|
|
66590
|
+
return "definition";
|
|
66591
|
+
}
|
|
66592
|
+
return "reference";
|
|
66593
|
+
}
|
|
66594
|
+
function rankLocations(locations, context, cap2 = 50) {
|
|
66595
|
+
const capped = locations.length > cap2;
|
|
66596
|
+
const changedSet = new Set(context.changedFiles);
|
|
66597
|
+
function score(loc) {
|
|
66598
|
+
let s2 = 0;
|
|
66599
|
+
if (loc.filePath === context.sourceFilePath)
|
|
66600
|
+
s2 += 1000;
|
|
66601
|
+
else if (changedSet.has(loc.filePath))
|
|
66602
|
+
s2 += 500;
|
|
66603
|
+
else if (sameDirectory(loc.filePath, context.sourceFilePath))
|
|
66604
|
+
s2 += 200;
|
|
66605
|
+
if (isTestFile(loc.filePath) && !context.isTestFile)
|
|
66606
|
+
s2 -= 300;
|
|
66607
|
+
if (loc.kind === "definition")
|
|
66608
|
+
s2 += 100;
|
|
66609
|
+
if (loc.confidence === "likely")
|
|
66610
|
+
s2 += 50;
|
|
66611
|
+
return s2;
|
|
66612
|
+
}
|
|
66613
|
+
const sorted = [...locations].sort((a2, b3) => score(b3) - score(a2));
|
|
66614
|
+
const truncated = sorted.slice(0, cap2);
|
|
66615
|
+
return {
|
|
66616
|
+
definitions: truncated.filter((l3) => l3.kind === "definition"),
|
|
66617
|
+
references: truncated.filter((l3) => l3.kind === "reference"),
|
|
66618
|
+
capped
|
|
66619
|
+
};
|
|
66620
|
+
}
|
|
66621
|
+
function extractChangedFiles(patch) {
|
|
66622
|
+
if (!patch)
|
|
66623
|
+
return [];
|
|
66624
|
+
const set = new Set;
|
|
66625
|
+
const re3 = /^diff --git a\/(.+?) b\/(.+)$/gm;
|
|
66626
|
+
let m3;
|
|
66627
|
+
while ((m3 = re3.exec(patch)) !== null) {
|
|
66628
|
+
set.add(m3[1]);
|
|
66629
|
+
set.add(m3[2]);
|
|
66630
|
+
}
|
|
66631
|
+
return [...set];
|
|
66632
|
+
}
|
|
66633
|
+
function validateCodeNavRequest(body3) {
|
|
66634
|
+
if (!body3 || typeof body3 !== "object")
|
|
66635
|
+
return "Invalid request body";
|
|
66636
|
+
const b3 = body3;
|
|
66637
|
+
if (typeof b3.symbol !== "string" || !b3.symbol.trim()) {
|
|
66638
|
+
return "Missing or empty symbol";
|
|
66639
|
+
}
|
|
66640
|
+
if (typeof b3.filePath !== "string" || !b3.filePath.trim()) {
|
|
66641
|
+
return "Missing filePath";
|
|
66642
|
+
}
|
|
66643
|
+
try {
|
|
66644
|
+
validateFilePath2(b3.filePath);
|
|
66645
|
+
} catch {
|
|
66646
|
+
return "Invalid filePath";
|
|
66647
|
+
}
|
|
66648
|
+
if (b3.side !== "old" && b3.side !== "new") {
|
|
66649
|
+
return "side must be 'old' or 'new'";
|
|
66650
|
+
}
|
|
66651
|
+
return null;
|
|
66652
|
+
}
|
|
66653
|
+
var rgAvailable = null;
|
|
66654
|
+
async function resolveCodeNav(runtime4, request, cwd, changedFiles) {
|
|
66655
|
+
const start = Date.now();
|
|
66656
|
+
if (rgAvailable === null) {
|
|
66657
|
+
const check = await runtime4.runCommand("rg", ["--version"], {
|
|
66658
|
+
cwd,
|
|
66659
|
+
timeoutMs: 2000
|
|
66660
|
+
});
|
|
66661
|
+
rgAvailable = check.exitCode === 0;
|
|
66662
|
+
}
|
|
66663
|
+
if (!rgAvailable) {
|
|
66664
|
+
return {
|
|
66665
|
+
backend: "unavailable",
|
|
66666
|
+
complete: true,
|
|
66667
|
+
definitions: [],
|
|
66668
|
+
references: [],
|
|
66669
|
+
searchScope: "head",
|
|
66670
|
+
stats: { elapsedMs: Date.now() - start, capped: false }
|
|
66671
|
+
};
|
|
66672
|
+
}
|
|
66673
|
+
const args = buildRgArgs(request.symbol, request.language);
|
|
66674
|
+
const result = await runtime4.runCommand("rg", args, {
|
|
66675
|
+
cwd,
|
|
66676
|
+
timeoutMs: 5000
|
|
66677
|
+
});
|
|
66678
|
+
if (result.exitCode === 2) {
|
|
66679
|
+
return {
|
|
66680
|
+
backend: "search",
|
|
66681
|
+
complete: true,
|
|
66682
|
+
definitions: [],
|
|
66683
|
+
references: [],
|
|
66684
|
+
searchScope: "head",
|
|
66685
|
+
stats: { elapsedMs: Date.now() - start, capped: false }
|
|
66686
|
+
};
|
|
66687
|
+
}
|
|
66688
|
+
const locations = parseRgJsonOutput(result.stdout, request.symbol, request.language);
|
|
66689
|
+
const ranked = rankLocations(locations, {
|
|
66690
|
+
sourceFilePath: request.filePath,
|
|
66691
|
+
changedFiles,
|
|
66692
|
+
isTestFile: isTestFile(request.filePath)
|
|
66693
|
+
});
|
|
66694
|
+
return {
|
|
66695
|
+
backend: "search",
|
|
66696
|
+
complete: true,
|
|
66697
|
+
definitions: ranked.definitions,
|
|
66698
|
+
references: ranked.references,
|
|
66699
|
+
searchScope: "head",
|
|
66700
|
+
stats: { elapsedMs: Date.now() - start, capped: ranked.capped }
|
|
66701
|
+
};
|
|
66702
|
+
}
|
|
66703
|
+
|
|
66704
|
+
// ../../packages/server/code-nav.ts
|
|
66705
|
+
var bunCodeNavRuntime = {
|
|
66706
|
+
async runCommand(command, args, options) {
|
|
66707
|
+
let proc;
|
|
66708
|
+
try {
|
|
66709
|
+
proc = Bun.spawn([command, ...args], {
|
|
66710
|
+
cwd: options?.cwd,
|
|
66711
|
+
stdout: "pipe",
|
|
66712
|
+
stderr: "pipe"
|
|
66713
|
+
});
|
|
66714
|
+
} catch {
|
|
66715
|
+
return { stdout: "", stderr: "command not found", exitCode: 1 };
|
|
66716
|
+
}
|
|
66717
|
+
let timer;
|
|
66718
|
+
if (options?.timeoutMs) {
|
|
66719
|
+
timer = setTimeout(() => proc.kill(), options.timeoutMs);
|
|
66720
|
+
}
|
|
66721
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
66722
|
+
new Response(proc.stdout).text(),
|
|
66723
|
+
new Response(proc.stderr).text(),
|
|
66724
|
+
proc.exited
|
|
66725
|
+
]);
|
|
66726
|
+
if (timer)
|
|
66727
|
+
clearTimeout(timer);
|
|
66728
|
+
return { stdout, stderr, exitCode };
|
|
66729
|
+
}
|
|
66730
|
+
};
|
|
66731
|
+
async function handleCodeNavResolve(req, cwd, changedFiles) {
|
|
66732
|
+
try {
|
|
66733
|
+
const body3 = await req.json();
|
|
66734
|
+
const error = validateCodeNavRequest(body3);
|
|
66735
|
+
if (error) {
|
|
66736
|
+
return Response.json({ error }, { status: 400 });
|
|
66737
|
+
}
|
|
66738
|
+
const result = await resolveCodeNav(bunCodeNavRuntime, body3, cwd, changedFiles);
|
|
66739
|
+
return Response.json(result);
|
|
66740
|
+
} catch (err) {
|
|
66741
|
+
return Response.json({ error: err instanceof Error ? err.message : "Code navigation failed" }, { status: 500 });
|
|
66742
|
+
}
|
|
66743
|
+
}
|
|
66744
|
+
|
|
66430
66745
|
// ../../packages/server/review.ts
|
|
66431
66746
|
var MAX_RETRIES2 = 5;
|
|
66432
66747
|
var RETRY_DELAY_MS2 = 500;
|
|
@@ -66986,6 +67301,37 @@ async function startReviewServer(options) {
|
|
|
66986
67301
|
}
|
|
66987
67302
|
return Response.json({ error: "No file access available" }, { status: 400 });
|
|
66988
67303
|
}
|
|
67304
|
+
if (url.pathname === "/api/code-nav/resolve" && req.method === "POST") {
|
|
67305
|
+
const hasCodeNavAccess = !!gitContext || !!options.agentCwd || !!options.worktreePool;
|
|
67306
|
+
if (!hasCodeNavAccess) {
|
|
67307
|
+
return Response.json({ error: "Code navigation requires local access" }, { status: 400 });
|
|
67308
|
+
}
|
|
67309
|
+
const navCwd = resolveAgentCwd();
|
|
67310
|
+
const changedFiles = extractChangedFiles(currentPatch);
|
|
67311
|
+
return handleCodeNavResolve(req, navCwd, changedFiles);
|
|
67312
|
+
}
|
|
67313
|
+
if (url.pathname === "/api/code-nav/file" && req.method === "GET") {
|
|
67314
|
+
const hasCodeNavAccess = !!gitContext || !!options.agentCwd || !!options.worktreePool;
|
|
67315
|
+
if (!hasCodeNavAccess) {
|
|
67316
|
+
return Response.json({ error: "Code navigation requires local access" }, { status: 400 });
|
|
67317
|
+
}
|
|
67318
|
+
const filePath = url.searchParams.get("path");
|
|
67319
|
+
if (!filePath) {
|
|
67320
|
+
return Response.json({ error: "Missing path" }, { status: 400 });
|
|
67321
|
+
}
|
|
67322
|
+
try {
|
|
67323
|
+
validateFilePath(filePath);
|
|
67324
|
+
} catch {
|
|
67325
|
+
return Response.json({ error: "Invalid path" }, { status: 400 });
|
|
67326
|
+
}
|
|
67327
|
+
try {
|
|
67328
|
+
const navCwd = resolveAgentCwd();
|
|
67329
|
+
const content = await Bun.file(`${navCwd}/${filePath}`).text();
|
|
67330
|
+
return Response.json({ content });
|
|
67331
|
+
} catch {
|
|
67332
|
+
return Response.json({ error: "File not found" }, { status: 404 });
|
|
67333
|
+
}
|
|
67334
|
+
}
|
|
66989
67335
|
if (url.pathname === "/api/git-add" && req.method === "POST") {
|
|
66990
67336
|
const stageCwd = resolveVcsCwd(currentDiffType, gitContext?.cwd);
|
|
66991
67337
|
if (isPRMode || !await canStageFiles(currentDiffType, stageCwd)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plannotator/opencode",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.16",
|
|
4
4
|
"description": "Plannotator plugin for OpenCode - interactive plan review with visual annotation",
|
|
5
5
|
"author": "backnotprop",
|
|
6
6
|
"license": "MIT OR Apache-2.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@opencode-ai/plugin": "^1.1.10"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@plannotator/server": "0.19.
|
|
41
|
+
"@plannotator/server": "0.19.16",
|
|
42
42
|
"@plannotator/shared": "0.0.1"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|