@plannotator/opencode 0.16.5 → 0.16.6

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 CHANGED
@@ -26667,6 +26667,14 @@ async function gitResetFile(runtime, filePath, cwd) {
26667
26667
  validateFilePath(filePath);
26668
26668
  await ensureGitSuccess(runtime, ["reset", "HEAD", "--", filePath], cwd);
26669
26669
  }
26670
+ function parseP4DiffType(diffType) {
26671
+ if (diffType === "p4-default")
26672
+ return { changelist: "default" };
26673
+ if (diffType.startsWith("p4-changelist:")) {
26674
+ return { changelist: diffType.slice("p4-changelist:".length) };
26675
+ }
26676
+ return null;
26677
+ }
26670
26678
 
26671
26679
  // ../../packages/server/git.ts
26672
26680
  async function runGit(args, options) {
@@ -26711,6 +26719,369 @@ function gitResetFile2(filePath, cwd) {
26711
26719
  return gitResetFile(runtime, filePath, cwd);
26712
26720
  }
26713
26721
 
26722
+ // ../../packages/server/p4.ts
26723
+ async function runP4(args, options) {
26724
+ const proc = Bun.spawn(["p4", ...args], {
26725
+ cwd: options?.cwd,
26726
+ stdout: "pipe",
26727
+ stderr: "pipe"
26728
+ });
26729
+ const [stdout, stderr, exitCode] = await Promise.all([
26730
+ new Response(proc.stdout).text(),
26731
+ new Response(proc.stderr).text(),
26732
+ proc.exited
26733
+ ]);
26734
+ return { stdout, stderr, exitCode };
26735
+ }
26736
+ function normalizePath(p) {
26737
+ return p.replace(/\\/g, "/");
26738
+ }
26739
+ function toRelativePath(absPath, normalizedRoot) {
26740
+ const normalized = normalizePath(absPath);
26741
+ if (normalized.startsWith(normalizedRoot + "/")) {
26742
+ return normalized.slice(normalizedRoot.length + 1);
26743
+ }
26744
+ if (normalized.startsWith(normalizedRoot)) {
26745
+ return normalized.slice(normalizedRoot.length);
26746
+ }
26747
+ return normalized;
26748
+ }
26749
+ var workspaceCache = new Map;
26750
+ var CACHE_TTL_MS = 30000;
26751
+ async function detectP4Workspace(cwd) {
26752
+ const key = cwd ?? process.cwd();
26753
+ const cached = workspaceCache.get(key);
26754
+ if (cached && Date.now() - cached.ts < CACHE_TTL_MS) {
26755
+ return cached.info;
26756
+ }
26757
+ const result = await runP4(["info"], { cwd });
26758
+ if (result.exitCode !== 0) {
26759
+ workspaceCache.set(key, { info: null, ts: Date.now() });
26760
+ return null;
26761
+ }
26762
+ const info = {};
26763
+ for (const line of result.stdout.split(`
26764
+ `)) {
26765
+ const colonIdx = line.indexOf(": ");
26766
+ if (colonIdx !== -1) {
26767
+ info[line.slice(0, colonIdx).trim()] = line.slice(colonIdx + 2).trim();
26768
+ }
26769
+ }
26770
+ const clientName = info["Client name"];
26771
+ const clientRoot = info["Client root"];
26772
+ const userName = info["User name"];
26773
+ const serverAddress = info["Server address"];
26774
+ if (!clientName || !clientRoot) {
26775
+ workspaceCache.set(key, { info: null, ts: Date.now() });
26776
+ return null;
26777
+ }
26778
+ const wsInfo = {
26779
+ clientName,
26780
+ clientRoot,
26781
+ normalizedRoot: normalizePath(clientRoot).replace(/\/$/, ""),
26782
+ userName,
26783
+ serverAddress
26784
+ };
26785
+ workspaceCache.set(key, { info: wsInfo, ts: Date.now() });
26786
+ return wsInfo;
26787
+ }
26788
+ async function getP4Context(cwd) {
26789
+ const workspace = await detectP4Workspace(cwd);
26790
+ if (!workspace) {
26791
+ return {
26792
+ currentBranch: "",
26793
+ defaultBranch: "",
26794
+ diffOptions: [],
26795
+ worktrees: [],
26796
+ cwd,
26797
+ vcsType: "p4"
26798
+ };
26799
+ }
26800
+ const diffOptions = [];
26801
+ const [defaultOpened, changesResult] = await Promise.all([
26802
+ runP4(["opened", "-c", "default"], { cwd }),
26803
+ runP4(["changes", "-s", "pending", "-u", workspace.userName, "-c", workspace.clientName], { cwd })
26804
+ ]);
26805
+ if (defaultOpened.exitCode === 0 && defaultOpened.stdout.trim()) {
26806
+ diffOptions.push({ id: "p4-default", label: "Default changelist" });
26807
+ }
26808
+ if (changesResult.exitCode === 0) {
26809
+ const candidates = [];
26810
+ for (const line of changesResult.stdout.trim().split(`
26811
+ `)) {
26812
+ if (!line)
26813
+ continue;
26814
+ const match = line.match(/^Change (\d+) on .+? by .+? \*?'(.*)'\s*$/);
26815
+ if (match) {
26816
+ const desc = match[2].length > 40 ? match[2].slice(0, 40) + "..." : match[2];
26817
+ candidates.push({ clNumber: match[1], desc });
26818
+ }
26819
+ }
26820
+ const checks = await Promise.all(candidates.map(({ clNumber }) => runP4(["opened", "-c", clNumber], { cwd })));
26821
+ for (let i = 0;i < candidates.length; i++) {
26822
+ if (checks[i].exitCode === 0 && checks[i].stdout.trim()) {
26823
+ diffOptions.push({
26824
+ id: `p4-changelist:${candidates[i].clNumber}`,
26825
+ label: `CL ${candidates[i].clNumber}: ${candidates[i].desc}`
26826
+ });
26827
+ }
26828
+ }
26829
+ }
26830
+ return {
26831
+ currentBranch: workspace.clientName,
26832
+ defaultBranch: "",
26833
+ diffOptions,
26834
+ worktrees: [],
26835
+ cwd: cwd ?? workspace.clientRoot,
26836
+ vcsType: "p4"
26837
+ };
26838
+ }
26839
+ function convertP4DiffToGitFormat(rawOutput, normalizedRoot) {
26840
+ const lines = rawOutput.split(`
26841
+ `);
26842
+ const result = [];
26843
+ for (let i = 0;i < lines.length; i++) {
26844
+ const line = lines[i];
26845
+ const separatorMatch = line.match(/^==== .+#\d+ - (.+?) ====/);
26846
+ if (separatorMatch) {
26847
+ const relativePath = toRelativePath(separatorMatch[1], normalizedRoot);
26848
+ result.push(`diff --git a/${relativePath} b/${relativePath}`);
26849
+ continue;
26850
+ }
26851
+ if (line.startsWith("--- ") && !line.startsWith("--- a/")) {
26852
+ const nextLine = lines[i + 1];
26853
+ let relativePath;
26854
+ if (nextLine && nextLine.startsWith("+++ ")) {
26855
+ const localPath = nextLine.slice(4).split("\t")[0];
26856
+ relativePath = toRelativePath(localPath, normalizedRoot);
26857
+ } else {
26858
+ const rawPath = line.slice(4).split("\t")[0];
26859
+ relativePath = toRelativePath(rawPath, normalizedRoot);
26860
+ }
26861
+ result.push(`diff --git a/${relativePath} b/${relativePath}`);
26862
+ result.push(`--- a/${relativePath}`);
26863
+ continue;
26864
+ }
26865
+ if (line.startsWith("+++ ") && !line.startsWith("+++ b/")) {
26866
+ const localPath = line.slice(4).split("\t")[0];
26867
+ const relativePath = toRelativePath(localPath, normalizedRoot);
26868
+ result.push(`+++ b/${relativePath}`);
26869
+ continue;
26870
+ }
26871
+ result.push(line);
26872
+ }
26873
+ return result.join(`
26874
+ `);
26875
+ }
26876
+ async function getNewFileDiff(localPath, relativePath) {
26877
+ try {
26878
+ const content = await Bun.file(localPath).text();
26879
+ const lines = content.split(`
26880
+ `);
26881
+ const header = [
26882
+ `diff --git a/${relativePath} b/${relativePath}`,
26883
+ "new file mode 100644",
26884
+ "--- /dev/null",
26885
+ `+++ b/${relativePath}`,
26886
+ `@@ -0,0 +1,${lines.length} @@`
26887
+ ];
26888
+ return header.join(`
26889
+ `) + `
26890
+ ` + lines.map((l) => `+${l}`).join(`
26891
+ `);
26892
+ } catch {
26893
+ return "";
26894
+ }
26895
+ }
26896
+ async function batchResolveDepotPaths(depotPaths, normalizedRoot, cwd) {
26897
+ const result = new Map;
26898
+ if (depotPaths.length === 0)
26899
+ return result;
26900
+ const whereResult = await runP4(["where", ...depotPaths], { cwd });
26901
+ if (whereResult.exitCode !== 0)
26902
+ return result;
26903
+ const outputLines = whereResult.stdout.trim().split(`
26904
+ `);
26905
+ for (let i = 0;i < outputLines.length && i < depotPaths.length; i++) {
26906
+ const whereOutput = outputLines[i];
26907
+ const rootIdx = whereOutput.indexOf(normalizePath(normalizedRoot).length > 0 ? normalizedRoot : "NOMATCH");
26908
+ const clientRootBackslash = normalizedRoot.replace(/\//g, "\\");
26909
+ const bsIdx = whereOutput.indexOf(clientRootBackslash);
26910
+ const fwIdx = whereOutput.indexOf(normalizedRoot);
26911
+ const idx = bsIdx !== -1 ? bsIdx : fwIdx;
26912
+ if (idx === -1)
26913
+ continue;
26914
+ const localPath = normalizePath(whereOutput.slice(idx));
26915
+ const relativePath = toRelativePath(localPath, normalizedRoot);
26916
+ result.set(depotPaths[i], { localPath, relativePath });
26917
+ }
26918
+ return result;
26919
+ }
26920
+ async function runP4Diff(diffType, cwd) {
26921
+ const workspace = await detectP4Workspace(cwd);
26922
+ if (!workspace) {
26923
+ return { patch: "", label: "P4 error", error: "Not in a Perforce workspace" };
26924
+ }
26925
+ const parsed = parseP4DiffType(diffType);
26926
+ if (!parsed) {
26927
+ return { patch: "", label: "Unknown diff type" };
26928
+ }
26929
+ try {
26930
+ const label = parsed.changelist === "default" ? "Default changelist" : `Changelist ${parsed.changelist}`;
26931
+ const openedArgs = parsed.changelist === "default" ? ["opened", "-c", "default"] : ["opened", "-c", parsed.changelist];
26932
+ const openedResult = await runP4(openedArgs, { cwd });
26933
+ if (openedResult.exitCode !== 0 || !openedResult.stdout.trim()) {
26934
+ return { patch: "", label, error: openedResult.stderr || undefined };
26935
+ }
26936
+ const diffDepotPaths = [];
26937
+ const addedDepotPaths = [];
26938
+ for (const line of openedResult.stdout.trim().split(`
26939
+ `)) {
26940
+ if (!line)
26941
+ continue;
26942
+ const typeMatch = line.match(/\((\S+)\)\s*(by\s|$)/);
26943
+ if (typeMatch && typeMatch[1].startsWith("binary"))
26944
+ continue;
26945
+ const depotPath = line.split("#")[0];
26946
+ if (!depotPath)
26947
+ continue;
26948
+ if (line.includes(" - add ")) {
26949
+ addedDepotPaths.push(depotPath);
26950
+ } else {
26951
+ diffDepotPaths.push(depotPath);
26952
+ }
26953
+ }
26954
+ let patch = "";
26955
+ if (diffDepotPaths.length > 0) {
26956
+ const diffResult = await runP4(["diff", "-du", ...diffDepotPaths], { cwd });
26957
+ if (diffResult.exitCode === 0 || diffResult.stdout.trim()) {
26958
+ patch = convertP4DiffToGitFormat(diffResult.stdout, workspace.normalizedRoot);
26959
+ }
26960
+ }
26961
+ if (addedDepotPaths.length > 0) {
26962
+ const resolved = await batchResolveDepotPaths(addedDepotPaths, workspace.normalizedRoot, cwd);
26963
+ for (const depotPath of addedDepotPaths) {
26964
+ const mapping = resolved.get(depotPath);
26965
+ if (!mapping)
26966
+ continue;
26967
+ const newFilePatch = await getNewFileDiff(mapping.localPath, mapping.relativePath);
26968
+ if (newFilePatch) {
26969
+ patch += (patch ? `
26970
+ ` : "") + newFilePatch;
26971
+ }
26972
+ }
26973
+ }
26974
+ return { patch, label };
26975
+ } catch (error) {
26976
+ const message = error instanceof Error ? error.message : String(error);
26977
+ return { patch: "", label: "P4 error", error: message };
26978
+ }
26979
+ }
26980
+ async function getP4FileContentsForDiff(diffType, filePath, cwd) {
26981
+ const workspace = await detectP4Workspace(cwd);
26982
+ if (!workspace)
26983
+ return { oldContent: null, newContent: null };
26984
+ validateFilePath(filePath);
26985
+ const fullLocalPath = `${workspace.normalizedRoot}/${filePath}`;
26986
+ const [printResult, newContent] = await Promise.all([
26987
+ runP4(["print", "-q", `${fullLocalPath}#have`], { cwd }),
26988
+ Bun.file(fullLocalPath).text().catch(() => null)
26989
+ ]);
26990
+ return {
26991
+ oldContent: printResult.exitCode === 0 ? printResult.stdout : null,
26992
+ newContent
26993
+ };
26994
+ }
26995
+
26996
+ // ../../packages/server/vcs.ts
26997
+ var GIT_DIFF_TYPES = new Set(["uncommitted", "staged", "unstaged", "last-commit", "branch"]);
26998
+ var gitProvider = {
26999
+ id: "git",
27000
+ async detect(cwd) {
27001
+ const proc = Bun.spawn(["git", "rev-parse", "--is-inside-work-tree"], {
27002
+ cwd: cwd ?? undefined,
27003
+ stdout: "ignore",
27004
+ stderr: "ignore"
27005
+ });
27006
+ return await proc.exited === 0;
27007
+ },
27008
+ ownsDiffType(diffType) {
27009
+ return GIT_DIFF_TYPES.has(diffType) || diffType.startsWith("worktree:");
27010
+ },
27011
+ getContext: getGitContext2,
27012
+ runDiff(diffType, defaultBranch, cwd) {
27013
+ return runGitDiff2(diffType, defaultBranch, cwd);
27014
+ },
27015
+ getFileContents(diffType, defaultBranch, filePath, oldPath, cwd) {
27016
+ return getFileContentsForDiff2(diffType, defaultBranch, filePath, oldPath, cwd);
27017
+ },
27018
+ stageFile: gitAddFile2,
27019
+ unstageFile: gitResetFile2,
27020
+ resolveCwd(diffType, fallbackCwd) {
27021
+ if (diffType.startsWith("worktree:")) {
27022
+ const parsed = parseWorktreeDiffType(diffType);
27023
+ if (parsed)
27024
+ return parsed.path;
27025
+ }
27026
+ return fallbackCwd;
27027
+ }
27028
+ };
27029
+ var p4Provider = {
27030
+ id: "p4",
27031
+ async detect(cwd) {
27032
+ return await detectP4Workspace(cwd) !== null;
27033
+ },
27034
+ ownsDiffType(diffType) {
27035
+ return diffType === "p4-default" || diffType.startsWith("p4-changelist:");
27036
+ },
27037
+ getContext: getP4Context,
27038
+ runDiff(diffType, _defaultBranch, cwd) {
27039
+ return runP4Diff(diffType, cwd);
27040
+ },
27041
+ getFileContents(diffType, _defaultBranch, filePath, _oldPath, cwd) {
27042
+ return getP4FileContentsForDiff(diffType, filePath, cwd);
27043
+ }
27044
+ };
27045
+ var providers = [gitProvider, p4Provider];
27046
+ var vcsCache = new Map;
27047
+ function getProviderForDiffType(diffType) {
27048
+ for (const provider of providers) {
27049
+ if (provider.ownsDiffType(diffType))
27050
+ return provider;
27051
+ }
27052
+ return gitProvider;
27053
+ }
27054
+ async function runVcsDiff(diffType, defaultBranch = "main", cwd) {
27055
+ const provider = getProviderForDiffType(diffType);
27056
+ return provider.runDiff(diffType, defaultBranch, cwd);
27057
+ }
27058
+ async function getVcsFileContentsForDiff(diffType, defaultBranch, filePath, oldPath, cwd) {
27059
+ const provider = getProviderForDiffType(diffType);
27060
+ return provider.getFileContents(diffType, defaultBranch, filePath, oldPath, cwd);
27061
+ }
27062
+ function canStageFiles(diffType) {
27063
+ const provider = getProviderForDiffType(diffType);
27064
+ return provider.stageFile !== undefined;
27065
+ }
27066
+ async function stageFile(diffType, filePath, cwd) {
27067
+ const provider = getProviderForDiffType(diffType);
27068
+ if (!provider.stageFile) {
27069
+ throw new Error(`Staging not available for ${provider.id}`);
27070
+ }
27071
+ return provider.stageFile(filePath, cwd);
27072
+ }
27073
+ async function unstageFile(diffType, filePath, cwd) {
27074
+ const provider = getProviderForDiffType(diffType);
27075
+ if (!provider.unstageFile) {
27076
+ throw new Error(`Unstaging not available for ${provider.id}`);
27077
+ }
27078
+ return provider.unstageFile(filePath, cwd);
27079
+ }
27080
+ function resolveVcsCwd(diffType, fallbackCwd) {
27081
+ const provider = getProviderForDiffType(diffType);
27082
+ return provider.resolveCwd?.(diffType, fallbackCwd) ?? fallbackCwd;
27083
+ }
27084
+
26714
27085
  // ../../packages/shared/agent-jobs.ts
26715
27086
  var AGENT_HEARTBEAT_COMMENT = `:
26716
27087
 
@@ -27963,12 +28334,7 @@ async function startReviewServer(options) {
27963
28334
  mode: "review",
27964
28335
  getServerUrl: () => serverUrl,
27965
28336
  getCwd: () => {
27966
- if (currentDiffType.startsWith("worktree:")) {
27967
- const parsed = parseWorktreeDiffType(currentDiffType);
27968
- if (parsed)
27969
- return parsed.path;
27970
- }
27971
- return gitContext?.cwd ?? process.cwd();
28337
+ return resolveVcsCwd(currentDiffType, gitContext?.cwd) ?? process.cwd();
27972
28338
  }
27973
28339
  });
27974
28340
  const aiRegistry = new ProviderRegistry;
@@ -28029,12 +28395,7 @@ async function startReviewServer(options) {
28029
28395
  registry: aiRegistry,
28030
28396
  sessionManager: aiSessionManager,
28031
28397
  getCwd: () => {
28032
- if (currentDiffType.startsWith("worktree:")) {
28033
- const parsed = parseWorktreeDiffType(currentDiffType);
28034
- if (parsed)
28035
- return parsed.path;
28036
- }
28037
- return gitContext?.cwd ?? process.cwd();
28398
+ return resolveVcsCwd(currentDiffType, gitContext?.cwd) ?? process.cwd();
28038
28399
  }
28039
28400
  });
28040
28401
  }
@@ -28092,7 +28453,7 @@ async function startReviewServer(options) {
28092
28453
  }
28093
28454
  const defaultBranch = gitContext?.defaultBranch || "main";
28094
28455
  const defaultCwd = gitContext?.cwd;
28095
- const result = await runGitDiff2(newDiffType, defaultBranch, defaultCwd);
28456
+ const result = await runVcsDiff(newDiffType, defaultBranch, defaultCwd);
28096
28457
  currentPatch = result.patch;
28097
28458
  currentGitRef = result.label;
28098
28459
  currentDiffType = newDiffType;
@@ -28147,35 +28508,27 @@ async function startReviewServer(options) {
28147
28508
  }
28148
28509
  const defaultBranch = gitContext?.defaultBranch || "main";
28149
28510
  const defaultCwd = gitContext?.cwd;
28150
- const result = await getFileContentsForDiff2(currentDiffType, defaultBranch, filePath, oldPath, defaultCwd);
28511
+ const result = await getVcsFileContentsForDiff(currentDiffType, defaultBranch, filePath, oldPath, defaultCwd);
28151
28512
  return Response.json(result);
28152
28513
  }
28153
28514
  if (url.pathname === "/api/git-add" && req.method === "POST") {
28154
- if (isPRMode) {
28155
- return Response.json({ error: "Not available for PR reviews" }, { status: 400 });
28515
+ if (isPRMode || !canStageFiles(currentDiffType)) {
28516
+ return Response.json({ error: "Staging not available" }, { status: 400 });
28156
28517
  }
28157
28518
  try {
28158
28519
  const body = await req.json();
28159
28520
  if (!body.filePath) {
28160
28521
  return Response.json({ error: "Missing filePath" }, { status: 400 });
28161
28522
  }
28162
- let cwd;
28163
- if (currentDiffType.startsWith("worktree:")) {
28164
- const parsed = parseWorktreeDiffType(currentDiffType);
28165
- if (parsed)
28166
- cwd = parsed.path;
28167
- }
28168
- if (!cwd) {
28169
- cwd = gitContext?.cwd;
28170
- }
28523
+ const cwd = resolveVcsCwd(currentDiffType, gitContext?.cwd);
28171
28524
  if (body.undo) {
28172
- await gitResetFile2(body.filePath, cwd);
28525
+ await unstageFile(currentDiffType, body.filePath, cwd);
28173
28526
  } else {
28174
- await gitAddFile2(body.filePath, cwd);
28527
+ await stageFile(currentDiffType, body.filePath, cwd);
28175
28528
  }
28176
28529
  return Response.json({ ok: true });
28177
28530
  } catch (err) {
28178
- const message = err instanceof Error ? err.message : "Failed to git add";
28531
+ const message = err instanceof Error ? err.message : "Failed to stage file";
28179
28532
  return Response.json({ error: message }, { status: 500 });
28180
28533
  }
28181
28534
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plannotator/opencode",
3
- "version": "0.16.5",
3
+ "version": "0.16.6",
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.16.5",
41
+ "@plannotator/server": "0.16.6",
42
42
  "@plannotator/shared": "0.0.1"
43
43
  },
44
44
  "peerDependencies": {