@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.
Files changed (71) hide show
  1. package/README.md +6 -5
  2. package/assets/skill/SKILL.md +25 -0
  3. package/assets/skill/mcp-reference.md +6 -0
  4. package/browser-extension/artifacts/{gno-browser-clipper-v1.30.7.zip → gno-browser-clipper-v1.32.0.zip} +0 -0
  5. package/browser-extension/artifacts/gno-browser-clipper-v1.32.0.zip.sha256 +1 -0
  6. package/browser-extension/dist/manifest.json +1 -1
  7. package/package.json +1 -1
  8. package/spec/cli.md +19 -0
  9. package/spec/db/schema.sql +55 -0
  10. package/spec/mcp.md +176 -11
  11. package/spec/output-schemas/file-refactor-apply-result.schema.json +305 -0
  12. package/spec/output-schemas/file-refactor-preview.schema.json +393 -0
  13. package/spec/output-schemas/section-target-create-result.schema.json +20 -0
  14. package/spec/output-schemas/section-target-resolve-result.schema.json +194 -0
  15. package/spec/output-schemas/section-target.schema.json +118 -0
  16. package/spec/output-schemas/section.schema.json +113 -0
  17. package/src/core/document-capabilities.ts +13 -0
  18. package/src/core/file-ops.ts +129 -1
  19. package/src/core/file-refactor-adapter.ts +329 -0
  20. package/src/core/file-refactor-apply-edits.ts +61 -0
  21. package/src/core/file-refactor-apply-fs.ts +512 -0
  22. package/src/core/file-refactor-apply-safety.ts +340 -0
  23. package/src/core/file-refactor-apply-validate.ts +401 -0
  24. package/src/core/file-refactor-contract.ts +486 -0
  25. package/src/core/file-refactor-destination.ts +123 -0
  26. package/src/core/file-refactor-from-snapshot.ts +148 -0
  27. package/src/core/file-refactor-journal-port.ts +150 -0
  28. package/src/core/file-refactor-journal.ts +347 -0
  29. package/src/core/file-refactor-paths.ts +60 -0
  30. package/src/core/file-refactor-plan-classify.ts +208 -0
  31. package/src/core/file-refactor-plan-validate.ts +169 -0
  32. package/src/core/file-refactor-planner-types.ts +62 -0
  33. package/src/core/file-refactor-planner.ts +423 -0
  34. package/src/core/file-refactor-resolve.ts +280 -0
  35. package/src/core/file-refactor-service.ts +468 -0
  36. package/src/core/file-refactors.ts +84 -56
  37. package/src/core/link-destination-parse.ts +275 -0
  38. package/src/core/link-inventory-markdown.ts +454 -0
  39. package/src/core/link-inventory-opaque.ts +244 -0
  40. package/src/core/link-inventory-types.ts +47 -0
  41. package/src/core/link-inventory.ts +182 -0
  42. package/src/core/link-relevance.ts +150 -0
  43. package/src/core/section-parse.ts +187 -0
  44. package/src/core/section-target-link.ts +154 -0
  45. package/src/core/section-target-resolve.ts +351 -0
  46. package/src/core/section-target-transport.ts +519 -0
  47. package/src/core/section-target.ts +263 -0
  48. package/src/core/sections.ts +60 -115
  49. package/src/mcp/AGENTS.md +1 -0
  50. package/src/mcp/CLAUDE.md +1 -0
  51. package/src/mcp/http-egress.ts +1 -0
  52. package/src/mcp/tools/index.ts +37 -17
  53. package/src/mcp/tools/sections.ts +512 -0
  54. package/src/mcp/tools/workspace-write.ts +215 -97
  55. package/src/sdk/client.ts +238 -116
  56. package/src/sdk/index.ts +12 -0
  57. package/src/sdk/types.ts +61 -3
  58. package/src/serve/file-refactor-http.ts +239 -0
  59. package/src/serve/public/components/RefactorImpactPreview.tsx +227 -0
  60. package/src/serve/public/globals.built.css +1 -1
  61. package/src/serve/public/lib/section-links.ts +189 -0
  62. package/src/serve/public/pages/DocView.tsx +395 -77
  63. package/src/serve/routes/api.ts +191 -104
  64. package/src/serve/routes/section-targets.ts +221 -0
  65. package/src/serve/server.ts +34 -0
  66. package/src/store/migrations/026-file-refactor-recovery-journal.ts +72 -0
  67. package/src/store/migrations/index.ts +2 -0
  68. package/src/store/sqlite/adapter.ts +452 -0
  69. package/src/store/sqlite/file-refactor-journal-store.ts +275 -0
  70. package/src/store/types.ts +84 -0
  71. package/browser-extension/artifacts/gno-browser-clipper-v1.30.7.zip.sha256 +0 -1
