@plannotator/opencode 0.19.17 → 0.19.18
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 +128 -37
- package/package.json +2 -2
- package/plannotator.html +2 -2
- package/review-editor.html +2 -2
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, readFileSync as readFileSync7 } from "fs";
|
|
45047
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
45048
45048
|
import path4 from "path";
|
|
45049
45049
|
|
|
45050
45050
|
// ../../packages/server/remote.ts
|
|
@@ -67743,13 +67743,6 @@ 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
|
-
}
|
|
67753
67746
|
var DEFAULT_REVIEW_APPROVED_PROMPT = `# Code Review
|
|
67754
67747
|
|
|
67755
67748
|
Code review completed \u2014 no changes requested.`;
|
|
@@ -68762,21 +68755,63 @@ function getReviewHtml() {
|
|
|
68762
68755
|
return _reviewHtml;
|
|
68763
68756
|
}
|
|
68764
68757
|
var DEFAULT_PLAN_TIMEOUT_SECONDS = 345600;
|
|
68765
|
-
|
|
68766
|
-
|
|
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;
|
|
68767
68777
|
}
|
|
68768
|
-
function
|
|
68769
|
-
|
|
68770
|
-
|
|
68771
|
-
if (!
|
|
68772
|
-
|
|
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
|
+
}
|
|
68773
68794
|
}
|
|
68774
|
-
return { content, filePath: plan };
|
|
68775
68795
|
}
|
|
68776
|
-
|
|
68777
|
-
|
|
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
|
+
}
|
|
68778
68806
|
}
|
|
68779
|
-
return
|
|
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
|
+
`);
|
|
68780
68815
|
}
|
|
68781
68816
|
function getPlanningPrompt() {
|
|
68782
68817
|
return `## Plannotator \u2014 Plan Review
|
|
@@ -68785,10 +68820,26 @@ You have a plan submission tool called \`submit_plan\`. It opens an interactive
|
|
|
68785
68820
|
|
|
68786
68821
|
**How to use it:**
|
|
68787
68822
|
|
|
68788
|
-
-
|
|
68789
|
-
|
|
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
|
+
\`\`\`
|
|
68790
68837
|
|
|
68791
|
-
|
|
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
|
|
68792
68843
|
|
|
68793
68844
|
### Before you write a plan
|
|
68794
68845
|
|
|
@@ -68956,9 +69007,9 @@ tools (except writing markdown files), or otherwise make changes to the system.
|
|
|
68956
69007
|
return;
|
|
68957
69008
|
output.system.push(`## Plan Submission
|
|
68958
69009
|
|
|
68959
|
-
When you have completed your plan, call the \`submit_plan\` tool to submit it for user review. Pass your plan as
|
|
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": "..." }] }\`.
|
|
68960
69011
|
|
|
68961
|
-
The user will review your plan in a visual UI where they can annotate, approve, or request changes. If rejected,
|
|
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.
|
|
68962
69013
|
|
|
68963
69014
|
Do NOT proceed with implementation until your plan is approved.`);
|
|
68964
69015
|
},
|
|
@@ -69007,9 +69058,13 @@ Do NOT proceed with implementation until your plan is approved.`);
|
|
|
69007
69058
|
if (shouldRegisterSubmitPlan(workflowOptions)) {
|
|
69008
69059
|
plugin.tool = {
|
|
69009
69060
|
submit_plan: tool({
|
|
69010
|
-
description: "
|
|
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.",
|
|
69011
69062
|
args: {
|
|
69012
|
-
|
|
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.")
|
|
69013
69068
|
},
|
|
69014
69069
|
async execute(args, context) {
|
|
69015
69070
|
const invokingAgent = context.agent;
|
|
@@ -69018,18 +69073,37 @@ Do NOT proceed with implementation until your plan is approved.`);
|
|
|
69018
69073
|
|
|
69019
69074
|
Use /plannotator-last or /plannotator-annotate for manual review, or set workflow to all-agents to allow broader submit_plan access.`;
|
|
69020
69075
|
}
|
|
69021
|
-
|
|
69022
|
-
|
|
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;
|
|
69023
69093
|
try {
|
|
69024
|
-
|
|
69025
|
-
planContent = resolved.content;
|
|
69026
|
-
sourceFilePath = resolved.filePath;
|
|
69094
|
+
resultLines = applyEdits(existingLines, args.edits);
|
|
69027
69095
|
} catch (err) {
|
|
69028
|
-
return `Error: ${err instanceof Error ? err.message : String(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.`;
|
|
69029
69102
|
}
|
|
69030
69103
|
if (!planContent.trim()) {
|
|
69031
|
-
return "Error: Plan content is empty
|
|
69104
|
+
return "Error: Plan content is empty after applying edits.";
|
|
69032
69105
|
}
|
|
69106
|
+
writeFileSync5(backingPath, planContent, "utf-8");
|
|
69033
69107
|
const sharingEnabled = await getSharingEnabled();
|
|
69034
69108
|
const server2 = await startPlannotatorServer({
|
|
69035
69109
|
plan: planContent,
|
|
@@ -69077,7 +69151,7 @@ Use /plannotator-last or /plannotator-annotate for manual review, or set workflo
|
|
|
69077
69151
|
}
|
|
69078
69152
|
if (result.feedback) {
|
|
69079
69153
|
return getPlanApprovedWithNotesPrompt("opencode", undefined, {
|
|
69080
|
-
planFilePath:
|
|
69154
|
+
planFilePath: backingPath,
|
|
69081
69155
|
doneMsg: result.savedPath ? `Saved to: ${result.savedPath}` : "",
|
|
69082
69156
|
feedback: result.feedback,
|
|
69083
69157
|
proceedSuffix: shouldSwitchAgent ? `
|
|
@@ -69086,15 +69160,28 @@ Proceed with implementation, incorporating these notes where applicable.` : ""
|
|
|
69086
69160
|
});
|
|
69087
69161
|
}
|
|
69088
69162
|
return getPlanApprovedPrompt("opencode", undefined, {
|
|
69089
|
-
planFilePath:
|
|
69163
|
+
planFilePath: backingPath,
|
|
69090
69164
|
doneMsg: result.savedPath ? ` Saved to: ${result.savedPath}` : ""
|
|
69091
69165
|
});
|
|
69092
69166
|
} else {
|
|
69167
|
+
const lineNumberedPlan = formatWithLineNumbers(planContent);
|
|
69168
|
+
const totalLines = planContent.split(`
|
|
69169
|
+
`).length;
|
|
69093
69170
|
return getPlanDeniedPrompt("opencode", undefined, {
|
|
69094
69171
|
toolName: getPlanToolName("opencode"),
|
|
69095
|
-
planFileRule:
|
|
69172
|
+
planFileRule: "",
|
|
69096
69173
|
feedback: result.feedback || "Plan changes requested"
|
|
69097
|
-
}) +
|
|
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.`;
|
|
69098
69185
|
}
|
|
69099
69186
|
}
|
|
69100
69187
|
})
|
|
@@ -69104,6 +69191,10 @@ Proceed with implementation, incorporating these notes where applicable.` : ""
|
|
|
69104
69191
|
};
|
|
69105
69192
|
var opencode_plugin_default = PlannotatorPlugin;
|
|
69106
69193
|
export {
|
|
69194
|
+
validateEdits,
|
|
69195
|
+
getPlanBackingPath,
|
|
69196
|
+
formatWithLineNumbers,
|
|
69107
69197
|
opencode_plugin_default as default,
|
|
69198
|
+
applyEdits,
|
|
69108
69199
|
PlannotatorPlugin
|
|
69109
69200
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plannotator/opencode",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.18",
|
|
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.18",
|
|
42
42
|
"@plannotator/shared": "0.0.1"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|