@gmickel/gno 1.30.7 → 1.32.0
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/README.md +6 -5
- package/assets/skill/SKILL.md +25 -0
- package/assets/skill/mcp-reference.md +6 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.30.7.zip → gno-browser-clipper-v1.32.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.32.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +19 -0
- package/spec/db/schema.sql +55 -0
- package/spec/mcp.md +176 -11
- package/spec/output-schemas/file-refactor-apply-result.schema.json +305 -0
- package/spec/output-schemas/file-refactor-preview.schema.json +393 -0
- package/spec/output-schemas/section-target-create-result.schema.json +20 -0
- package/spec/output-schemas/section-target-resolve-result.schema.json +194 -0
- package/spec/output-schemas/section-target.schema.json +118 -0
- package/spec/output-schemas/section.schema.json +113 -0
- package/src/core/document-capabilities.ts +13 -0
- package/src/core/file-ops.ts +129 -1
- package/src/core/file-refactor-adapter.ts +329 -0
- package/src/core/file-refactor-apply-edits.ts +61 -0
- package/src/core/file-refactor-apply-fs.ts +512 -0
- package/src/core/file-refactor-apply-safety.ts +340 -0
- package/src/core/file-refactor-apply-validate.ts +401 -0
- package/src/core/file-refactor-contract.ts +486 -0
- package/src/core/file-refactor-destination.ts +123 -0
- package/src/core/file-refactor-from-snapshot.ts +148 -0
- package/src/core/file-refactor-journal-port.ts +150 -0
- package/src/core/file-refactor-journal.ts +347 -0
- package/src/core/file-refactor-paths.ts +60 -0
- package/src/core/file-refactor-plan-classify.ts +208 -0
- package/src/core/file-refactor-plan-validate.ts +169 -0
- package/src/core/file-refactor-planner-types.ts +62 -0
- package/src/core/file-refactor-planner.ts +423 -0
- package/src/core/file-refactor-resolve.ts +280 -0
- package/src/core/file-refactor-service.ts +468 -0
- package/src/core/file-refactors.ts +84 -56
- package/src/core/link-destination-parse.ts +275 -0
- package/src/core/link-inventory-markdown.ts +454 -0
- package/src/core/link-inventory-opaque.ts +244 -0
- package/src/core/link-inventory-types.ts +47 -0
- package/src/core/link-inventory.ts +182 -0
- package/src/core/link-relevance.ts +150 -0
- package/src/core/section-parse.ts +187 -0
- package/src/core/section-target-link.ts +154 -0
- package/src/core/section-target-resolve.ts +351 -0
- package/src/core/section-target-transport.ts +519 -0
- package/src/core/section-target.ts +263 -0
- package/src/core/sections.ts +60 -115
- package/src/mcp/AGENTS.md +1 -0
- package/src/mcp/CLAUDE.md +1 -0
- package/src/mcp/http-egress.ts +1 -0
- package/src/mcp/tools/index.ts +37 -17
- package/src/mcp/tools/sections.ts +512 -0
- package/src/mcp/tools/workspace-write.ts +215 -97
- package/src/sdk/client.ts +238 -116
- package/src/sdk/index.ts +12 -0
- package/src/sdk/types.ts +61 -3
- package/src/serve/file-refactor-http.ts +239 -0
- package/src/serve/public/components/RefactorImpactPreview.tsx +227 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/section-links.ts +189 -0
- package/src/serve/public/pages/DocView.tsx +395 -77
- package/src/serve/routes/api.ts +191 -104
- package/src/serve/routes/section-targets.ts +221 -0
- package/src/serve/server.ts +34 -0
- package/src/store/migrations/026-file-refactor-recovery-journal.ts +72 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +452 -0
- package/src/store/sqlite/file-refactor-journal-store.ts +275 -0
- package/src/store/types.ts +84 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.30.7.zip.sha256 +0 -1
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-neutral SDK/MCP adapter for canonical reference-safe rename/move.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces must not implement link rewrite logic — they resolve targets, call
|
|
5
|
+
* the core planner/apply service, and expose typed content-free results.
|
|
6
|
+
* Do not import from `src/serve` (REST remains a separate adapter).
|
|
7
|
+
*
|
|
8
|
+
* @module src/core/file-refactor-adapter
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// node:path join — no Bun path utils
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
import type { Collection } from "../config/types";
|
|
15
|
+
import type { DocumentRow } from "../store/types";
|
|
16
|
+
import type {
|
|
17
|
+
FileRefactorApplyResult,
|
|
18
|
+
FileRefactorOperation,
|
|
19
|
+
FileRefactorPreviewPlan,
|
|
20
|
+
FileRefactorReasonCode,
|
|
21
|
+
} from "./file-refactor-contract";
|
|
22
|
+
import type { FileRefactorSnapshotLike } from "./file-refactor-from-snapshot";
|
|
23
|
+
import type { ApplyFileRefactorDeps } from "./file-refactor-service";
|
|
24
|
+
|
|
25
|
+
import { isRefactorFingerprint } from "./file-refactor-apply-safety";
|
|
26
|
+
import {
|
|
27
|
+
FILE_REFACTOR_APPLY_CONFIRMATION,
|
|
28
|
+
FILE_REFACTOR_SCHEMA_VERSION,
|
|
29
|
+
} from "./file-refactor-contract";
|
|
30
|
+
import { planFileRefactorImpactFromSnapshot } from "./file-refactor-from-snapshot";
|
|
31
|
+
import { planMoveRefactor, planRenameRefactor } from "./file-refactor-paths";
|
|
32
|
+
import { planFileRefactorImpact } from "./file-refactor-planner";
|
|
33
|
+
import {
|
|
34
|
+
applyFileRefactor,
|
|
35
|
+
journalPortFromStore,
|
|
36
|
+
} from "./file-refactor-service";
|
|
37
|
+
|
|
38
|
+
export const FILE_REFACTOR_COLLECTION_LOCK_NAME = ".gno-refactor.lock";
|
|
39
|
+
|
|
40
|
+
export interface FileRefactorPathTarget {
|
|
41
|
+
nextRelPath: string;
|
|
42
|
+
nextUri: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BuildCanonicalRefactorPlanInput {
|
|
46
|
+
operation: FileRefactorOperation;
|
|
47
|
+
doc: Pick<
|
|
48
|
+
DocumentRow,
|
|
49
|
+
"id" | "uri" | "relPath" | "collection" | "title" | "mirrorHash"
|
|
50
|
+
>;
|
|
51
|
+
collection: Collection;
|
|
52
|
+
/** Absolute path of the live source file. */
|
|
53
|
+
sourceFullPath: string;
|
|
54
|
+
target: FileRefactorPathTarget;
|
|
55
|
+
store: Partial<{
|
|
56
|
+
getFileRefactorResolutionSnapshot: (input: {
|
|
57
|
+
sourceUri: string;
|
|
58
|
+
}) => Promise<{
|
|
59
|
+
ok: boolean;
|
|
60
|
+
value?: FileRefactorSnapshotLike;
|
|
61
|
+
error?: { message: string };
|
|
62
|
+
}>;
|
|
63
|
+
}> &
|
|
64
|
+
object;
|
|
65
|
+
sourceEditable?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ParsedRefactorApplyConfirmation {
|
|
69
|
+
planDigest: string;
|
|
70
|
+
confirmation: typeof FILE_REFACTOR_APPLY_CONFIRMATION;
|
|
71
|
+
schemaVersion: typeof FILE_REFACTOR_SCHEMA_VERSION;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function collectionRefactorLockPath(collectionRoot: string): string {
|
|
75
|
+
return join(collectionRoot, FILE_REFACTOR_COLLECTION_LOCK_NAME);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolveRenameTarget(input: {
|
|
79
|
+
collection: string;
|
|
80
|
+
currentRelPath: string;
|
|
81
|
+
nextName: string;
|
|
82
|
+
}): FileRefactorPathTarget {
|
|
83
|
+
return planRenameRefactor(input);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function resolveMoveTarget(input: {
|
|
87
|
+
collection: string;
|
|
88
|
+
currentRelPath: string;
|
|
89
|
+
folderPath: string;
|
|
90
|
+
nextName?: string;
|
|
91
|
+
}): FileRefactorPathTarget {
|
|
92
|
+
return planMoveRefactor(input);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function targetOccupiedOnDisk(
|
|
96
|
+
collectionRoot: string,
|
|
97
|
+
targetRelPath: string,
|
|
98
|
+
sourceRelPath: string
|
|
99
|
+
): Promise<boolean> {
|
|
100
|
+
if (targetRelPath === sourceRelPath) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return Bun.file(join(collectionRoot, targetRelPath)).exists();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function buildPlanFromLiveDisk(
|
|
107
|
+
input: BuildCanonicalRefactorPlanInput,
|
|
108
|
+
occupied: boolean
|
|
109
|
+
): Promise<FileRefactorPreviewPlan> {
|
|
110
|
+
const content = await Bun.file(input.sourceFullPath).text();
|
|
111
|
+
return planFileRefactorImpact({
|
|
112
|
+
operation: input.operation,
|
|
113
|
+
source: {
|
|
114
|
+
uri: input.doc.uri,
|
|
115
|
+
relPath: input.doc.relPath,
|
|
116
|
+
collection: input.doc.collection,
|
|
117
|
+
title: input.doc.title,
|
|
118
|
+
content,
|
|
119
|
+
editable: input.sourceEditable ?? true,
|
|
120
|
+
},
|
|
121
|
+
target: {
|
|
122
|
+
uri: input.target.nextUri,
|
|
123
|
+
relPath: input.target.nextRelPath,
|
|
124
|
+
collection: input.collection.name,
|
|
125
|
+
title: input.doc.title,
|
|
126
|
+
},
|
|
127
|
+
documents: [
|
|
128
|
+
{
|
|
129
|
+
id: input.doc.id,
|
|
130
|
+
uri: input.doc.uri,
|
|
131
|
+
relPath: input.doc.relPath,
|
|
132
|
+
collection: input.doc.collection,
|
|
133
|
+
title: input.doc.title,
|
|
134
|
+
active: true,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
targetOccupied: occupied,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Build the canonical preview plan for rename/move.
|
|
143
|
+
* Prefers the store resolution snapshot; falls back to live disk content
|
|
144
|
+
* when the snapshot seam is unavailable (focused unit tests / stubs).
|
|
145
|
+
*/
|
|
146
|
+
export async function buildCanonicalRefactorPlan(
|
|
147
|
+
input: BuildCanonicalRefactorPlanInput
|
|
148
|
+
): Promise<FileRefactorPreviewPlan> {
|
|
149
|
+
const occupied = await targetOccupiedOnDisk(
|
|
150
|
+
input.collection.path,
|
|
151
|
+
input.target.nextRelPath,
|
|
152
|
+
input.doc.relPath
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const snapshotFn = input.store.getFileRefactorResolutionSnapshot;
|
|
156
|
+
if (typeof snapshotFn === "function") {
|
|
157
|
+
const snapshotResult = await snapshotFn.call(input.store, {
|
|
158
|
+
sourceUri: input.doc.uri,
|
|
159
|
+
});
|
|
160
|
+
if (!snapshotResult.ok) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
snapshotResult.error?.message ?? "Failed to load refactor snapshot"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (!snapshotResult.value) {
|
|
166
|
+
throw new Error("Refactor resolution snapshot was empty");
|
|
167
|
+
}
|
|
168
|
+
return planFileRefactorImpactFromSnapshot({
|
|
169
|
+
operation: input.operation,
|
|
170
|
+
snapshot: snapshotResult.value,
|
|
171
|
+
target: {
|
|
172
|
+
uri: input.target.nextUri,
|
|
173
|
+
relPath: input.target.nextRelPath,
|
|
174
|
+
collection: input.collection.name,
|
|
175
|
+
title: input.doc.title,
|
|
176
|
+
},
|
|
177
|
+
targetOccupied: occupied,
|
|
178
|
+
sourceEditable: input.sourceEditable,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return buildPlanFromLiveDisk(input, occupied);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function storeHasJournalPort(
|
|
186
|
+
store: object
|
|
187
|
+
): store is Parameters<typeof journalPortFromStore>[0] {
|
|
188
|
+
const candidate = store as Record<string, unknown>;
|
|
189
|
+
return (
|
|
190
|
+
typeof candidate.createFileRefactorPreparedReceipt === "function" &&
|
|
191
|
+
typeof candidate.advanceFileRefactorReceipt === "function" &&
|
|
192
|
+
typeof candidate.getFileRefactorReceiptById === "function" &&
|
|
193
|
+
typeof candidate.getLatestFileRefactorReceiptByPlanDigest === "function"
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Production apply deps — durable journal only. Never falls back to memory.
|
|
199
|
+
*/
|
|
200
|
+
export function buildDurableFileRefactorApplyDeps(input: {
|
|
201
|
+
collection: Collection;
|
|
202
|
+
store: object;
|
|
203
|
+
syncAfterCommit: ApplyFileRefactorDeps["syncAfterCommit"];
|
|
204
|
+
}): ApplyFileRefactorDeps {
|
|
205
|
+
if (!storeHasJournalPort(input.store)) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"Durable file-refactor journal store is unavailable on this adapter"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
collectionRoot: input.collection.path,
|
|
212
|
+
lockPath: collectionRefactorLockPath(input.collection.path),
|
|
213
|
+
journal: journalPortFromStore(input.store),
|
|
214
|
+
syncAfterCommit: input.syncAfterCommit,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function parseRefactorApplyConfirmation(body: {
|
|
219
|
+
planDigest?: unknown;
|
|
220
|
+
confirmation?: unknown;
|
|
221
|
+
schemaVersion?: unknown;
|
|
222
|
+
}): ParsedRefactorApplyConfirmation | { error: string; message: string } {
|
|
223
|
+
if (
|
|
224
|
+
typeof body.planDigest !== "string" ||
|
|
225
|
+
!isRefactorFingerprint(body.planDigest)
|
|
226
|
+
) {
|
|
227
|
+
return {
|
|
228
|
+
error: "INVALID_INPUT",
|
|
229
|
+
message: "planDigest must be a 64-character lowercase SHA-256 digest",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (body.confirmation !== FILE_REFACTOR_APPLY_CONFIRMATION) {
|
|
233
|
+
return {
|
|
234
|
+
error: "INVALID_INPUT",
|
|
235
|
+
message: `confirmation must be "${FILE_REFACTOR_APPLY_CONFIRMATION}"`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (body.schemaVersion !== FILE_REFACTOR_SCHEMA_VERSION) {
|
|
239
|
+
return {
|
|
240
|
+
error: "INVALID_INPUT",
|
|
241
|
+
message: `schemaVersion must be "${FILE_REFACTOR_SCHEMA_VERSION}"`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
planDigest: body.planDigest,
|
|
247
|
+
confirmation: FILE_REFACTOR_APPLY_CONFIRMATION,
|
|
248
|
+
schemaVersion: FILE_REFACTOR_SCHEMA_VERSION,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* A collection sync that reports file failures is not convergence. Throwing
|
|
254
|
+
* lets the apply service preserve the durable commit as sync-pending.
|
|
255
|
+
*/
|
|
256
|
+
export function assertFileRefactorSyncConverged(result: {
|
|
257
|
+
filesErrored?: number;
|
|
258
|
+
errors?: readonly unknown[];
|
|
259
|
+
}): void {
|
|
260
|
+
if ((result.filesErrored ?? 0) > 0 || (result.errors?.length ?? 0) > 0) {
|
|
261
|
+
throw new Error("File-refactor index convergence reported sync errors");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Apply a rename/move through the canonical service.
|
|
267
|
+
* The supplied digest must match the freshly computed plan. Callers must
|
|
268
|
+
* preview first; apply never silently manufactures confirmation.
|
|
269
|
+
*/
|
|
270
|
+
export async function applyCanonicalFileRefactor(input: {
|
|
271
|
+
plan: FileRefactorPreviewPlan;
|
|
272
|
+
confirmation: ParsedRefactorApplyConfirmation;
|
|
273
|
+
deps: ApplyFileRefactorDeps;
|
|
274
|
+
signal?: AbortSignal;
|
|
275
|
+
}): Promise<FileRefactorApplyResult> {
|
|
276
|
+
const { plan, confirmation, deps, signal } = input;
|
|
277
|
+
|
|
278
|
+
if (confirmation.planDigest !== plan.planDigest) {
|
|
279
|
+
return {
|
|
280
|
+
schemaVersion: FILE_REFACTOR_SCHEMA_VERSION,
|
|
281
|
+
planDigest: plan.planDigest,
|
|
282
|
+
operation: plan.operation,
|
|
283
|
+
source: plan.source,
|
|
284
|
+
target: plan.target,
|
|
285
|
+
status: "stale_plan",
|
|
286
|
+
reasonCode: "stale_plan",
|
|
287
|
+
filesystem: { state: "unchanged" },
|
|
288
|
+
indexConvergence: { state: "not_attempted" },
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!plan.canApply) {
|
|
293
|
+
const targetAbs = join(deps.collectionRoot, plan.target.relPath);
|
|
294
|
+
const occupied =
|
|
295
|
+
plan.target.relPath !== plan.source.relPath &&
|
|
296
|
+
(await Bun.file(targetAbs).exists());
|
|
297
|
+
const reason =
|
|
298
|
+
plan.safety.blockingReasonCodes[0] ??
|
|
299
|
+
(occupied
|
|
300
|
+
? ("occupied_target" as FileRefactorReasonCode)
|
|
301
|
+
: ("capability_denied" as FileRefactorReasonCode));
|
|
302
|
+
const status =
|
|
303
|
+
reason === "occupied_target" || reason === "unsafe_target"
|
|
304
|
+
? ("conflict" as const)
|
|
305
|
+
: ("unsupported" as const);
|
|
306
|
+
return {
|
|
307
|
+
schemaVersion: FILE_REFACTOR_SCHEMA_VERSION,
|
|
308
|
+
planDigest: plan.planDigest,
|
|
309
|
+
operation: plan.operation,
|
|
310
|
+
source: plan.source,
|
|
311
|
+
target: plan.target,
|
|
312
|
+
status,
|
|
313
|
+
reasonCode: reason,
|
|
314
|
+
filesystem: { state: "unchanged" },
|
|
315
|
+
indexConvergence: { state: "not_attempted" },
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return applyFileRefactor(
|
|
320
|
+
plan,
|
|
321
|
+
{
|
|
322
|
+
schemaVersion: FILE_REFACTOR_SCHEMA_VERSION,
|
|
323
|
+
planDigest: plan.planDigest,
|
|
324
|
+
confirmation: FILE_REFACTOR_APPLY_CONFIRMATION,
|
|
325
|
+
},
|
|
326
|
+
deps,
|
|
327
|
+
signal
|
|
328
|
+
);
|
|
329
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Destination-only multi-span edits for reference-safe apply.
|
|
3
|
+
*
|
|
4
|
+
* @module src/core/file-refactor-apply-edits
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { FileRefactorDestinationSpan } from "./file-refactor-contract";
|
|
8
|
+
|
|
9
|
+
import { applyDestinationOnlyEdit } from "./file-refactor-contract";
|
|
10
|
+
|
|
11
|
+
export class FileRefactorEditError extends Error {
|
|
12
|
+
readonly code: "stale_span" | "overlapping_spans";
|
|
13
|
+
|
|
14
|
+
constructor(code: "stale_span" | "overlapping_spans", message: string) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "FileRefactorEditError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Apply destination-only edits in descending UTF-16 offsets.
|
|
23
|
+
* Verifies non-overlap against original coordinates and exact original slices.
|
|
24
|
+
*/
|
|
25
|
+
export function applyDestinationEditsDescending(
|
|
26
|
+
content: string,
|
|
27
|
+
edits: FileRefactorDestinationSpan[]
|
|
28
|
+
): string {
|
|
29
|
+
if (edits.length === 0) return content;
|
|
30
|
+
|
|
31
|
+
const ascending = [...edits].sort(
|
|
32
|
+
(left, right) => left.startOffset - right.startOffset
|
|
33
|
+
);
|
|
34
|
+
for (let index = 1; index < ascending.length; index += 1) {
|
|
35
|
+
const prev = ascending[index - 1];
|
|
36
|
+
const next = ascending[index];
|
|
37
|
+
if (!prev || !next) continue;
|
|
38
|
+
if (next.startOffset < prev.endOffset) {
|
|
39
|
+
throw new FileRefactorEditError(
|
|
40
|
+
"overlapping_spans",
|
|
41
|
+
"Destination edit spans overlap"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const descending = [...edits].sort(
|
|
47
|
+
(left, right) => right.startOffset - left.startOffset
|
|
48
|
+
);
|
|
49
|
+
let result = content;
|
|
50
|
+
for (const edit of descending) {
|
|
51
|
+
try {
|
|
52
|
+
result = applyDestinationOnlyEdit(result, edit);
|
|
53
|
+
} catch (cause) {
|
|
54
|
+
throw new FileRefactorEditError(
|
|
55
|
+
"stale_span",
|
|
56
|
+
cause instanceof Error ? cause.message : "Stale destination span"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|