@gmickel/gno 1.31.0 → 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 +5 -4
- package/assets/skill/SKILL.md +23 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.31.0.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 +114 -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/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/mcp/tools/index.ts +18 -17
- package/src/mcp/tools/workspace-write.ts +215 -97
- package/src/sdk/client.ts +167 -115
- package/src/sdk/index.ts +7 -0
- package/src/sdk/types.ts +32 -2
- 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/pages/DocView.tsx +176 -41
- package/src/serve/routes/api.ts +191 -104
- 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.31.0.zip.sha256 +0 -1
package/src/serve/routes/api.ts
CHANGED
|
@@ -71,16 +71,14 @@ import {
|
|
|
71
71
|
atomicWrite,
|
|
72
72
|
copyFilePath,
|
|
73
73
|
createFolderPath,
|
|
74
|
-
renameFilePath,
|
|
75
74
|
revealFilePath,
|
|
76
75
|
trashFilePath,
|
|
77
76
|
} from "../../core/file-ops";
|
|
78
77
|
import {
|
|
78
|
+
assertFileRefactorSyncConverged,
|
|
79
79
|
buildRefactorWarnings,
|
|
80
80
|
planCreateFolder,
|
|
81
81
|
planDuplicateRefactor,
|
|
82
|
-
planMoveRefactor,
|
|
83
|
-
planRenameRefactor,
|
|
84
82
|
} from "../../core/file-refactors";
|
|
85
83
|
import {
|
|
86
84
|
hasContentMutation,
|
|
@@ -166,6 +164,17 @@ import {
|
|
|
166
164
|
resetDownloadState,
|
|
167
165
|
type ServerContext,
|
|
168
166
|
} from "../context";
|
|
167
|
+
import {
|
|
168
|
+
applyCanonicalFileRefactor,
|
|
169
|
+
buildCanonicalRefactorPlan,
|
|
170
|
+
buildFileRefactorApplyDeps,
|
|
171
|
+
mapApplyResultToHttpSuccess,
|
|
172
|
+
parseRefactorApplyConfirmation,
|
|
173
|
+
resolveMoveTarget,
|
|
174
|
+
resolveRenameTarget,
|
|
175
|
+
toRefactorPlanResponse,
|
|
176
|
+
type FileRefactorHttpError,
|
|
177
|
+
} from "../file-refactor-http";
|
|
169
178
|
import { analyzeImportPath } from "../import-preview";
|
|
170
179
|
import { getActiveJob, getJobStatus, startJob } from "../jobs";
|
|
171
180
|
import {
|
|
@@ -465,12 +474,22 @@ export interface CreateCaptureRequestBody extends PublicCaptureInput {}
|
|
|
465
474
|
export interface RenameDocRequestBody {
|
|
466
475
|
name: string;
|
|
467
476
|
uri?: string;
|
|
477
|
+
/** Exact plan digest from preview. */
|
|
478
|
+
planDigest: string;
|
|
479
|
+
/** Exact destructive confirmation token. */
|
|
480
|
+
confirmation: string;
|
|
481
|
+
schemaVersion: string;
|
|
468
482
|
}
|
|
469
483
|
|
|
470
484
|
export interface MoveDocRequestBody {
|
|
471
485
|
folderPath: string;
|
|
472
486
|
name?: string;
|
|
473
487
|
uri?: string;
|
|
488
|
+
/** Exact plan digest from preview. */
|
|
489
|
+
planDigest: string;
|
|
490
|
+
/** Exact destructive confirmation token. */
|
|
491
|
+
confirmation: string;
|
|
492
|
+
schemaVersion: string;
|
|
474
493
|
}
|
|
475
494
|
|
|
476
495
|
export interface DuplicateDocRequestBody {
|
|
@@ -546,6 +565,10 @@ function errorResponse(
|
|
|
546
565
|
);
|
|
547
566
|
}
|
|
548
567
|
|
|
568
|
+
function fileRefactorHttpErrorResponse(error: FileRefactorHttpError): Response {
|
|
569
|
+
return errorResponse(error.code, error.message, error.status, error.details);
|
|
570
|
+
}
|
|
571
|
+
|
|
549
572
|
interface RestTraceStart {
|
|
550
573
|
session: RetrievalTraceSession | null;
|
|
551
574
|
error: Response | null;
|
|
@@ -2388,7 +2411,6 @@ export async function handleRenameDoc(
|
|
|
2388
2411
|
docId: string,
|
|
2389
2412
|
req: Request,
|
|
2390
2413
|
deps?: {
|
|
2391
|
-
renameFilePath?: typeof renameFilePath;
|
|
2392
2414
|
syncCollection?: typeof defaultSyncService.syncCollection;
|
|
2393
2415
|
}
|
|
2394
2416
|
): Promise<Response> {
|
|
@@ -2409,6 +2431,11 @@ export async function handleRenameDoc(
|
|
|
2409
2431
|
return errorResponse("VALIDATION", "uri must be a string");
|
|
2410
2432
|
}
|
|
2411
2433
|
|
|
2434
|
+
const confirmation = parseRefactorApplyConfirmation(body);
|
|
2435
|
+
if ("code" in confirmation) {
|
|
2436
|
+
return fileRefactorHttpErrorResponse(confirmation);
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2412
2439
|
const docResult = await resolveDocumentReference(store, docId, body.uri);
|
|
2413
2440
|
if (!docResult.ok) {
|
|
2414
2441
|
return errorResponse("RUNTIME", docResult.error.message, 500);
|
|
@@ -2444,71 +2471,85 @@ export async function handleRenameDoc(
|
|
|
2444
2471
|
return errorResponse("FILE_NOT_FOUND", "Source file no longer exists", 404);
|
|
2445
2472
|
}
|
|
2446
2473
|
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
const nextFullPath = nodePath.join(collection.path, nextRelPath);
|
|
2456
|
-
|
|
2457
|
-
if (nextFullPath === fullPath) {
|
|
2458
|
-
return errorResponse("VALIDATION", "New name matches current file");
|
|
2459
|
-
}
|
|
2460
|
-
if (await Bun.file(nextFullPath).exists()) {
|
|
2474
|
+
let target;
|
|
2475
|
+
try {
|
|
2476
|
+
target = resolveRenameTarget({
|
|
2477
|
+
collection: collection.name,
|
|
2478
|
+
currentRelPath: doc.relPath,
|
|
2479
|
+
nextName: body.name,
|
|
2480
|
+
});
|
|
2481
|
+
} catch (error) {
|
|
2461
2482
|
return errorResponse(
|
|
2462
|
-
"
|
|
2463
|
-
|
|
2464
|
-
409
|
|
2483
|
+
"VALIDATION",
|
|
2484
|
+
error instanceof Error ? error.message : "Invalid rename target"
|
|
2465
2485
|
);
|
|
2466
2486
|
}
|
|
2467
2487
|
|
|
2488
|
+
if (target.nextRelPath === doc.relPath) {
|
|
2489
|
+
return errorResponse("VALIDATION", "New name matches current file");
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
const nodePath = await import("node:path"); // no bun equivalent
|
|
2493
|
+
const nextFullPath = nodePath.join(collection.path, target.nextRelPath);
|
|
2494
|
+
|
|
2468
2495
|
try {
|
|
2496
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
2497
|
+
operation: "rename",
|
|
2498
|
+
doc,
|
|
2499
|
+
collection,
|
|
2500
|
+
sourceFullPath: fullPath,
|
|
2501
|
+
target,
|
|
2502
|
+
store,
|
|
2503
|
+
sourceEditable: capabilities.editable,
|
|
2504
|
+
});
|
|
2505
|
+
|
|
2469
2506
|
const syncCollection =
|
|
2470
2507
|
deps?.syncCollection ??
|
|
2471
2508
|
((collectionArg, storeArg, optionsArg) =>
|
|
2472
2509
|
defaultSyncService.syncCollection(collectionArg, storeArg, optionsArg));
|
|
2510
|
+
|
|
2473
2511
|
ctxHolder.watchService?.suppress(fullPath);
|
|
2474
2512
|
ctxHolder.watchService?.suppress(nextFullPath);
|
|
2475
|
-
|
|
2476
|
-
const
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
ctxHolder,
|
|
2513
|
+
|
|
2514
|
+
const applyResult = await applyCanonicalFileRefactor({
|
|
2515
|
+
plan,
|
|
2516
|
+
confirmation,
|
|
2517
|
+
deps: buildFileRefactorApplyDeps({
|
|
2481
2518
|
collection,
|
|
2482
2519
|
store,
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2520
|
+
syncAfterCommit: async () => {
|
|
2521
|
+
const syncResult = await syncResidentCollection(
|
|
2522
|
+
ctxHolder,
|
|
2523
|
+
collection,
|
|
2524
|
+
store,
|
|
2525
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
2526
|
+
syncCollection
|
|
2527
|
+
);
|
|
2528
|
+
assertFileRefactorSyncConverged(syncResult);
|
|
2529
|
+
},
|
|
2530
|
+
}),
|
|
2531
|
+
signal: req.signal,
|
|
2532
|
+
});
|
|
2533
|
+
|
|
2534
|
+
const mapped = mapApplyResultToHttpSuccess({
|
|
2535
|
+
plan,
|
|
2536
|
+
result: applyResult,
|
|
2537
|
+
targetFullPath: nextFullPath,
|
|
2538
|
+
operationLabel: "renamed",
|
|
2539
|
+
});
|
|
2540
|
+
if ("code" in mapped) {
|
|
2541
|
+
return fileRefactorHttpErrorResponse(mapped);
|
|
2489
2542
|
}
|
|
2543
|
+
|
|
2490
2544
|
ctxHolder.eventBus?.emit({
|
|
2491
2545
|
type: "document-changed",
|
|
2492
|
-
uri:
|
|
2546
|
+
uri: mapped.uri,
|
|
2493
2547
|
collection: collection.name,
|
|
2494
|
-
relPath:
|
|
2548
|
+
relPath: mapped.relPath,
|
|
2495
2549
|
origin: "save",
|
|
2496
2550
|
changedAt: new Date().toISOString(),
|
|
2497
2551
|
});
|
|
2498
|
-
|
|
2499
|
-
await getRefactorSnapshot(store, doc.id),
|
|
2500
|
-
{
|
|
2501
|
-
filenameChanged: true,
|
|
2502
|
-
}
|
|
2503
|
-
);
|
|
2504
|
-
return jsonResponse({
|
|
2505
|
-
success: true,
|
|
2506
|
-
uri: nextUri,
|
|
2507
|
-
path: nextFullPath,
|
|
2508
|
-
relPath: nextRelPath,
|
|
2509
|
-
refactorWarnings,
|
|
2510
|
-
warning,
|
|
2511
|
-
});
|
|
2552
|
+
return jsonResponse(mapped);
|
|
2512
2553
|
} catch (error) {
|
|
2513
2554
|
return errorResponse(
|
|
2514
2555
|
"RUNTIME",
|
|
@@ -2550,7 +2591,6 @@ export async function handleRefactorPlan(
|
|
|
2550
2591
|
);
|
|
2551
2592
|
}
|
|
2552
2593
|
|
|
2553
|
-
const snapshot = await getRefactorSnapshot(store, doc.id);
|
|
2554
2594
|
const collection = getCollectionByName(
|
|
2555
2595
|
ctxHolder.config.collections,
|
|
2556
2596
|
doc.collection
|
|
@@ -2568,41 +2608,69 @@ export async function handleRefactorPlan(
|
|
|
2568
2608
|
if (!body.name?.trim()) {
|
|
2569
2609
|
return errorResponse("VALIDATION", "Missing or invalid name");
|
|
2570
2610
|
}
|
|
2571
|
-
const
|
|
2611
|
+
const resolvedDocPath = await resolveAbsoluteDocPath(
|
|
2612
|
+
ctxHolder.config.collections,
|
|
2613
|
+
doc
|
|
2614
|
+
);
|
|
2615
|
+
if (!resolvedDocPath) {
|
|
2616
|
+
return errorResponse(
|
|
2617
|
+
"NOT_FOUND",
|
|
2618
|
+
`Collection not found: ${doc.collection}`,
|
|
2619
|
+
404
|
|
2620
|
+
);
|
|
2621
|
+
}
|
|
2622
|
+
const target = resolveRenameTarget({
|
|
2572
2623
|
collection: collection.name,
|
|
2573
2624
|
currentRelPath: doc.relPath,
|
|
2574
2625
|
nextName: body.name.trim(),
|
|
2575
2626
|
});
|
|
2576
|
-
|
|
2577
|
-
operation:
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2627
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
2628
|
+
operation: "rename",
|
|
2629
|
+
doc,
|
|
2630
|
+
collection,
|
|
2631
|
+
sourceFullPath: resolvedDocPath.fullPath,
|
|
2632
|
+
target,
|
|
2633
|
+
store,
|
|
2634
|
+
sourceEditable: capabilities.editable,
|
|
2582
2635
|
});
|
|
2636
|
+
return jsonResponse(toRefactorPlanResponse(plan));
|
|
2583
2637
|
}
|
|
2584
2638
|
|
|
2585
2639
|
if (body.operation === "move") {
|
|
2586
2640
|
if (!body.folderPath?.trim()) {
|
|
2587
2641
|
return errorResponse("VALIDATION", "Missing or invalid folderPath");
|
|
2588
2642
|
}
|
|
2589
|
-
const
|
|
2643
|
+
const resolvedDocPath = await resolveAbsoluteDocPath(
|
|
2644
|
+
ctxHolder.config.collections,
|
|
2645
|
+
doc
|
|
2646
|
+
);
|
|
2647
|
+
if (!resolvedDocPath) {
|
|
2648
|
+
return errorResponse(
|
|
2649
|
+
"NOT_FOUND",
|
|
2650
|
+
`Collection not found: ${doc.collection}`,
|
|
2651
|
+
404
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
const target = resolveMoveTarget({
|
|
2590
2655
|
collection: collection.name,
|
|
2591
2656
|
currentRelPath: doc.relPath,
|
|
2592
2657
|
folderPath: body.folderPath.trim(),
|
|
2593
2658
|
nextName: body.name?.trim(),
|
|
2594
2659
|
});
|
|
2595
|
-
|
|
2596
|
-
operation:
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2660
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
2661
|
+
operation: "move",
|
|
2662
|
+
doc,
|
|
2663
|
+
collection,
|
|
2664
|
+
sourceFullPath: resolvedDocPath.fullPath,
|
|
2665
|
+
target,
|
|
2666
|
+
store,
|
|
2667
|
+
sourceEditable: capabilities.editable,
|
|
2602
2668
|
});
|
|
2669
|
+
return jsonResponse(toRefactorPlanResponse(plan));
|
|
2603
2670
|
}
|
|
2604
2671
|
|
|
2605
2672
|
if (body.operation === "duplicate") {
|
|
2673
|
+
const snapshot = await getRefactorSnapshot(store, doc.id);
|
|
2606
2674
|
const plan = planDuplicateRefactor({
|
|
2607
2675
|
collection: collection.name,
|
|
2608
2676
|
currentRelPath: doc.relPath,
|
|
@@ -2731,7 +2799,10 @@ export async function handleMoveDoc(
|
|
|
2731
2799
|
ctxHolder: ContextHolder,
|
|
2732
2800
|
store: SqliteAdapter,
|
|
2733
2801
|
docId: string,
|
|
2734
|
-
req: Request
|
|
2802
|
+
req: Request,
|
|
2803
|
+
deps?: {
|
|
2804
|
+
syncCollection?: typeof defaultSyncService.syncCollection;
|
|
2805
|
+
}
|
|
2735
2806
|
): Promise<Response> {
|
|
2736
2807
|
let body: MoveDocRequestBody;
|
|
2737
2808
|
try {
|
|
@@ -2750,6 +2821,11 @@ export async function handleMoveDoc(
|
|
|
2750
2821
|
return errorResponse("VALIDATION", "uri must be a string");
|
|
2751
2822
|
}
|
|
2752
2823
|
|
|
2824
|
+
const confirmation = parseRefactorApplyConfirmation(body);
|
|
2825
|
+
if ("code" in confirmation) {
|
|
2826
|
+
return fileRefactorHttpErrorResponse(confirmation);
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2753
2829
|
const docResult = await resolveDocumentReference(store, docId, body.uri);
|
|
2754
2830
|
if (!docResult.ok) {
|
|
2755
2831
|
return errorResponse("RUNTIME", docResult.error.message, 500);
|
|
@@ -2780,9 +2856,9 @@ export async function handleMoveDoc(
|
|
|
2780
2856
|
}
|
|
2781
2857
|
const { collection, fullPath } = resolvedDocPath;
|
|
2782
2858
|
|
|
2783
|
-
let
|
|
2859
|
+
let target;
|
|
2784
2860
|
try {
|
|
2785
|
-
|
|
2861
|
+
target = resolveMoveTarget({
|
|
2786
2862
|
collection: collection.name,
|
|
2787
2863
|
currentRelPath: doc.relPath,
|
|
2788
2864
|
folderPath: body.folderPath,
|
|
@@ -2796,55 +2872,66 @@ export async function handleMoveDoc(
|
|
|
2796
2872
|
}
|
|
2797
2873
|
|
|
2798
2874
|
const nodePath = await import("node:path"); // no bun equivalent
|
|
2799
|
-
const nextFullPath = nodePath.join(collection.path,
|
|
2800
|
-
if (await Bun.file(nextFullPath).exists()) {
|
|
2801
|
-
return errorResponse(
|
|
2802
|
-
"CONFLICT",
|
|
2803
|
-
"A file with that name already exists at the destination",
|
|
2804
|
-
409
|
|
2805
|
-
);
|
|
2806
|
-
}
|
|
2875
|
+
const nextFullPath = nodePath.join(collection.path, target.nextRelPath);
|
|
2807
2876
|
|
|
2808
2877
|
try {
|
|
2878
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
2879
|
+
operation: "move",
|
|
2880
|
+
doc,
|
|
2881
|
+
collection,
|
|
2882
|
+
sourceFullPath: fullPath,
|
|
2883
|
+
target,
|
|
2884
|
+
store,
|
|
2885
|
+
sourceEditable: capabilities.editable,
|
|
2886
|
+
});
|
|
2887
|
+
|
|
2888
|
+
const syncCollection =
|
|
2889
|
+
deps?.syncCollection ??
|
|
2890
|
+
((collectionArg, storeArg, optionsArg) =>
|
|
2891
|
+
defaultSyncService.syncCollection(collectionArg, storeArg, optionsArg));
|
|
2892
|
+
|
|
2809
2893
|
ctxHolder.watchService?.suppress(fullPath);
|
|
2810
2894
|
ctxHolder.watchService?.suppress(nextFullPath);
|
|
2811
|
-
|
|
2812
|
-
await
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
await syncResidentCollection(
|
|
2817
|
-
ctxHolder,
|
|
2895
|
+
|
|
2896
|
+
const applyResult = await applyCanonicalFileRefactor({
|
|
2897
|
+
plan,
|
|
2898
|
+
confirmation,
|
|
2899
|
+
deps: buildFileRefactorApplyDeps({
|
|
2818
2900
|
collection,
|
|
2819
2901
|
store,
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2902
|
+
syncAfterCommit: async () => {
|
|
2903
|
+
const syncResult = await syncResidentCollection(
|
|
2904
|
+
ctxHolder,
|
|
2905
|
+
collection,
|
|
2906
|
+
store,
|
|
2907
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
2908
|
+
syncCollection
|
|
2909
|
+
);
|
|
2910
|
+
assertFileRefactorSyncConverged(syncResult);
|
|
2911
|
+
},
|
|
2912
|
+
}),
|
|
2913
|
+
signal: req.signal,
|
|
2914
|
+
});
|
|
2915
|
+
|
|
2916
|
+
const mapped = mapApplyResultToHttpSuccess({
|
|
2917
|
+
plan,
|
|
2918
|
+
result: applyResult,
|
|
2919
|
+
targetFullPath: nextFullPath,
|
|
2920
|
+
operationLabel: "moved",
|
|
2921
|
+
});
|
|
2922
|
+
if ("code" in mapped) {
|
|
2923
|
+
return fileRefactorHttpErrorResponse(mapped);
|
|
2825
2924
|
}
|
|
2925
|
+
|
|
2826
2926
|
ctxHolder.eventBus?.emit({
|
|
2827
2927
|
type: "document-changed",
|
|
2828
|
-
uri:
|
|
2928
|
+
uri: mapped.uri,
|
|
2829
2929
|
collection: collection.name,
|
|
2830
|
-
relPath:
|
|
2930
|
+
relPath: mapped.relPath,
|
|
2831
2931
|
origin: "save",
|
|
2832
2932
|
changedAt: new Date().toISOString(),
|
|
2833
2933
|
});
|
|
2834
|
-
return jsonResponse(
|
|
2835
|
-
success: true,
|
|
2836
|
-
uri: plan.nextUri,
|
|
2837
|
-
path: nextFullPath,
|
|
2838
|
-
relPath: plan.nextRelPath,
|
|
2839
|
-
refactorWarnings: buildRefactorWarnings(
|
|
2840
|
-
await getRefactorSnapshot(store, doc.id),
|
|
2841
|
-
{
|
|
2842
|
-
folderChanged: true,
|
|
2843
|
-
filenameChanged: Boolean(body.name?.trim()),
|
|
2844
|
-
}
|
|
2845
|
-
),
|
|
2846
|
-
warning,
|
|
2847
|
-
});
|
|
2934
|
+
return jsonResponse(mapped);
|
|
2848
2935
|
} catch (error) {
|
|
2849
2936
|
return errorResponse(
|
|
2850
2937
|
"RUNTIME",
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration: bounded, metadata-only file-refactor recovery journal.
|
|
3
|
+
*
|
|
4
|
+
* Stores IDs, plan digests, collection/relpaths, phase/state, timestamps,
|
|
5
|
+
* and fingerprints/status only. Never stores note bodies or replacements.
|
|
6
|
+
* Create prunes oldest terminal receipts under FILE_REFACTOR_JOURNAL_MAX_RECEIPTS.
|
|
7
|
+
*
|
|
8
|
+
* @module src/store/migrations/026-file-refactor-recovery-journal
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Database } from "bun:sqlite";
|
|
12
|
+
|
|
13
|
+
import type { Migration } from "./runner";
|
|
14
|
+
|
|
15
|
+
export const migration: Migration = {
|
|
16
|
+
version: 26,
|
|
17
|
+
name: "file_refactor_recovery_journal",
|
|
18
|
+
|
|
19
|
+
up(db: Database): void {
|
|
20
|
+
db.exec(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS file_refactor_recovery_journal (
|
|
22
|
+
journal_id TEXT PRIMARY KEY,
|
|
23
|
+
plan_digest TEXT NOT NULL,
|
|
24
|
+
collection TEXT NOT NULL,
|
|
25
|
+
operation TEXT NOT NULL
|
|
26
|
+
CHECK (operation IN ('rename', 'move')),
|
|
27
|
+
source_rel_path TEXT NOT NULL,
|
|
28
|
+
target_rel_path TEXT NOT NULL,
|
|
29
|
+
phase TEXT NOT NULL
|
|
30
|
+
CHECK (phase IN (
|
|
31
|
+
'prepared', 'staging', 'committing', 'committed', 'sync_pending',
|
|
32
|
+
'converged', 'rolling_back', 'rolled_back', 'recovery_required',
|
|
33
|
+
'aborted'
|
|
34
|
+
)),
|
|
35
|
+
phase_ordinal INTEGER NOT NULL CHECK (phase_ordinal >= 0),
|
|
36
|
+
filesystem_state TEXT NOT NULL
|
|
37
|
+
CHECK (filesystem_state IN (
|
|
38
|
+
'unchanged', 'committed', 'rolled_back', 'recovery_required'
|
|
39
|
+
)),
|
|
40
|
+
index_state TEXT NOT NULL
|
|
41
|
+
CHECK (index_state IN (
|
|
42
|
+
'not_attempted', 'pending', 'converged', 'skipped'
|
|
43
|
+
)),
|
|
44
|
+
file_entries_json TEXT NOT NULL
|
|
45
|
+
CHECK (length(CAST(file_entries_json AS BLOB)) <= 65536),
|
|
46
|
+
created_at_ms INTEGER NOT NULL CHECK (created_at_ms >= 0),
|
|
47
|
+
updated_at_ms INTEGER NOT NULL CHECK (updated_at_ms >= 0),
|
|
48
|
+
CHECK (length(CAST(journal_id AS BLOB)) BETWEEN 1 AND 128),
|
|
49
|
+
CHECK (
|
|
50
|
+
length(plan_digest) = 64
|
|
51
|
+
AND plan_digest NOT GLOB '*[^0-9a-f]*'
|
|
52
|
+
),
|
|
53
|
+
CHECK (length(CAST(collection AS BLOB)) BETWEEN 1 AND 256),
|
|
54
|
+
CHECK (length(CAST(source_rel_path AS BLOB)) BETWEEN 1 AND 4096),
|
|
55
|
+
CHECK (length(CAST(target_rel_path AS BLOB)) BETWEEN 1 AND 4096),
|
|
56
|
+
CHECK (updated_at_ms >= created_at_ms)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_file_refactor_journal_plan_digest
|
|
60
|
+
ON file_refactor_recovery_journal(plan_digest, updated_at_ms DESC, journal_id DESC);
|
|
61
|
+
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_file_refactor_journal_collection
|
|
63
|
+
ON file_refactor_recovery_journal(collection, updated_at_ms DESC);
|
|
64
|
+
`);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
down(db: Database): void {
|
|
68
|
+
db.exec("DROP INDEX IF EXISTS idx_file_refactor_journal_collection");
|
|
69
|
+
db.exec("DROP INDEX IF EXISTS idx_file_refactor_journal_plan_digest");
|
|
70
|
+
db.exec("DROP TABLE IF EXISTS file_refactor_recovery_journal");
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -39,6 +39,7 @@ import { migration as m022 } from "./022-record-export-lineage";
|
|
|
39
39
|
import { migration as m023 } from "./023-collection-egress-policy";
|
|
40
40
|
import { migration as m024 } from "./024-egress-derived-lineage";
|
|
41
41
|
import { migration as m025 } from "./025-collection-egress-policy-revision";
|
|
42
|
+
import { migration as m026 } from "./026-file-refactor-recovery-journal";
|
|
42
43
|
|
|
43
44
|
/** All migrations in order */
|
|
44
45
|
export const migrations = [
|
|
@@ -67,4 +68,5 @@ export const migrations = [
|
|
|
67
68
|
m023,
|
|
68
69
|
m024,
|
|
69
70
|
m025,
|
|
71
|
+
m026,
|
|
70
72
|
];
|