You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(submit-plan): replace dual-mode with edit-based interface (#730)
* feat(submit-plan): replace text/file-path mode with edit-based interface
Switches the OpenCode submit_plan tool from a dual-mode interface
(inline text or file path) to an edit-based one. The plugin now owns a
backing file at .opencode/plans/_active-plan.md; the agent never reads
or writes it directly. On denial, the response includes the current plan
with line numbers so the agent can apply surgical edits instead of
resubmitting the entire document, reducing token waste on iterative
revisions.
- Add applyEdits, validateEdits, formatWithLineNumbers, and
getPlanBackingPath helpers to the plugin
- Validate edit ranges (bounds, overlap, size limit) before mutating the
backing file
- Return line-numbered plan in denial responses to anchor targeted edits
- Remove getPlanDirectory, validatePlanPath, and file-path auto-
detection from plan-mode.ts
- Replace plan-mode.test.ts path-validation coverage with submit-
plan.test.ts for the edit engine
- Update custom-feedback.md and opencode.md docs for edit-based
semantics
Refs #365
* chore(opencode): drop unused buildPlanFileRule import
Removed in PR #730 deny path along with the only call site, but the
import was left behind.
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Copy file name to clipboardExpand all lines: apps/marketing/src/content/docs/guides/custom-feedback.md
+5-2Lines changed: 5 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -67,8 +67,8 @@ Templates use `{{variable}}` placeholders. Here's what each one contains:
67
67
|----------|-------------|
68
68
|`{{feedback}}`| Your annotations, exported as structured text. This is the main content. |
69
69
|`{{toolName}}`| The tool the agent needs to call to resubmit (`ExitPlanMode`, `submit_plan`, etc.). Varies by runtime. |
70
-
|`{{planFileRule}}`| A conditional line telling the agent where the plan file is saved and how to edit it. Empty string when there's no file path. |
71
-
|`{{planFilePath}}`| Path to the planfile being reviewed. |
70
+
|`{{planFileRule}}`| A conditional line about the plan file location. In edit-based mode (OpenCode), this is always empty since the plugin manages the backing file internally. |
71
+
|`{{planFilePath}}`| Path to the plan's backing file. In edit-based mode (OpenCode), this points to the plugin-managed backing file. |
72
72
|`{{doneMsg}}`| Optional checklist instruction or save-path info, depending on the runtime. |
73
73
|`{{fileHeader}}`| Either `"File"` or `"Folder"`, depending on what was annotated. |
74
74
|`{{filePath}}`| Path to the annotated file or folder. |
@@ -137,6 +137,7 @@ Here's a config that customizes several messages at once:
137
137
If you don't set any `prompts` config, everything works the same as it always has. The built-in defaults are the exact messages Plannotator has always sent. Here are the key ones for reference:
138
138
139
139
**Plan denied (default):**
140
+
140
141
```
141
142
YOUR PLAN WAS NOT APPROVED.
142
143
@@ -152,12 +153,14 @@ Rules:
152
153
```
153
154
154
155
**Plan approved (default, Pi runtime):**
156
+
155
157
```
156
158
Plan approved. You now have full tool access (read, bash, edit,
157
159
write). Execute the plan in {{planFilePath}}. {{doneMsg}}
The tool auto-detects whether you passed text or a file path. Both open the same review UI.
246
+
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:
- \`start\` and \`end\` are 1-indexed, inclusive line numbers
257
+
- Omit \`end\` to replace from \`start\` through end of file (use this for the initial full write)
258
+
- Empty \`content\` with \`start\`/\`end\` deletes those lines
259
+
- Multiple edits in one call are applied in order; line numbers refer to the state before edits
175
260
176
261
### Before you write a plan
177
262
@@ -376,9 +461,9 @@ tools (except writing markdown files), or otherwise make changes to the system.
376
461
377
462
output.system.push(`## Plan Submission
378
463
379
-
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.
464
+
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": "..." }] }\`.
380
465
381
-
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.
466
+
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.
382
467
383
468
Do NOT proceed with implementation until your plan is approved.`);
384
469
},
@@ -449,11 +534,17 @@ Do NOT proceed with implementation until your plan is approved.`);
449
534
plugin.tool={
450
535
submit_plan: tool({
451
536
description:
452
-
"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.",
537
+
"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.",
453
538
args: {
454
-
plan: tool.schema
455
-
.string()
456
-
.describe("The plan — either markdown text or an absolute path to a .md file on disk."),
539
+
edits: tool.schema
540
+
.array(
541
+
tool.schema.object({
542
+
start: tool.schema.number().describe("1-indexed start line (inclusive)"),
543
+
end: tool.schema.number().optional().describe("1-indexed end line (inclusive). Omit to replace from start through end of file."),
544
+
content: tool.schema.string().describe("Replacement content. Empty string deletes the line range."),
545
+
}),
546
+
)
547
+
.describe("Array of line-range edits to apply to the plan."),
457
548
},
458
549
459
550
asyncexecute(args,context){
@@ -464,21 +555,46 @@ Do NOT proceed with implementation until your plan is approved.`);
464
555
Use /plannotator-last or /plannotator-annotate for manual review, or set workflow to all-agents to allow broader submit_plan access.`;
465
556
}
466
557
467
-
// Auto-detect: file path or plan text
468
-
letplanContent: string;
469
-
letsourceFilePath: string|undefined;
558
+
if(!args.edits||args.edits.length===0){
559
+
return"Error: No edits provided. Pass at least one edit with start and content.";
560
+
}
561
+
562
+
// Read existing backing file (empty on first call)
})+"\n\nAfter making your revisions, call`submit_plan` again to resubmit for review.";
680
+
})+`\n\n## Current Plan (${totalLines} lines)\n\nThe plan below shows the current state with line numbers. Use these exact line numbers in your next \`submit_plan\` call:\n\n\`\`\`\n${lineNumberedPlan}\n\`\`\`\n\nCall \`submit_plan\` with targeted edits to address the feedback above.`;
0 commit comments