@gmickel/gno 1.31.0 → 1.33.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 +15 -5
- package/assets/skill/SKILL.md +34 -0
- package/assets/skill/cli-reference.md +10 -2
- package/browser-extension/artifacts/{gno-browser-clipper-v1.31.0.zip → gno-browser-clipper-v1.33.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.33.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +5 -1
- package/spec/cli.md +35 -1
- 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/spec/output-schemas/publish-artifact.schema.json +76 -1
- package/src/cli/commands/publish.ts +43 -7
- 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/ingestion/strip.ts +152 -26
- package/src/mcp/tools/index.ts +18 -17
- package/src/mcp/tools/workspace-write.ts +215 -97
- package/src/publish/artifact-asset-codec.ts +75 -0
- package/src/publish/artifact-asset-contract.ts +152 -0
- package/src/publish/artifact-asset-parse.ts +401 -0
- package/src/publish/artifact-asset-sniff.ts +108 -0
- package/src/publish/artifact-asset-validate.ts +209 -0
- package/src/publish/artifact-assets.ts +58 -0
- package/src/publish/artifact-validation.ts +32 -6
- package/src/publish/artifact.ts +50 -3
- package/src/publish/attachment-bundle.ts +145 -0
- package/src/publish/attachment-discover.ts +203 -0
- package/src/publish/attachment-load.ts +133 -0
- package/src/publish/attachment-obsidian.ts +45 -0
- package/src/publish/attachment-path.ts +334 -0
- package/src/publish/attachment-raster.ts +852 -0
- package/src/publish/attachment-resolver.ts +280 -0
- package/src/publish/attachment-types.ts +54 -0
- package/src/publish/encrypted-export.ts +121 -44
- package/src/publish/export-attachments.ts +224 -0
- package/src/publish/export-service.ts +142 -80
- package/src/publish/obsidian-sanitize.ts +121 -13
- 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 +193 -105
- 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;
|
|
@@ -1257,7 +1280,7 @@ export async function handlePublishExport(
|
|
|
1257
1280
|
}
|
|
1258
1281
|
|
|
1259
1282
|
try {
|
|
1260
|
-
const { artifact, warnings } = await exportPublishArtifact({
|
|
1283
|
+
const { artifact, assetSummary, warnings } = await exportPublishArtifact({
|
|
1261
1284
|
collections: config.collections,
|
|
1262
1285
|
options: {
|
|
1263
1286
|
encryptionPassphrase: body.encryptionPassphrase,
|
|
@@ -1272,6 +1295,7 @@ export async function handlePublishExport(
|
|
|
1272
1295
|
|
|
1273
1296
|
return jsonResponse({
|
|
1274
1297
|
artifact,
|
|
1298
|
+
assetSummary,
|
|
1275
1299
|
fileName: derivePublishArtifactFilename(artifact),
|
|
1276
1300
|
uploadUrl: "https://gno.sh/studio",
|
|
1277
1301
|
warnings,
|
|
@@ -2388,7 +2412,6 @@ export async function handleRenameDoc(
|
|
|
2388
2412
|
docId: string,
|
|
2389
2413
|
req: Request,
|
|
2390
2414
|
deps?: {
|
|
2391
|
-
renameFilePath?: typeof renameFilePath;
|
|
2392
2415
|
syncCollection?: typeof defaultSyncService.syncCollection;
|
|
2393
2416
|
}
|
|
2394
2417
|
): Promise<Response> {
|
|
@@ -2409,6 +2432,11 @@ export async function handleRenameDoc(
|
|
|
2409
2432
|
return errorResponse("VALIDATION", "uri must be a string");
|
|
2410
2433
|
}
|
|
2411
2434
|
|
|
2435
|
+
const confirmation = parseRefactorApplyConfirmation(body);
|
|
2436
|
+
if ("code" in confirmation) {
|
|
2437
|
+
return fileRefactorHttpErrorResponse(confirmation);
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2412
2440
|
const docResult = await resolveDocumentReference(store, docId, body.uri);
|
|
2413
2441
|
if (!docResult.ok) {
|
|
2414
2442
|
return errorResponse("RUNTIME", docResult.error.message, 500);
|
|
@@ -2444,71 +2472,85 @@ export async function handleRenameDoc(
|
|
|
2444
2472
|
return errorResponse("FILE_NOT_FOUND", "Source file no longer exists", 404);
|
|
2445
2473
|
}
|
|
2446
2474
|
|
|
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()) {
|
|
2475
|
+
let target;
|
|
2476
|
+
try {
|
|
2477
|
+
target = resolveRenameTarget({
|
|
2478
|
+
collection: collection.name,
|
|
2479
|
+
currentRelPath: doc.relPath,
|
|
2480
|
+
nextName: body.name,
|
|
2481
|
+
});
|
|
2482
|
+
} catch (error) {
|
|
2461
2483
|
return errorResponse(
|
|
2462
|
-
"
|
|
2463
|
-
|
|
2464
|
-
409
|
|
2484
|
+
"VALIDATION",
|
|
2485
|
+
error instanceof Error ? error.message : "Invalid rename target"
|
|
2465
2486
|
);
|
|
2466
2487
|
}
|
|
2467
2488
|
|
|
2489
|
+
if (target.nextRelPath === doc.relPath) {
|
|
2490
|
+
return errorResponse("VALIDATION", "New name matches current file");
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
const nodePath = await import("node:path"); // no bun equivalent
|
|
2494
|
+
const nextFullPath = nodePath.join(collection.path, target.nextRelPath);
|
|
2495
|
+
|
|
2468
2496
|
try {
|
|
2497
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
2498
|
+
operation: "rename",
|
|
2499
|
+
doc,
|
|
2500
|
+
collection,
|
|
2501
|
+
sourceFullPath: fullPath,
|
|
2502
|
+
target,
|
|
2503
|
+
store,
|
|
2504
|
+
sourceEditable: capabilities.editable,
|
|
2505
|
+
});
|
|
2506
|
+
|
|
2469
2507
|
const syncCollection =
|
|
2470
2508
|
deps?.syncCollection ??
|
|
2471
2509
|
((collectionArg, storeArg, optionsArg) =>
|
|
2472
2510
|
defaultSyncService.syncCollection(collectionArg, storeArg, optionsArg));
|
|
2511
|
+
|
|
2473
2512
|
ctxHolder.watchService?.suppress(fullPath);
|
|
2474
2513
|
ctxHolder.watchService?.suppress(nextFullPath);
|
|
2475
|
-
|
|
2476
|
-
const
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
ctxHolder,
|
|
2514
|
+
|
|
2515
|
+
const applyResult = await applyCanonicalFileRefactor({
|
|
2516
|
+
plan,
|
|
2517
|
+
confirmation,
|
|
2518
|
+
deps: buildFileRefactorApplyDeps({
|
|
2481
2519
|
collection,
|
|
2482
2520
|
store,
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2521
|
+
syncAfterCommit: async () => {
|
|
2522
|
+
const syncResult = await syncResidentCollection(
|
|
2523
|
+
ctxHolder,
|
|
2524
|
+
collection,
|
|
2525
|
+
store,
|
|
2526
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
2527
|
+
syncCollection
|
|
2528
|
+
);
|
|
2529
|
+
assertFileRefactorSyncConverged(syncResult);
|
|
2530
|
+
},
|
|
2531
|
+
}),
|
|
2532
|
+
signal: req.signal,
|
|
2533
|
+
});
|
|
2534
|
+
|
|
2535
|
+
const mapped = mapApplyResultToHttpSuccess({
|
|
2536
|
+
plan,
|
|
2537
|
+
result: applyResult,
|
|
2538
|
+
targetFullPath: nextFullPath,
|
|
2539
|
+
operationLabel: "renamed",
|
|
2540
|
+
});
|
|
2541
|
+
if ("code" in mapped) {
|
|
2542
|
+
return fileRefactorHttpErrorResponse(mapped);
|
|
2489
2543
|
}
|
|
2544
|
+
|
|
2490
2545
|
ctxHolder.eventBus?.emit({
|
|
2491
2546
|
type: "document-changed",
|
|
2492
|
-
uri:
|
|
2547
|
+
uri: mapped.uri,
|
|
2493
2548
|
collection: collection.name,
|
|
2494
|
-
relPath:
|
|
2549
|
+
relPath: mapped.relPath,
|
|
2495
2550
|
origin: "save",
|
|
2496
2551
|
changedAt: new Date().toISOString(),
|
|
2497
2552
|
});
|
|
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
|
-
});
|
|
2553
|
+
return jsonResponse(mapped);
|
|
2512
2554
|
} catch (error) {
|
|
2513
2555
|
return errorResponse(
|
|
2514
2556
|
"RUNTIME",
|
|
@@ -2550,7 +2592,6 @@ export async function handleRefactorPlan(
|
|
|
2550
2592
|
);
|
|
2551
2593
|
}
|
|
2552
2594
|
|
|
2553
|
-
const snapshot = await getRefactorSnapshot(store, doc.id);
|
|
2554
2595
|
const collection = getCollectionByName(
|
|
2555
2596
|
ctxHolder.config.collections,
|
|
2556
2597
|
doc.collection
|
|
@@ -2568,41 +2609,69 @@ export async function handleRefactorPlan(
|
|
|
2568
2609
|
if (!body.name?.trim()) {
|
|
2569
2610
|
return errorResponse("VALIDATION", "Missing or invalid name");
|
|
2570
2611
|
}
|
|
2571
|
-
const
|
|
2612
|
+
const resolvedDocPath = await resolveAbsoluteDocPath(
|
|
2613
|
+
ctxHolder.config.collections,
|
|
2614
|
+
doc
|
|
2615
|
+
);
|
|
2616
|
+
if (!resolvedDocPath) {
|
|
2617
|
+
return errorResponse(
|
|
2618
|
+
"NOT_FOUND",
|
|
2619
|
+
`Collection not found: ${doc.collection}`,
|
|
2620
|
+
404
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2623
|
+
const target = resolveRenameTarget({
|
|
2572
2624
|
collection: collection.name,
|
|
2573
2625
|
currentRelPath: doc.relPath,
|
|
2574
2626
|
nextName: body.name.trim(),
|
|
2575
2627
|
});
|
|
2576
|
-
|
|
2577
|
-
operation:
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2628
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
2629
|
+
operation: "rename",
|
|
2630
|
+
doc,
|
|
2631
|
+
collection,
|
|
2632
|
+
sourceFullPath: resolvedDocPath.fullPath,
|
|
2633
|
+
target,
|
|
2634
|
+
store,
|
|
2635
|
+
sourceEditable: capabilities.editable,
|
|
2582
2636
|
});
|
|
2637
|
+
return jsonResponse(toRefactorPlanResponse(plan));
|
|
2583
2638
|
}
|
|
2584
2639
|
|
|
2585
2640
|
if (body.operation === "move") {
|
|
2586
2641
|
if (!body.folderPath?.trim()) {
|
|
2587
2642
|
return errorResponse("VALIDATION", "Missing or invalid folderPath");
|
|
2588
2643
|
}
|
|
2589
|
-
const
|
|
2644
|
+
const resolvedDocPath = await resolveAbsoluteDocPath(
|
|
2645
|
+
ctxHolder.config.collections,
|
|
2646
|
+
doc
|
|
2647
|
+
);
|
|
2648
|
+
if (!resolvedDocPath) {
|
|
2649
|
+
return errorResponse(
|
|
2650
|
+
"NOT_FOUND",
|
|
2651
|
+
`Collection not found: ${doc.collection}`,
|
|
2652
|
+
404
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
const target = resolveMoveTarget({
|
|
2590
2656
|
collection: collection.name,
|
|
2591
2657
|
currentRelPath: doc.relPath,
|
|
2592
2658
|
folderPath: body.folderPath.trim(),
|
|
2593
2659
|
nextName: body.name?.trim(),
|
|
2594
2660
|
});
|
|
2595
|
-
|
|
2596
|
-
operation:
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2661
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
2662
|
+
operation: "move",
|
|
2663
|
+
doc,
|
|
2664
|
+
collection,
|
|
2665
|
+
sourceFullPath: resolvedDocPath.fullPath,
|
|
2666
|
+
target,
|
|
2667
|
+
store,
|
|
2668
|
+
sourceEditable: capabilities.editable,
|
|
2602
2669
|
});
|
|
2670
|
+
return jsonResponse(toRefactorPlanResponse(plan));
|
|
2603
2671
|
}
|
|
2604
2672
|
|
|
2605
2673
|
if (body.operation === "duplicate") {
|
|
2674
|
+
const snapshot = await getRefactorSnapshot(store, doc.id);
|
|
2606
2675
|
const plan = planDuplicateRefactor({
|
|
2607
2676
|
collection: collection.name,
|
|
2608
2677
|
currentRelPath: doc.relPath,
|
|
@@ -2731,7 +2800,10 @@ export async function handleMoveDoc(
|
|
|
2731
2800
|
ctxHolder: ContextHolder,
|
|
2732
2801
|
store: SqliteAdapter,
|
|
2733
2802
|
docId: string,
|
|
2734
|
-
req: Request
|
|
2803
|
+
req: Request,
|
|
2804
|
+
deps?: {
|
|
2805
|
+
syncCollection?: typeof defaultSyncService.syncCollection;
|
|
2806
|
+
}
|
|
2735
2807
|
): Promise<Response> {
|
|
2736
2808
|
let body: MoveDocRequestBody;
|
|
2737
2809
|
try {
|
|
@@ -2750,6 +2822,11 @@ export async function handleMoveDoc(
|
|
|
2750
2822
|
return errorResponse("VALIDATION", "uri must be a string");
|
|
2751
2823
|
}
|
|
2752
2824
|
|
|
2825
|
+
const confirmation = parseRefactorApplyConfirmation(body);
|
|
2826
|
+
if ("code" in confirmation) {
|
|
2827
|
+
return fileRefactorHttpErrorResponse(confirmation);
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2753
2830
|
const docResult = await resolveDocumentReference(store, docId, body.uri);
|
|
2754
2831
|
if (!docResult.ok) {
|
|
2755
2832
|
return errorResponse("RUNTIME", docResult.error.message, 500);
|
|
@@ -2780,9 +2857,9 @@ export async function handleMoveDoc(
|
|
|
2780
2857
|
}
|
|
2781
2858
|
const { collection, fullPath } = resolvedDocPath;
|
|
2782
2859
|
|
|
2783
|
-
let
|
|
2860
|
+
let target;
|
|
2784
2861
|
try {
|
|
2785
|
-
|
|
2862
|
+
target = resolveMoveTarget({
|
|
2786
2863
|
collection: collection.name,
|
|
2787
2864
|
currentRelPath: doc.relPath,
|
|
2788
2865
|
folderPath: body.folderPath,
|
|
@@ -2796,55 +2873,66 @@ export async function handleMoveDoc(
|
|
|
2796
2873
|
}
|
|
2797
2874
|
|
|
2798
2875
|
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
|
-
}
|
|
2876
|
+
const nextFullPath = nodePath.join(collection.path, target.nextRelPath);
|
|
2807
2877
|
|
|
2808
2878
|
try {
|
|
2879
|
+
const plan = await buildCanonicalRefactorPlan({
|
|
2880
|
+
operation: "move",
|
|
2881
|
+
doc,
|
|
2882
|
+
collection,
|
|
2883
|
+
sourceFullPath: fullPath,
|
|
2884
|
+
target,
|
|
2885
|
+
store,
|
|
2886
|
+
sourceEditable: capabilities.editable,
|
|
2887
|
+
});
|
|
2888
|
+
|
|
2889
|
+
const syncCollection =
|
|
2890
|
+
deps?.syncCollection ??
|
|
2891
|
+
((collectionArg, storeArg, optionsArg) =>
|
|
2892
|
+
defaultSyncService.syncCollection(collectionArg, storeArg, optionsArg));
|
|
2893
|
+
|
|
2809
2894
|
ctxHolder.watchService?.suppress(fullPath);
|
|
2810
2895
|
ctxHolder.watchService?.suppress(nextFullPath);
|
|
2811
|
-
|
|
2812
|
-
await
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
await syncResidentCollection(
|
|
2817
|
-
ctxHolder,
|
|
2896
|
+
|
|
2897
|
+
const applyResult = await applyCanonicalFileRefactor({
|
|
2898
|
+
plan,
|
|
2899
|
+
confirmation,
|
|
2900
|
+
deps: buildFileRefactorApplyDeps({
|
|
2818
2901
|
collection,
|
|
2819
2902
|
store,
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2903
|
+
syncAfterCommit: async () => {
|
|
2904
|
+
const syncResult = await syncResidentCollection(
|
|
2905
|
+
ctxHolder,
|
|
2906
|
+
collection,
|
|
2907
|
+
store,
|
|
2908
|
+
withContentTypeRules({ runUpdateCmd: false }, ctxHolder.config),
|
|
2909
|
+
syncCollection
|
|
2910
|
+
);
|
|
2911
|
+
assertFileRefactorSyncConverged(syncResult);
|
|
2912
|
+
},
|
|
2913
|
+
}),
|
|
2914
|
+
signal: req.signal,
|
|
2915
|
+
});
|
|
2916
|
+
|
|
2917
|
+
const mapped = mapApplyResultToHttpSuccess({
|
|
2918
|
+
plan,
|
|
2919
|
+
result: applyResult,
|
|
2920
|
+
targetFullPath: nextFullPath,
|
|
2921
|
+
operationLabel: "moved",
|
|
2922
|
+
});
|
|
2923
|
+
if ("code" in mapped) {
|
|
2924
|
+
return fileRefactorHttpErrorResponse(mapped);
|
|
2825
2925
|
}
|
|
2926
|
+
|
|
2826
2927
|
ctxHolder.eventBus?.emit({
|
|
2827
2928
|
type: "document-changed",
|
|
2828
|
-
uri:
|
|
2929
|
+
uri: mapped.uri,
|
|
2829
2930
|
collection: collection.name,
|
|
2830
|
-
relPath:
|
|
2931
|
+
relPath: mapped.relPath,
|
|
2831
2932
|
origin: "save",
|
|
2832
2933
|
changedAt: new Date().toISOString(),
|
|
2833
2934
|
});
|
|
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
|
-
});
|
|
2935
|
+
return jsonResponse(mapped);
|
|
2848
2936
|
} catch (error) {
|
|
2849
2937
|
return errorResponse(
|
|
2850
2938
|
"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
|
];
|