@@ -0,0 +1,468 @@
1
+ /**
2
+ * Transport-neutral collection-scoped apply service for reference-safe refactors.
3
+ *
4
+ * Validates an exact preview plan, stages/commits or restores the full file set,
5
+ * records a content-free recovery receipt, then drives index convergence.
6
+ *
7
+ * @module src/core/file-refactor-service
8
+ */
9
+
10
+ import type { WriteLockHandle } from "./file-lock";
11
+ import type {
12
+ FileRefactorApplyRequest,
13
+ FileRefactorApplyResult,
14
+ FileRefactorPreviewPlan,
15
+ FileRefactorReasonCode,
16
+ } from "./file-refactor-contract";
17
+ import type {
18
+ FileRefactorJournalFileEntry,
19
+ FileRefactorJournalPort,
20
+ FileRefactorRecoveryReceipt,
21
+ } from "./file-refactor-journal";
22
+
23
+ import { acquireWriteLock } from "./file-lock";
24
+ import { removePathRequired } from "./file-ops";
25
+ import {
26
+ assignRefactorArtifactPaths,
27
+ cleanupAfterSuccessfulCommit,
28
+ cleanupReceiptArtifacts,
29
+ commitRefactorFiles,
30
+ rollbackRefactorFiles,
31
+ stageRefactorFiles,
32
+ type FileRefactorBoundaryHook,
33
+ type PreparedRefactorFile,
34
+ type StagedRefactorFile,
35
+ } from "./file-refactor-apply-fs";
36
+ import {
37
+ validateRefactorJournalId,
38
+ type RemoveOwnedEmptyDirsDeps,
39
+ } from "./file-refactor-apply-safety";
40
+ import {
41
+ baseResult,
42
+ unchangedResult,
43
+ validateLiveRefactorPlan,
44
+ validateRefactorPlanStructure,
45
+ } from "./file-refactor-apply-validate";
46
+ import {
47
+ computeFileRefactorPlanDigest,
48
+ FILE_REFACTOR_APPLY_CONFIRMATION,
49
+ FILE_REFACTOR_SCHEMA_VERSION,
50
+ } from "./file-refactor-contract";
51
+ import {
52
+ FILE_REFACTOR_SYNC_PENDING_INSTRUCTION,
53
+ isCommittedFilesystemPhase,
54
+ isUncertainJournalPhase,
55
+ } from "./file-refactor-journal";
56
+
57
+ export type { FileRefactorBoundaryHook } from "./file-refactor-apply-fs";
58
+ export {
59
+ createMemoryFileRefactorJournal,
60
+ journalPortFromStore,
61
+ } from "./file-refactor-journal-port";
62
+
63
+ export type FileRefactorSyncCallback = (input: {
64
+ plan: FileRefactorPreviewPlan;
65
+ receipt: FileRefactorRecoveryReceipt;
66
+ signal?: AbortSignal;
67
+ }) => Promise<void>;
68
+
69
+ export interface ApplyFileRefactorDeps {
70
+ collectionRoot: string;
71
+ lockPath: string;
72
+ journal: FileRefactorJournalPort;
73
+ syncAfterCommit: FileRefactorSyncCallback;
74
+ acquireLock?: typeof acquireWriteLock;
75
+ lockTimeoutMs?: number;
76
+ onBoundary?: FileRefactorBoundaryHook;
77
+ nowMs?: () => number;
78
+ createJournalId?: () => string;
79
+ /** Injectable required removal for race/failure injection tests. */
80
+ removePathRequired?: typeof removePathRequired;
81
+ /** Injectable rmdir for owned-directory cleanup failure seams. */
82
+ rmdir?: RemoveOwnedEmptyDirsDeps["rmdir"];
83
+ }
84
+
85
+ function receiptFileEntries(
86
+ files: PreparedRefactorFile[]
87
+ ): FileRefactorJournalFileEntry[] {
88
+ return files.map((file) => ({
89
+ role: file.role === "source" ? "source" : "affected",
90
+ relPath:
91
+ file.role === "source"
92
+ ? (file.sourceRelPath ?? file.relPath)
93
+ : file.relPath,
94
+ stageRelPath: file.stageRelPath,
95
+ backupRelPath: file.backupRelPath,
96
+ originalFingerprint: file.originalFingerprint,
97
+ expectedFingerprint: file.expectedFingerprint,
98
+ status: "pending" as const,
99
+ }));
100
+ }
101
+
102
+ function updateEntriesByRelPath(
103
+ entries: FileRefactorJournalFileEntry[],
104
+ relPath: string,
105
+ status: FileRefactorJournalFileEntry["status"],
106
+ prepared: PreparedRefactorFile[]
107
+ ): FileRefactorJournalFileEntry[] {
108
+ const match = prepared.find(
109
+ (file) => file.relPath === relPath || file.sourceRelPath === relPath
110
+ );
111
+ const keys = new Set<string>([relPath]);
112
+ if (match?.sourceRelPath) keys.add(match.sourceRelPath);
113
+ if (match) keys.add(match.relPath);
114
+ return entries.map((entry) =>
115
+ keys.has(entry.relPath) ? { ...entry, status } : entry
116
+ );
117
+ }
118
+
119
+ async function runSyncOnly(
120
+ plan: FileRefactorPreviewPlan,
121
+ receipt: FileRefactorRecoveryReceipt,
122
+ deps: ApplyFileRefactorDeps,
123
+ signal?: AbortSignal
124
+ ): Promise<FileRefactorApplyResult> {
125
+ const stamp = (): number =>
126
+ Math.max((deps.nowMs ?? Date.now)(), receipt.updatedAtMs + 1);
127
+
128
+ const syncPending = (): FileRefactorApplyResult => ({
129
+ ...baseResult(plan),
130
+ status: "applied_with_sync_pending",
131
+ reasonCode: "sync_pending",
132
+ filesystem: {
133
+ state: "committed",
134
+ recoveryJournalId: receipt.journalId,
135
+ },
136
+ indexConvergence: {
137
+ state: "pending",
138
+ recoveryInstruction: FILE_REFACTOR_SYNC_PENDING_INSTRUCTION,
139
+ },
140
+ });
141
+
142
+ try {
143
+ await deps.syncAfterCommit({ plan, receipt, signal });
144
+ try {
145
+ const converged = await deps.journal.advanceReceipt(receipt.journalId, {
146
+ phase: "converged",
147
+ filesystemState: "committed",
148
+ indexState: "converged",
149
+ updatedAtMs: stamp(),
150
+ });
151
+ return {
152
+ ...baseResult(plan),
153
+ status: "applied",
154
+ filesystem: {
155
+ state: "committed",
156
+ recoveryJournalId: converged.journalId,
157
+ },
158
+ indexConvergence: { state: "converged" },
159
+ };
160
+ } catch {
161
+ return syncPending();
162
+ }
163
+ } catch {
164
+ try {
165
+ await deps.journal.advanceReceipt(receipt.journalId, {
166
+ phase: "sync_pending",
167
+ filesystemState: "committed",
168
+ indexState: "pending",
169
+ updatedAtMs: stamp(),
170
+ });
171
+ } catch {
172
+ /* preserve mutation-free sync-pending result even if journal write fails */
173
+ }
174
+ return syncPending();
175
+ }
176
+ }
177
+
178
+ function recoveryRequiredResult(
179
+ plan: FileRefactorPreviewPlan,
180
+ journalId: string
181
+ ): FileRefactorApplyResult {
182
+ return {
183
+ ...baseResult(plan),
184
+ status: "failed_rolled_back",
185
+ reasonCode: "rollback_recovery_required",
186
+ filesystem: { state: "recovery_required", recoveryJournalId: journalId },
187
+ indexConvergence: { state: "not_attempted" },
188
+ };
189
+ }
190
+
191
+ function validateApplyRequest(
192
+ plan: FileRefactorPreviewPlan,
193
+ request: FileRefactorApplyRequest
194
+ ): FileRefactorApplyResult | null {
195
+ if (request.schemaVersion !== FILE_REFACTOR_SCHEMA_VERSION) {
196
+ return unchangedResult(plan, "unsupported", "unsafe_target");
197
+ }
198
+ if (request.confirmation !== FILE_REFACTOR_APPLY_CONFIRMATION) {
199
+ return unchangedResult(plan, "unsupported", "capability_denied");
200
+ }
201
+ if (request.planDigest !== plan.planDigest) {
202
+ return unchangedResult(plan, "stale_plan", "stale_plan");
203
+ }
204
+ return null;
205
+ }
206
+
207
+ async function validatePlanIntegrity(
208
+ plan: FileRefactorPreviewPlan
209
+ ): Promise<FileRefactorApplyResult | null> {
210
+ const structural = validateRefactorPlanStructure(plan);
211
+ if (structural) return structural;
212
+
213
+ const { planDigest: _ignored, ...material } = plan;
214
+ const recomputed = await computeFileRefactorPlanDigest(material);
215
+ if (recomputed !== plan.planDigest) {
216
+ return unchangedResult(plan, "stale_plan", "stale_plan");
217
+ }
218
+ if (!plan.canApply) {
219
+ const reason =
220
+ plan.safety.blockingReasonCodes[0] ??
221
+ ("capability_denied" as FileRefactorReasonCode);
222
+ return unchangedResult(plan, "unsupported", reason);
223
+ }
224
+ return null;
225
+ }
226
+
227
+ /**
228
+ * Apply an already-produced preview plan with an exact apply request.
229
+ * Never throws for ordinary expected conflicts after partial work.
230
+ */
231
+ export async function applyFileRefactor(
232
+ plan: FileRefactorPreviewPlan,
233
+ request: FileRefactorApplyRequest,
234
+ deps: ApplyFileRefactorDeps,
235
+ signal?: AbortSignal
236
+ ): Promise<FileRefactorApplyResult> {
237
+ const acquire = deps.acquireLock ?? acquireWriteLock;
238
+ const lock = await acquire(deps.lockPath, deps.lockTimeoutMs ?? 5000);
239
+ if (!lock) {
240
+ return unchangedResult(plan, "conflict", "unsafe_target");
241
+ }
242
+
243
+ try {
244
+ return await applyUnderLock(plan, request, deps, lock, signal);
245
+ } finally {
246
+ await lock.release();
247
+ }
248
+ }
249
+
250
+ async function applyUnderLock(
251
+ plan: FileRefactorPreviewPlan,
252
+ request: FileRefactorApplyRequest,
253
+ deps: ApplyFileRefactorDeps,
254
+ _lock: WriteLockHandle,
255
+ signal?: AbortSignal
256
+ ): Promise<FileRefactorApplyResult> {
257
+ const nowMs = deps.nowMs ?? Date.now;
258
+
259
+ const requestError = validateApplyRequest(plan, request);
260
+ if (requestError) return requestError;
261
+
262
+ const integrityError = await validatePlanIntegrity(plan);
263
+ if (integrityError) return integrityError;
264
+
265
+ if (signal?.aborted) {
266
+ return unchangedResult(plan, "conflict", "unsafe_target");
267
+ }
268
+
269
+ const existing = await deps.journal.getLatestReceiptByPlanDigest(
270
+ plan.planDigest
271
+ );
272
+ if (existing) {
273
+ if (isUncertainJournalPhase(existing.phase)) {
274
+ return recoveryRequiredResult(plan, existing.journalId);
275
+ }
276
+ if (isCommittedFilesystemPhase(existing.phase)) {
277
+ if (existing.phase === "converged") {
278
+ return {
279
+ ...baseResult(plan),
280
+ status: "applied",
281
+ filesystem: {
282
+ state: "committed",
283
+ recoveryJournalId: existing.journalId,
284
+ },
285
+ indexConvergence: { state: "converged" },
286
+ };
287
+ }
288
+ return runSyncOnly(plan, existing, deps, signal);
289
+ }
290
+ if (existing.phase === "aborted" || existing.phase === "rolled_back") {
291
+ const cleaned = await cleanupReceiptArtifacts(
292
+ deps.collectionRoot,
293
+ existing.fileEntries.map((entry) => entry.stageRelPath),
294
+ existing.fileEntries.map((entry) => entry.backupRelPath),
295
+ deps.removePathRequired ?? removePathRequired
296
+ );
297
+ if (!cleaned) {
298
+ return recoveryRequiredResult(plan, existing.journalId);
299
+ }
300
+ // Fall through to a new clean attempt after artifact cleanup.
301
+ }
302
+ }
303
+
304
+ const validated = await validateLiveRefactorPlan(plan, deps.collectionRoot);
305
+ if (!validated.ok) return validated.result;
306
+
307
+ if (signal?.aborted) {
308
+ return unchangedResult(plan, "conflict", "unsafe_target");
309
+ }
310
+
311
+ const journalIdRaw = (deps.createJournalId ?? (() => crypto.randomUUID()))();
312
+ let journalId: string;
313
+ try {
314
+ journalId = validateRefactorJournalId(journalIdRaw);
315
+ } catch {
316
+ return unchangedResult(plan, "unsupported", "unsafe_target");
317
+ }
318
+ const prepared = assignRefactorArtifactPaths(
319
+ validated.files,
320
+ deps.collectionRoot,
321
+ journalId
322
+ );
323
+ let fileEntries = receiptFileEntries(prepared);
324
+
325
+ await deps.journal.createPreparedReceipt({
326
+ journalId,
327
+ planDigest: plan.planDigest,
328
+ collection: plan.source.collection,
329
+ operation: plan.operation,
330
+ sourceRelPath: plan.source.relPath,
331
+ targetRelPath: plan.target.relPath,
332
+ fileEntries,
333
+ createdAtMs: nowMs(),
334
+ });
335
+
336
+ let staged: StagedRefactorFile[] = [];
337
+ let commitStarted = false;
338
+ let activePhase: "staging" | "committing" | "rolling_back" = "staging";
339
+ let clock = nowMs();
340
+ const nextTs = (): number => {
341
+ clock += 1;
342
+ return clock;
343
+ };
344
+
345
+ const fsHooks = {
346
+ onBoundary: deps.onBoundary,
347
+ removePathRequired: deps.removePathRequired,
348
+ collectionRoot: deps.collectionRoot,
349
+ rmdir: deps.rmdir,
350
+ onFileProgress: async (update: {
351
+ relPath: string;
352
+ status: FileRefactorJournalFileEntry["status"];
353
+ }) => {
354
+ fileEntries = updateEntriesByRelPath(
355
+ fileEntries,
356
+ update.relPath,
357
+ update.status,
358
+ prepared
359
+ );
360
+ if (activePhase === "rolling_back") return;
361
+ await deps.journal.advanceReceipt(journalId, {
362
+ phase: activePhase,
363
+ fileEntries,
364
+ updatedAtMs: nextTs(),
365
+ });
366
+ },
367
+ };
368
+
369
+ try {
370
+ await deps.journal.advanceReceipt(journalId, {
371
+ phase: "staging",
372
+ updatedAtMs: nextTs(),
373
+ });
374
+ staged = await stageRefactorFiles(prepared, fsHooks, staged);
375
+
376
+ if (deps.onBoundary) {
377
+ await deps.onBoundary({ kind: "before_commit" });
378
+ }
379
+ if (signal?.aborted) {
380
+ await rollbackRefactorFiles(staged, false, fsHooks);
381
+ await deps.journal.advanceReceipt(journalId, {
382
+ phase: "aborted",
383
+ filesystemState: "unchanged",
384
+ indexState: "not_attempted",
385
+ updatedAtMs: nextTs(),
386
+ });
387
+ return unchangedResult(plan, "conflict", "unsafe_target", journalId);
388
+ }
389
+
390
+ activePhase = "committing";
391
+ await deps.journal.advanceReceipt(journalId, {
392
+ phase: "committing",
393
+ updatedAtMs: nextTs(),
394
+ });
395
+ commitStarted = true;
396
+ await commitRefactorFiles(staged, fsHooks);
397
+
398
+ fileEntries = fileEntries.map((entry) => ({
399
+ ...entry,
400
+ status: "committed" as const,
401
+ }));
402
+ await deps.journal.advanceReceipt(journalId, {
403
+ phase: "committed",
404
+ filesystemState: "committed",
405
+ indexState: "not_attempted",
406
+ fileEntries,
407
+ updatedAtMs: nextTs(),
408
+ });
409
+ await cleanupAfterSuccessfulCommit(staged);
410
+
411
+ const committedReceipt = await deps.journal.getReceiptById(journalId);
412
+ if (!committedReceipt) {
413
+ return recoveryRequiredResult(plan, journalId);
414
+ }
415
+ return runSyncOnly(plan, committedReceipt, deps, signal);
416
+ } catch {
417
+ let verified = false;
418
+ try {
419
+ activePhase = "rolling_back";
420
+ try {
421
+ await deps.journal.advanceReceipt(journalId, {
422
+ phase: "rolling_back",
423
+ updatedAtMs: nextTs(),
424
+ });
425
+ } catch {
426
+ /* continue rollback even if journal advance fails */
427
+ }
428
+ verified = (await rollbackRefactorFiles(staged, commitStarted, fsHooks))
429
+ .verified;
430
+ } catch {
431
+ verified = false;
432
+ }
433
+
434
+ if (verified) {
435
+ try {
436
+ await deps.journal.advanceReceipt(journalId, {
437
+ phase: "rolled_back",
438
+ filesystemState: "rolled_back",
439
+ indexState: "not_attempted",
440
+ fileEntries,
441
+ updatedAtMs: nextTs(),
442
+ });
443
+ return {
444
+ ...baseResult(plan),
445
+ status: "failed_rolled_back",
446
+ reasonCode: "filesystem_commit_failed",
447
+ filesystem: { state: "rolled_back", recoveryJournalId: journalId },
448
+ indexConvergence: { state: "not_attempted" },
449
+ };
450
+ } catch {
451
+ return recoveryRequiredResult(plan, journalId);
452
+ }
453
+ }
454
+
455
+ try {
456
+ await deps.journal.advanceReceipt(journalId, {
457
+ phase: "recovery_required",
458
+ filesystemState: "recovery_required",
459
+ indexState: "not_attempted",
460
+ fileEntries,
461
+ updatedAtMs: nextTs(),
462
+ });
463
+ } catch {
464
+ /* still return recovery_required with known journal id */
465
+ }
466
+ return recoveryRequiredResult(plan, journalId);
467
+ }
468
+ }
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Shared file refactor planning helpers.
2
+ * Shared file refactor planning helpers and reference-safe contract exports.
3
3
  *
