@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 +37 -128
- package/package.json +3 -3
- 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,
|
|
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
|
-
|
|
68759
|
-
|
|
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
|
|
68779
|
-
|
|
68780
|
-
|
|
68781
|
-
if (!
|
|
68782
|
-
|
|
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
|
-
|
|
68797
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
69077
|
-
|
|
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
|
-
|
|
69024
|
+
const resolved = resolvePlanContent(args.plan);
|
|
69025
|
+
planContent = resolved.content;
|
|
69026
|
+
sourceFilePath = resolved.filePath;
|
|
69095
69027
|
} catch (err) {
|
|
69096
|
-
return `Error
|
|
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
|
|
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:
|
|
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:
|
|
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.
|
|
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": "
|
|
42
|
-
"@plannotator/shared": "
|
|
41
|
+
"@plannotator/server": "workspace:*",
|
|
42
|
+
"@plannotator/shared": "workspace:*"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"bun": ">=1.0.0"
|