MCPcopy Create free account
hub / github.com/backnotprop/plannotator / validateEdits

Function validateEdits

apps/opencode-plugin/plan-edits.ts:55–91  ·  view source on GitHub ↗
(existingLines: string[], edits: PlanEdit[])

Source from the content-addressed store, hash-verified

53 * Returns an error string if invalid, or null if all edits are acceptable.
54 */
55export function validateEdits(existingLines: string[], edits: PlanEdit[]): string | null {
56 const lineCount = existingLines.length;
57
58 for (const edit of edits) {
59 if (!Number.isInteger(edit.start) || edit.start < 1) {
60 return `start must be a positive integer >= 1, got ${edit.start}`;
61 }
62 if (edit.start > lineCount + 1) {
63 return `start (${edit.start}) exceeds file length + 1 (${lineCount + 1})`;
64 }
65 if (edit.end != null) {
66 if (!Number.isInteger(edit.end) || edit.end < edit.start) {
67 return `end (${edit.end}) must be >= start (${edit.start})`;
68 }
69 // On an empty file (lineCount === 0) every edit is a pure insert;
70 // end is semantically meaningless and applyEdits handles it via splice
71 // clamping. Rejecting here breaks first-call payloads where the agent
72 // or framework includes end (see #742).
73 if (edit.end > lineCount && lineCount > 0) {
74 return `end (${edit.end}) exceeds file length (${lineCount})`;
75 }
76 }
77 }
78
79 const sorted = [...edits].sort((a, b) => a.start - b.start);
80 for (let i = 1; i < sorted.length; i++) {
81 const prev = sorted[i - 1];
82 const curr = sorted[i];
83 if (prev.start > lineCount) continue;
84 const prevEnd = prev.end ?? lineCount;
85 if (curr.start <= prevEnd) {
86 return `edits overlap: [${prev.start},${prevEnd}] and [${curr.start},${curr.end ?? "end"}]`;
87 }
88 }
89
90 return null;
91}
92
93/**
94 * Format the plan content with line numbers for the agent's reference.

Callers 2

executeFunction · 0.90

Calls

no outgoing calls

Tested by

no test coverage detected