@plannotator/opencode 0.19.18 → 0.19.19

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
@@ -45044,7 +45044,7 @@ var init_opencode_sdk = __esm(() => {
45044
45044
 
45045
45045
  // index.ts
45046
45046
  import { tool } from "@opencode-ai/plugin";
45047
- import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
45047
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
45048
45048
  import path4 from "path";
45049
45049
 
45050
45050
  // ../../packages/server/remote.ts
@@ -67743,6 +67743,13 @@ var PLAN_TOOL_NAMES = {
67743
67743
  function getPlanToolName(runtime4) {
67744
67744
  return runtime4 && PLAN_TOOL_NAMES[runtime4] || "ExitPlanMode";
67745
67745
  }
67746
+ function buildPlanFileRule(toolName, planFilePath) {
67747
+ if (!planFilePath)
67748
+ return "";
67749
+ return `- Your plan is saved at: ${planFilePath}
67750
+ You can edit this file to make targeted changes, then pass its path to ${toolName}.
67751
+ `;
67752
+ }
67746
67753
  var DEFAULT_REVIEW_APPROVED_PROMPT = `# Code Review
67747
67754
 
67748
67755
  Code review completed \u2014 no changes requested.`;
@@ -68755,63 +68762,21 @@ function getReviewHtml() {
68755
68762
  return _reviewHtml;
68756
68763
  }
68757
68764
  var DEFAULT_PLAN_TIMEOUT_SECONDS = 345600;
68758
- var MAX_PLAN_SIZE = 5 * 1024 * 1024;
68759
- function getPlanBackingPath(directory) {
68760
- const planDir = path4.join(directory, ".opencode", "plans");
68761
- return path4.join(planDir, "_active-plan.md");
68762
- }
68763
- function applyEdits(existingLines, edits) {
68764
- const sorted = [...edits].sort((a3, b3) => a3.start - b3.start);
68765
- const lines = [...existingLines];
68766
- let offset = 0;
68767
- for (const edit of sorted) {
68768
- const start = edit.start - 1 + offset;
68769
- const end = edit.end != null ? edit.end + offset : lines.length;
68770
- const newLines = edit.content ? edit.content.split(`
68771
- `) : [];
68772
- const removedCount = end - start;
68773
- lines.splice(start, removedCount, ...newLines);
68774
- offset += newLines.length - removedCount;
68775
- }
68776
- return lines;
68765
+ function isFilePath(value) {
68766
+ return path4.isAbsolute(value) && value.endsWith(".md") && existsSync10(value);
68777
68767
  }
68778
- function validateEdits(existingLines, edits) {
68779
- const lineCount = existingLines.length;
68780
- for (const edit of edits) {
68781
- if (!Number.isInteger(edit.start) || edit.start < 1) {
68782
- return `start must be a positive integer >= 1, got ${edit.start}`;
68783
- }
68784
- if (edit.start > lineCount + 1) {
68785
- return `start (${edit.start}) exceeds file length + 1 (${lineCount + 1})`;
68786
- }
68787
- if (edit.end != null) {
68788
- if (!Number.isInteger(edit.end) || edit.end < edit.start) {
68789
- return `end (${edit.end}) must be >= start (${edit.start})`;
68790
- }
68791
- if (edit.end > lineCount) {
68792
- return `end (${edit.end}) exceeds file length (${lineCount})`;
68793
- }
68768
+ function resolvePlanContent(plan) {
68769
+ if (isFilePath(plan)) {
68770
+ const content = readFileSync7(plan, "utf-8");
68771
+ if (!content.trim()) {
68772
+ throw new Error(`Plan file at ${plan} is empty. Write your plan content first, then call submit_plan.`);
68794
68773
  }
68774
+ return { content, filePath: plan };
68795
68775
  }
68796
- const sorted = [...edits].sort((a3, b3) => a3.start - b3.start);
68797
- for (let i4 = 1;i4 < sorted.length; i4++) {
68798
- const prev = sorted[i4 - 1];
68799
- const curr = sorted[i4];
68800
- if (prev.start > lineCount)
68801
- continue;
68802
- const prevEnd = prev.end ?? lineCount;
68803
- if (curr.start <= prevEnd) {
68804
- return `edits overlap: [${prev.start},${prevEnd}] and [${curr.start},${curr.end ?? "end"}]`;
68805
- }
68776
+ if (path4.isAbsolute(plan) && plan.endsWith(".md")) {
68777
+ throw new Error(`File not found: ${plan}. Check the path and try again.`);
68806
68778
  }
68807
- return null;
68808
- }
68809
- function formatWithLineNumbers(content) {
68810
- const lines = content.split(`
68811
- `);
68812
- const width = String(lines.length).length;
68813
- return lines.map((line, i4) => `${String(i4 + 1).padStart(width)}| ${line}`).join(`
68814
- `);
68779
+ return { content: plan };
68815
68780
  }
68816
68781
  function getPlanningPrompt() {
68817
68782
  return `## Plannotator \u2014 Plan Review
@@ -68820,26 +68785,10 @@ You have a plan submission tool called \`submit_plan\`. It opens an interactive
68820
68785
 
68821
68786
  **How to use it:**
68822
68787
 
68823
- \`submit_plan\` accepts an array of line-range edits. On first submission, pass the full plan as a single edit starting at line 1:
68824
-
68825
- \`\`\`json
68826
- { "edits": [{ "start": 1, "content": "# My Plan\\n\\n## Goals\\n..." }] }
68827
- \`\`\`
68828
-
68829
- If the user denies and requests changes, apply surgical edits using line ranges. The tool response includes your plan with line numbers so you can target specific ranges:
68830
-
68831
- \`\`\`json
68832
- { "edits": [
68833
- { "start": 12, "end": 14, "content": "revised section content" },
68834
- { "start": 30, "end": 30, "content": "" }
68835
- ] }
68836
- \`\`\`
68788
+ - Pass your plan as markdown text \u2014 \`submit_plan(plan: "# My Plan\\n...")\`.
68789
+ - Or pass an absolute file path to a .md file \u2014 \`submit_plan(plan: "/path/to/plan.md")\`.
68837
68790
 
68838
- Edit semantics:
68839
- - \`start\` and \`end\` are 1-indexed, inclusive line numbers
68840
- - Omit \`end\` to replace from \`start\` through end of file (use this for the initial full write)
68841
- - Empty \`content\` with \`start\`/\`end\` deletes those lines
68842
- - Multiple edits in one call are applied in order; line numbers refer to the state before edits
68791
+ The tool auto-detects whether you passed text or a file path. Both open the same review UI.
68843
68792
 
68844
68793
  ### Before you write a plan
68845
68794
 
@@ -69007,9 +68956,9 @@ tools (except writing markdown files), or otherwise make changes to the system.
69007
68956
  return;
69008
68957
  output.system.push(`## Plan Submission
69009
68958
 
69010
- When you have completed your plan, call the \`submit_plan\` tool to submit it for user review. Pass your full plan as a single edit: \`{ "edits": [{ "start": 1, "content": "..." }] }\`.
68959
+ When you have completed your plan, call the \`submit_plan\` tool to submit it for user review. Pass your plan as markdown text, or pass an absolute file path to a .md file.
69011
68960
 
69012
- The user will review your plan in a visual UI where they can annotate, approve, or request changes. If rejected, the response includes your plan with line numbers; use targeted edits to revise specific sections.
68961
+ The user will review your plan in a visual UI where they can annotate, approve, or request changes. If rejected, revise based on their feedback and call submit_plan again.
69013
68962
 
69014
68963
  Do NOT proceed with implementation until your plan is approved.`);
69015
68964
  },
@@ -69058,13 +69007,9 @@ Do NOT proceed with implementation until your plan is approved.`);
69058
69007
  if (shouldRegisterSubmitPlan(workflowOptions)) {
69059
69008
  plugin.tool = {
69060
69009
  submit_plan: tool({
69061
- description: "Submit a plan for user review via line-range edits. First call: pass a single edit with start=1 and your full plan as content (omit end). Subsequent calls after denial: pass targeted edits using the line numbers from the previous response. The tool manages a backing file; you never touch the file directly.",
69010
+ description: "Planning tool used to submit a plan to the user for review. Before calling this tool you must conduct interactive and exploratory analysis in order to submit a quality plan. Ask questions. Explore the codebase for context if needed. Only call submit_plan once you have enough details to create a quality plan. Work with the user to get those details. Pass either markdown text or an absolute path to a .md file.",
69062
69011
  args: {
69063
- edits: tool.schema.array(tool.schema.object({
69064
- start: tool.schema.number().describe("1-indexed start line (inclusive)"),
69065
- end: tool.schema.number().optional().describe("1-indexed end line (inclusive). Omit to replace from start through end of file."),
69066
- content: tool.schema.string().describe("Replacement content. Empty string deletes the line range.")
69067
- })).describe("Array of line-range edits to apply to the plan.")
69012
+ plan: tool.schema.string().describe("The plan \u2014 either markdown text or an absolute path to a .md file on disk.")
69068
69013
  },
69069
69014
  async execute(args, context) {
69070
69015
  const invokingAgent = context.agent;
@@ -69073,37 +69018,18 @@ Do NOT proceed with implementation until your plan is approved.`);
69073
69018
 
69074
69019
  Use /plannotator-last or /plannotator-annotate for manual review, or set workflow to all-agents to allow broader submit_plan access.`;
69075
69020
  }
69076
- if (!args.edits || args.edits.length === 0) {
69077
- return "Error: No edits provided. Pass at least one edit with start and content.";
69078
- }
69079
- const backingPath = getPlanBackingPath(ctx.directory);
69080
- const backingDir = path4.dirname(backingPath);
69081
- mkdirSync8(backingDir, { recursive: true });
69082
- let existingContent = "";
69083
- if (existsSync10(backingPath)) {
69084
- existingContent = readFileSync7(backingPath, "utf-8");
69085
- }
69086
- const existingLines = existingContent ? existingContent.split(`
69087
- `) : [];
69088
- const validationError = validateEdits(existingLines, args.edits);
69089
- if (validationError) {
69090
- return `Error: ${validationError}`;
69091
- }
69092
- let resultLines;
69021
+ let planContent;
69022
+ let sourceFilePath;
69093
69023
  try {
69094
- resultLines = applyEdits(existingLines, args.edits);
69024
+ const resolved = resolvePlanContent(args.plan);
69025
+ planContent = resolved.content;
69026
+ sourceFilePath = resolved.filePath;
69095
69027
  } catch (err) {
69096
- return `Error applying edits: ${err instanceof Error ? err.message : String(err)}`;
69097
- }
69098
- const planContent = resultLines.join(`
69099
- `);
69100
- if (planContent.length > MAX_PLAN_SIZE) {
69101
- return `Error: Plan content exceeds the maximum size of ${MAX_PLAN_SIZE / (1024 * 1024)}MB.`;
69028
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
69102
69029
  }
69103
69030
  if (!planContent.trim()) {
69104
- return "Error: Plan content is empty after applying edits.";
69031
+ return "Error: Plan content is empty. Write your plan first, then call submit_plan.";
69105
69032
  }
69106
- writeFileSync5(backingPath, planContent, "utf-8");
69107
69033
  const sharingEnabled = await getSharingEnabled();
69108
69034
  const server2 = await startPlannotatorServer({
69109
69035
  plan: planContent,
@@ -69151,7 +69077,7 @@ Use /plannotator-last or /plannotator-annotate for manual review, or set workflo
69151
69077
  }
69152
69078
  if (result.feedback) {
69153
69079
  return getPlanApprovedWithNotesPrompt("opencode", undefined, {
69154
- planFilePath: backingPath,
69080
+ planFilePath: sourceFilePath,
69155
69081
  doneMsg: result.savedPath ? `Saved to: ${result.savedPath}` : "",
69156
69082
  feedback: result.feedback,
69157
69083
  proceedSuffix: shouldSwitchAgent ? `
@@ -69160,28 +69086,15 @@ Proceed with implementation, incorporating these notes where applicable.` : ""
69160
69086
  });
69161
69087
  }
69162
69088
  return getPlanApprovedPrompt("opencode", undefined, {
69163
- planFilePath: backingPath,
69089
+ planFilePath: sourceFilePath,
69164
69090
  doneMsg: result.savedPath ? ` Saved to: ${result.savedPath}` : ""
69165
69091
  });
69166
69092
  } else {
69167
- const lineNumberedPlan = formatWithLineNumbers(planContent);
69168
- const totalLines = planContent.split(`
69169
- `).length;
69170
69093
  return getPlanDeniedPrompt("opencode", undefined, {
69171
69094
  toolName: getPlanToolName("opencode"),
69172
- planFileRule: "",
69095
+ planFileRule: buildPlanFileRule(getPlanToolName("opencode"), sourceFilePath),
69173
69096
  feedback: result.feedback || "Plan changes requested"
69174
- }) + `
69175
-
69176
- ## Current Plan (${totalLines} lines)
69177
-
69178
- The plan below shows the current state with line numbers. Use these exact line numbers in your next \`submit_plan\` call:
69179
-
69180
- \`\`\`
69181
- ${lineNumberedPlan}
69182
- \`\`\`
69183
-
69184
- Call \`submit_plan\` with targeted edits to address the feedback above.`;
69097
+ }) + "\n\nAfter making your revisions, call `submit_plan` again to resubmit for review.";
69185
69098
  }
69186
69099
  }
69187
69100
  })
@@ -69191,10 +69104,6 @@ Call \`submit_plan\` with targeted edits to address the feedback above.`;
69191
69104
  };
69192
69105
  var opencode_plugin_default = PlannotatorPlugin;
69193
69106
  export {
69194
- validateEdits,
69195
- getPlanBackingPath,
69196
- formatWithLineNumbers,
69197
69107
  opencode_plugin_default as default,
69198
- applyEdits,
69199
69108
  PlannotatorPlugin
69200
69109
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plannotator/opencode",
3
- "version": "0.19.18",
3
+ "version": "0.19.19",
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,8 +38,8 @@
38
38
  "@opencode-ai/plugin": "^1.1.10"
39
39
  },
40
40
  "devDependencies": {
41
- "@plannotator/server": "0.19.18",
42
- "@plannotator/shared": "0.0.1"
41
+ "@plannotator/server": "workspace:*",
42
+ "@plannotator/shared": "workspace:*"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "bun": ">=1.0.0"