4
- * Browser-safe path planning, with warning generation based on known link data.
4
+ * Browser-safe path planning and warning generation based on known link data.
5
+ * Versioned preview/apply contracts live in `file-refactor-contract.ts` and are
6
+ * re-exported here for a stable import path.
5
7
  *
6
8
  * @module src/core/file-refactors
7
9
  */
@@ -11,6 +13,86 @@ import { posix as pathPosix } from "node:path";
11
13
 
12
14
  import { validateRelPath } from "./validation";
13
15
 
16
+ export {
17
+ applyDestinationOnlyEdit,
18
+ compareExaminedReferences,
19
+ compareUtf16CodeUnits,
20
+ computeFileRefactorPlanDigest,
21
+ deriveCanApply,
22
+ FILE_REFACTOR_APPLY_CONFIRMATION,
23
+ FILE_REFACTOR_MUTATION_BOUNDARY,
24
+ FILE_REFACTOR_REASON_CODES,
25
+ FILE_REFACTOR_SCHEMA_VERSION,
26
+ fingerprintUtf8Content,
27
+ isBytePreservedOutsideSpan,
28
+ isContentPreservedOutsideSpan,
29
+ sortExaminedReferences,
30
+ stableStringify,
31
+ summarizeReferenceClassifications,
32
+ type FileRefactorAffectedDocument,
33
+ type FileRefactorApplyRequest,
34
+ type FileRefactorApplyResult,
35
+ type FileRefactorApplyStatus,
36
+ type FileRefactorConflictPolicy,
37
+ type FileRefactorDestinationSpan,
38
+ type FileRefactorDocumentRef,
39
+ type FileRefactorExaminedReference,
40
+ type FileRefactorFilesystemState,
41
+ type FileRefactorIndexConvergenceState,
42
+ type FileRefactorMutationBoundary,
43
+ type FileRefactorOperation,
44
+ type FileRefactorPreconditions,
45
+ type FileRefactorPreviewPlan,
46
+ type FileRefactorReasonCode,
47
+ type FileRefactorReferenceClassification,
48
+ type FileRefactorReferenceKind,
49
+ type FileRefactorSafetySummary,
50
+ } from "./file-refactor-contract";
51
+
52
+ export {
53
+ FILE_REFACTOR_PLANNER_CAPS,
54
+ planFileRefactorImpact,
55
+ type FileRefactorPlannerDocument,
56
+ type PlanFileRefactorImpactInput,
57
+ } from "./file-refactor-planner";
58
+
59
+ export {
60
+ planFileRefactorImpactFromSnapshot,
61
+ planInputFromResolutionSnapshot,
62
+ type FileRefactorSnapshotLike,
63
+ } from "./file-refactor-from-snapshot";
64
+
65
+ export {
66
+ applyFileRefactor,
67
+ createMemoryFileRefactorJournal,
68
+ journalPortFromStore,
69
+ type ApplyFileRefactorDeps,
70
+ type FileRefactorBoundaryHook,
71
+ type FileRefactorSyncCallback,
72
+ } from "./file-refactor-service";
73
+
74
+ export {
75
+ applyCanonicalFileRefactor,
76
+ assertFileRefactorSyncConverged,
77
+ buildCanonicalRefactorPlan,
78
+ buildDurableFileRefactorApplyDeps,
79
+ collectionRefactorLockPath,
80
+ FILE_REFACTOR_COLLECTION_LOCK_NAME,
81
+ parseRefactorApplyConfirmation,
82
+ resolveMoveTarget,
83
+ resolveRenameTarget,
84
+ type BuildCanonicalRefactorPlanInput,
85
+ type FileRefactorPathTarget,
86
+ type ParsedRefactorApplyConfirmation,
87
+ } from "./file-refactor-adapter";
88
+
89
+ export {
90
+ planMoveRefactor,
91
+ planRenameRefactor,
92
+ type MovePlan,
93
+ type RenamePlan,
94
+ } from "./file-refactor-paths";
95
+
14
96
  export interface RefactorWarningSummary {
15
97
  warnings: string[];
16
98
  backlinkCount: number;
@@ -24,16 +106,6 @@ export interface RefactorLinkSnapshot {
24
106
  markdownLinks: number;
25
107
  }
26
108
 
27
- export interface RenamePlan {
28
- nextRelPath: string;
29
- nextUri: string;
30
- }
31
-
32
- export interface MovePlan {
33
- nextRelPath: string;
34
- nextUri: string;
35
- }
36
-
37
109
  export interface DuplicatePlan {
38
110
  nextRelPath: string;
39
111
  nextUri: string;
@@ -91,50 +163,6 @@ function nextAvailableRelPath(relPath: string, existing: Set<string>): string {
91
163
  }
92
164
  }
93
165
 
94
- export function planRenameRefactor(input: {
95
- collection: string;
96
- currentRelPath: string;
97
- nextName: string;
98
- }): RenamePlan {
99
- const current = validateRelPath(input.currentRelPath);
100
- const directory = pathPosix.dirname(current);
101
- const currentExt = pathPosix.extname(current);
102
- const nextFilename = pathPosix.extname(input.nextName)
103
- ? input.nextName
104
- : `${input.nextName}${currentExt}`;
105
- const nextRelPath =
106
- directory === "."
107
- ? validateRelPath(nextFilename)
108
- : validateRelPath(`${directory}/${nextFilename}`);
109
-
110
- return {
111
- nextRelPath,
112
- nextUri: `gno://${input.collection}/${nextRelPath}`,
113
- };
114
- }
115
-
116
- export function planMoveRefactor(input: {
117
- collection: string;
118
- currentRelPath: string;
119
- folderPath: string;
120
- nextName?: string;
121
- }): MovePlan {
122
- const current = validateRelPath(input.currentRelPath);
123
- const safeFolder = validateRelPath(input.folderPath).replace(
124
- /^\.\/|\/+$/g,
125
- ""
126
- );
127
- const filename = input.nextName?.trim() || pathPosix.basename(current);
128
- const nextRelPath = safeFolder
129
- ? validateRelPath(`${safeFolder}/${filename}`)
130
- : validateRelPath(filename);
131
-
132
- return {
133
- nextRelPath,
134
- nextUri: `gno://${input.collection}/${nextRelPath}`,
135
- };
136
- }
137
-
138
166
  export function planDuplicateRefactor(input: {
139
167
  collection: string;
140
168
  currentRelPath: string;