@hanna84/mcp-writing 3.15.1 → 3.16.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.
@@ -0,0 +1,764 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ export const FILESYSTEM_ARTIFACT_CLASSES = Object.freeze({
6
+ AUTHORED_PROSE: "authored_prose",
7
+ METADATA_FILE: "metadata_file",
8
+ SIDECAR: "sidecar",
9
+ GENERATED_EXPORT: "generated_export",
10
+ STYLEGUIDE_CONFIG: "styleguide_config",
11
+ STYLEGUIDE_SKILL: "styleguide_skill",
12
+ AI_BOOT_FILE: "ai_boot_file",
13
+ IMPORT_SOURCE: "import_source",
14
+ IMPORT_DESTINATION: "import_destination",
15
+ SCRIVENER_RELOCATION: "scrivener_relocation",
16
+ RUNTIME_TEMP: "runtime_temp",
17
+ SUPPORT_SCRIPT: "support_script",
18
+ SYNC_ROOT_PROBE: "sync_root_probe",
19
+ WORLD_ENTITY: "world_entity",
20
+ });
21
+
22
+ const SYNC_ROOT_ARTIFACT_CLASSES = new Set([
23
+ FILESYSTEM_ARTIFACT_CLASSES.AUTHORED_PROSE,
24
+ FILESYSTEM_ARTIFACT_CLASSES.METADATA_FILE,
25
+ FILESYSTEM_ARTIFACT_CLASSES.SIDECAR,
26
+ FILESYSTEM_ARTIFACT_CLASSES.GENERATED_EXPORT,
27
+ FILESYSTEM_ARTIFACT_CLASSES.STYLEGUIDE_CONFIG,
28
+ FILESYSTEM_ARTIFACT_CLASSES.STYLEGUIDE_SKILL,
29
+ FILESYSTEM_ARTIFACT_CLASSES.AI_BOOT_FILE,
30
+ FILESYSTEM_ARTIFACT_CLASSES.IMPORT_DESTINATION,
31
+ FILESYSTEM_ARTIFACT_CLASSES.SCRIVENER_RELOCATION,
32
+ FILESYSTEM_ARTIFACT_CLASSES.SYNC_ROOT_PROBE,
33
+ FILESYSTEM_ARTIFACT_CLASSES.WORLD_ENTITY,
34
+ ]);
35
+
36
+ export function createCoreValidationError(code, message, details) {
37
+ const error = new Error(message);
38
+ error.name = "CoreValidationError";
39
+ error.code = code;
40
+ error.details = details;
41
+ return error;
42
+ }
43
+
44
+ function isOutsideBoundary(boundaryRoot, candidatePath) {
45
+ const relative = path.relative(boundaryRoot, candidatePath);
46
+ return relative.startsWith("..") || path.isAbsolute(relative);
47
+ }
48
+
49
+ function normalizeBoundaryRoot(boundaryRoot, boundaryRootReal = boundaryRoot) {
50
+ if (!boundaryRoot) {
51
+ throw new TypeError("boundaryRoot is required.");
52
+ }
53
+ return {
54
+ boundaryRootAbs: path.resolve(boundaryRoot),
55
+ boundaryRootReal: path.resolve(boundaryRootReal),
56
+ };
57
+ }
58
+
59
+ export function resolveBoundaryRootReal(boundaryRoot) {
60
+ const boundaryRootAbs = path.resolve(boundaryRoot);
61
+ try {
62
+ return fs.realpathSync.native(boundaryRootAbs);
63
+ } catch {
64
+ let existingAncestor = boundaryRootAbs;
65
+ while (!fs.existsSync(existingAncestor)) {
66
+ const parentDir = path.dirname(existingAncestor);
67
+ if (parentDir === existingAncestor) return boundaryRootAbs;
68
+ existingAncestor = parentDir;
69
+ }
70
+ try {
71
+ const realExistingAncestor = fs.realpathSync.native(existingAncestor);
72
+ return path.resolve(realExistingAncestor, path.relative(existingAncestor, boundaryRootAbs));
73
+ } catch {
74
+ return boundaryRootAbs;
75
+ }
76
+ }
77
+ }
78
+
79
+ export function resolveCandidateInsideBoundary(candidatePath, {
80
+ boundaryRoot,
81
+ boundaryRootReal = boundaryRoot,
82
+ errorCode = "INVALID_PATH",
83
+ errorMessage = "Path must be inside the configured boundary.",
84
+ details = {},
85
+ } = {}) {
86
+ const normalizedBoundary = normalizeBoundaryRoot(boundaryRoot, boundaryRootReal);
87
+
88
+ const resolvedCandidate = path.resolve(candidatePath);
89
+ let existingAncestor = resolvedCandidate;
90
+
91
+ while (!fs.existsSync(existingAncestor)) {
92
+ const parentDir = path.dirname(existingAncestor);
93
+ if (parentDir === existingAncestor) {
94
+ throw createCoreValidationError(errorCode, errorMessage, {
95
+ ...details,
96
+ path: resolvedCandidate,
97
+ boundary_root: normalizedBoundary.boundaryRootAbs,
98
+ });
99
+ }
100
+ existingAncestor = parentDir;
101
+ }
102
+
103
+ let realExistingAncestor;
104
+ try {
105
+ realExistingAncestor = fs.realpathSync.native(existingAncestor);
106
+ } catch (err) {
107
+ throw createCoreValidationError(
108
+ errorCode,
109
+ "Path ancestor could not be resolved: path may be inaccessible.",
110
+ {
111
+ ...details,
112
+ path: candidatePath,
113
+ existing_ancestor: existingAncestor,
114
+ cause: err instanceof Error ? err.message : String(err),
115
+ }
116
+ );
117
+ }
118
+
119
+ const relativeFromAncestor = path.relative(existingAncestor, resolvedCandidate);
120
+ const resolvedPath = path.resolve(realExistingAncestor, relativeFromAncestor);
121
+ const relativeToBoundary = path.relative(normalizedBoundary.boundaryRootReal, resolvedPath);
122
+
123
+ if (isOutsideBoundary(normalizedBoundary.boundaryRootReal, resolvedPath)) {
124
+ throw createCoreValidationError(errorCode, errorMessage, {
125
+ ...details,
126
+ path: resolvedPath,
127
+ boundary_root: normalizedBoundary.boundaryRootAbs,
128
+ });
129
+ }
130
+
131
+ return { resolvedPath, relativeToBoundary };
132
+ }
133
+
134
+ export function resolveExistingPathInsideBoundary(candidatePath, {
135
+ boundaryRoot,
136
+ boundaryRootReal = boundaryRoot,
137
+ errorCode = "INVALID_PATH",
138
+ errorMessage = "Path must be inside the configured boundary.",
139
+ details = {},
140
+ } = {}) {
141
+ const normalizedBoundary = normalizeBoundaryRoot(boundaryRoot, boundaryRootReal);
142
+ const resolvedCandidate = path.resolve(candidatePath);
143
+
144
+ let resolvedPath;
145
+ try {
146
+ resolvedPath = fs.realpathSync.native(resolvedCandidate);
147
+ } catch (err) {
148
+ throw createCoreValidationError(
149
+ errorCode,
150
+ "Path could not be resolved: path may not exist or may be inaccessible.",
151
+ {
152
+ ...details,
153
+ path: resolvedCandidate,
154
+ boundary_root: normalizedBoundary.boundaryRootAbs,
155
+ cause: err instanceof Error ? err.message : String(err),
156
+ }
157
+ );
158
+ }
159
+
160
+ const relativeToBoundary = path.relative(normalizedBoundary.boundaryRootReal, resolvedPath);
161
+ if (isOutsideBoundary(normalizedBoundary.boundaryRootReal, resolvedPath)) {
162
+ throw createCoreValidationError(errorCode, errorMessage, {
163
+ ...details,
164
+ path: resolvedPath,
165
+ boundary_root: normalizedBoundary.boundaryRootAbs,
166
+ });
167
+ }
168
+
169
+ return { resolvedPath, relativeToBoundary };
170
+ }
171
+
172
+ export function resolveArtifactPathInsideSyncRoot(candidatePath, {
173
+ syncDirAbs,
174
+ syncDirReal = syncDirAbs,
175
+ artifactClass,
176
+ requireExisting = false,
177
+ errorCode = "INVALID_SYNC_PATH",
178
+ errorMessage = "Path must be inside WRITING_SYNC_DIR.",
179
+ details = {},
180
+ } = {}) {
181
+ if (!SYNC_ROOT_ARTIFACT_CLASSES.has(artifactClass)) {
182
+ throw new TypeError(`Unsupported sync-root artifact class: ${artifactClass}`);
183
+ }
184
+
185
+ const resolver = requireExisting ? resolveExistingPathInsideBoundary : resolveCandidateInsideBoundary;
186
+ return resolver(candidatePath, {
187
+ boundaryRoot: syncDirAbs,
188
+ boundaryRootReal: syncDirReal,
189
+ errorCode,
190
+ errorMessage,
191
+ details: {
192
+ ...details,
193
+ artifact_class: artifactClass,
194
+ sync_dir: syncDirAbs,
195
+ },
196
+ });
197
+ }
198
+
199
+ export function resolveGeneratedOutputDirWithinSync(outputDir, {
200
+ syncDirAbs,
201
+ syncDirReal,
202
+ } = {}) {
203
+ const { resolvedPath, relativeToBoundary } = resolveCandidateInsideBoundary(outputDir, {
204
+ boundaryRoot: syncDirAbs,
205
+ boundaryRootReal: syncDirReal,
206
+ errorCode: "INVALID_OUTPUT_DIR",
207
+ errorMessage: "output_dir must be inside WRITING_SYNC_DIR.",
208
+ details: { output_dir: path.resolve(outputDir), sync_dir: syncDirAbs },
209
+ });
210
+
211
+ return {
212
+ resolvedOutputDir: resolvedPath,
213
+ relativeToSyncDir: relativeToBoundary,
214
+ };
215
+ }
216
+
217
+ export function assertImportSourcePath(importSourcePath, {
218
+ errorCode = "INVALID_IMPORT_SOURCE",
219
+ details = {},
220
+ } = {}) {
221
+ const resolvedPath = path.resolve(importSourcePath);
222
+ let realPath;
223
+ let stat;
224
+ try {
225
+ realPath = fs.realpathSync.native(resolvedPath);
226
+ stat = fs.statSync(realPath);
227
+ } catch (err) {
228
+ throw createCoreValidationError(
229
+ errorCode,
230
+ "Import source path could not be resolved.",
231
+ {
232
+ ...details,
233
+ path: resolvedPath,
234
+ artifact_class: FILESYSTEM_ARTIFACT_CLASSES.IMPORT_SOURCE,
235
+ cause: err instanceof Error ? err.message : String(err),
236
+ }
237
+ );
238
+ }
239
+
240
+ if (!stat.isFile() && !stat.isDirectory()) {
241
+ throw createCoreValidationError(
242
+ errorCode,
243
+ `Import source path must be a file or directory: ${resolvedPath}`,
244
+ {
245
+ ...details,
246
+ path: resolvedPath,
247
+ artifact_class: FILESYSTEM_ARTIFACT_CLASSES.IMPORT_SOURCE,
248
+ }
249
+ );
250
+ }
251
+
252
+ return { resolvedPath: realPath };
253
+ }
254
+
255
+ export function ensureDirectoryInsideBoundary(dirPath, {
256
+ errorCode = "INVALID_OUTPUT_DIR",
257
+ label = "directory",
258
+ } = {}) {
259
+ if (fs.existsSync(dirPath)) {
260
+ const stat = fs.lstatSync(dirPath);
261
+ if (stat.isSymbolicLink()) {
262
+ throw createCoreValidationError(
263
+ errorCode,
264
+ `${label} exists but is a symlink: ${dirPath}`,
265
+ { path: dirPath }
266
+ );
267
+ }
268
+ if (!stat.isDirectory()) {
269
+ throw createCoreValidationError(
270
+ errorCode,
271
+ `${label} exists but is not a directory: ${dirPath}`,
272
+ { path: dirPath }
273
+ );
274
+ }
275
+ } else {
276
+ fs.mkdirSync(dirPath, { recursive: true });
277
+ }
278
+
279
+ try {
280
+ fs.accessSync(dirPath, fs.constants.W_OK);
281
+ } catch {
282
+ throw createCoreValidationError(
283
+ errorCode,
284
+ `${label} is not writable: ${dirPath}`,
285
+ { path: dirPath }
286
+ );
287
+ }
288
+ }
289
+
290
+ export function ensureDirectoryForBoundaryPath(filePath, {
291
+ boundaryRoot,
292
+ boundaryRootReal = boundaryRoot,
293
+ errorCode = "INVALID_PATH",
294
+ label = "directory",
295
+ } = {}) {
296
+ const dirPath = path.dirname(filePath);
297
+ resolveCandidateInsideBoundary(dirPath, {
298
+ boundaryRoot,
299
+ boundaryRootReal,
300
+ errorCode,
301
+ errorMessage: `${label} must be inside the configured boundary.`,
302
+ });
303
+ ensureDirectoryInsideBoundary(dirPath, { errorCode, label });
304
+ return dirPath;
305
+ }
306
+
307
+ export function ensureDirectoryInsideSyncRoot(dirPath, {
308
+ syncDirAbs,
309
+ syncDirReal = syncDirAbs,
310
+ artifactClass,
311
+ errorCode = "INVALID_SYNC_PATH",
312
+ } = {}) {
313
+ if (!SYNC_ROOT_ARTIFACT_CLASSES.has(artifactClass)) {
314
+ throw new TypeError(`Unsupported sync-root artifact class: ${artifactClass}`);
315
+ }
316
+
317
+ const { resolvedPath } = resolveArtifactPathInsideSyncRoot(dirPath, {
318
+ syncDirAbs,
319
+ syncDirReal,
320
+ artifactClass,
321
+ errorCode,
322
+ });
323
+ ensureDirectoryInsideBoundary(resolvedPath, {
324
+ errorCode,
325
+ label: "target directory",
326
+ });
327
+ return resolvedPath;
328
+ }
329
+
330
+ export function resolveGeneratedOutputPath(outputDir, fileName, {
331
+ errorCode = "INVALID_OUTPUT_PATH",
332
+ } = {}) {
333
+ const normalizedOutputDir = path.resolve(outputDir);
334
+ const targetPath = path.resolve(normalizedOutputDir, fileName);
335
+
336
+ if (isOutsideBoundary(normalizedOutputDir, targetPath)) {
337
+ throw createCoreValidationError(
338
+ errorCode,
339
+ `Output file '${fileName}' resolves outside output_dir.`,
340
+ { output_dir: normalizedOutputDir, file_name: fileName }
341
+ );
342
+ }
343
+
344
+ return targetPath;
345
+ }
346
+
347
+ export function assertRegularFileReadTarget(filePath, {
348
+ errorCode = "INVALID_PATH",
349
+ } = {}) {
350
+ try {
351
+ const stat = fs.lstatSync(filePath);
352
+ if (stat.isSymbolicLink()) {
353
+ throw createCoreValidationError(
354
+ errorCode,
355
+ `Refusing to read: target path is a symlink: ${filePath}`,
356
+ { path: filePath }
357
+ );
358
+ }
359
+ if (!stat.isFile()) {
360
+ throw createCoreValidationError(
361
+ errorCode,
362
+ `Refusing to read: target path exists but is not a regular file: ${filePath}`,
363
+ { path: filePath }
364
+ );
365
+ }
366
+ } catch (error) {
367
+ if (error?.name === "CoreValidationError") throw error;
368
+ throw error;
369
+ }
370
+ }
371
+
372
+ export function assertRegularFileWriteTarget(filePath, {
373
+ errorCode = "INVALID_OUTPUT_PATH",
374
+ } = {}) {
375
+ try {
376
+ const stat = fs.lstatSync(filePath);
377
+ if (stat.isSymbolicLink()) {
378
+ throw createCoreValidationError(
379
+ errorCode,
380
+ `Refusing to write: target path is a symlink: ${filePath}`,
381
+ { path: filePath }
382
+ );
383
+ }
384
+ if (!stat.isFile()) {
385
+ throw createCoreValidationError(
386
+ errorCode,
387
+ `Refusing to write: target path exists but is not a regular file: ${filePath}`,
388
+ { path: filePath }
389
+ );
390
+ }
391
+ } catch (error) {
392
+ if (error?.name === "CoreValidationError") throw error;
393
+ if (error?.code !== "ENOENT") throw error;
394
+ }
395
+ }
396
+
397
+ export function writeFileInsideBoundary(filePath, data, {
398
+ boundaryRoot,
399
+ boundaryRootReal = boundaryRoot,
400
+ artifactClass = FILESYSTEM_ARTIFACT_CLASSES.GENERATED_EXPORT,
401
+ encoding,
402
+ errorCode = "INVALID_PATH",
403
+ } = {}) {
404
+ const { resolvedPath } = resolveCandidateInsideBoundary(filePath, {
405
+ boundaryRoot,
406
+ boundaryRootReal,
407
+ errorCode,
408
+ errorMessage: "Write target must be inside the configured boundary.",
409
+ details: { artifact_class: artifactClass },
410
+ });
411
+ ensureDirectoryForBoundaryPath(resolvedPath, {
412
+ boundaryRoot,
413
+ boundaryRootReal,
414
+ errorCode,
415
+ label: "target parent directory",
416
+ });
417
+ assertRegularFileWriteTarget(path.resolve(filePath), { errorCode });
418
+ assertRegularFileWriteTarget(resolvedPath, { errorCode });
419
+ if (encoding) {
420
+ fs.writeFileSync(resolvedPath, data, encoding);
421
+ } else {
422
+ fs.writeFileSync(resolvedPath, data);
423
+ }
424
+ }
425
+
426
+ export function writeTextInsideSyncRoot(filePath, data, {
427
+ syncDirAbs,
428
+ syncDirReal = syncDirAbs,
429
+ artifactClass,
430
+ encoding = "utf8",
431
+ errorCode = "INVALID_SYNC_PATH",
432
+ } = {}) {
433
+ if (!SYNC_ROOT_ARTIFACT_CLASSES.has(artifactClass)) {
434
+ throw new TypeError(`Unsupported sync-root artifact class: ${artifactClass}`);
435
+ }
436
+ writeFileInsideBoundary(filePath, data, {
437
+ boundaryRoot: syncDirAbs,
438
+ boundaryRootReal: syncDirReal,
439
+ artifactClass,
440
+ encoding,
441
+ errorCode,
442
+ });
443
+ }
444
+
445
+ export function writeGeneratedOutputFile(filePath, data, {
446
+ encoding,
447
+ errorCode = "INVALID_OUTPUT_PATH",
448
+ } = {}) {
449
+ assertRegularFileWriteTarget(filePath, { errorCode });
450
+ if (encoding) {
451
+ fs.writeFileSync(filePath, data, encoding);
452
+ } else {
453
+ fs.writeFileSync(filePath, data);
454
+ }
455
+ }
456
+
457
+ export function copyFileInsideBoundary(sourcePath, targetPath, {
458
+ sourceBoundaryRoot,
459
+ sourceBoundaryRootReal = sourceBoundaryRoot,
460
+ targetBoundaryRoot = sourceBoundaryRoot,
461
+ targetBoundaryRootReal,
462
+ artifactClass = FILESYSTEM_ARTIFACT_CLASSES.GENERATED_EXPORT,
463
+ errorCode = "INVALID_PATH",
464
+ } = {}) {
465
+ const effectiveTargetBoundaryRootReal = targetBoundaryRootReal ?? (
466
+ targetBoundaryRoot === sourceBoundaryRoot ? sourceBoundaryRootReal : targetBoundaryRoot
467
+ );
468
+
469
+ assertRegularFileReadTarget(path.resolve(sourcePath), { errorCode });
470
+ const source = resolveExistingPathInsideBoundary(sourcePath, {
471
+ boundaryRoot: sourceBoundaryRoot,
472
+ boundaryRootReal: sourceBoundaryRootReal,
473
+ errorCode,
474
+ errorMessage: "Copy source must be inside the configured boundary.",
475
+ details: { artifact_class: artifactClass },
476
+ });
477
+ assertRegularFileReadTarget(source.resolvedPath, { errorCode });
478
+
479
+ const target = resolveCandidateInsideBoundary(targetPath, {
480
+ boundaryRoot: targetBoundaryRoot,
481
+ boundaryRootReal: effectiveTargetBoundaryRootReal,
482
+ errorCode,
483
+ errorMessage: "Copy target must be inside the configured boundary.",
484
+ details: { artifact_class: artifactClass },
485
+ });
486
+ ensureDirectoryForBoundaryPath(target.resolvedPath, {
487
+ boundaryRoot: targetBoundaryRoot,
488
+ boundaryRootReal: effectiveTargetBoundaryRootReal,
489
+ errorCode,
490
+ label: "copy target parent directory",
491
+ });
492
+ assertRegularFileWriteTarget(path.resolve(targetPath), { errorCode });
493
+ assertRegularFileWriteTarget(target.resolvedPath, { errorCode });
494
+
495
+ fs.copyFileSync(source.resolvedPath, target.resolvedPath);
496
+ return { sourcePath: source.resolvedPath, targetPath: target.resolvedPath };
497
+ }
498
+
499
+ export function copyImportSourceFileToSyncRoot(sourcePath, targetPath, {
500
+ syncDirAbs,
501
+ syncDirReal = syncDirAbs,
502
+ artifactClass = FILESYSTEM_ARTIFACT_CLASSES.IMPORT_DESTINATION,
503
+ errorCode = "INVALID_IMPORT_DESTINATION",
504
+ } = {}) {
505
+ const source = assertImportSourcePath(sourcePath, {
506
+ errorCode,
507
+ details: { import_source: path.resolve(sourcePath) },
508
+ });
509
+ assertRegularFileReadTarget(source.resolvedPath, { errorCode });
510
+
511
+ const target = resolveArtifactPathInsideSyncRoot(targetPath, {
512
+ syncDirAbs,
513
+ syncDirReal,
514
+ artifactClass,
515
+ errorCode,
516
+ errorMessage: "Import destination must be inside WRITING_SYNC_DIR.",
517
+ });
518
+ ensureDirectoryForBoundaryPath(target.resolvedPath, {
519
+ boundaryRoot: syncDirAbs,
520
+ boundaryRootReal: syncDirReal,
521
+ errorCode,
522
+ label: "import destination parent directory",
523
+ });
524
+ assertRegularFileWriteTarget(path.resolve(targetPath), { errorCode });
525
+ assertRegularFileWriteTarget(target.resolvedPath, { errorCode });
526
+
527
+ fs.copyFileSync(source.resolvedPath, target.resolvedPath);
528
+ return { sourcePath: source.resolvedPath, targetPath: target.resolvedPath };
529
+ }
530
+
531
+ export function deleteInsideBoundary(targetPath, {
532
+ boundaryRoot,
533
+ boundaryRootReal = boundaryRoot,
534
+ artifactClass = FILESYSTEM_ARTIFACT_CLASSES.GENERATED_EXPORT,
535
+ force = false,
536
+ recursive = false,
537
+ errorCode = "INVALID_PATH",
538
+ } = {}) {
539
+ try {
540
+ const stat = fs.lstatSync(path.resolve(targetPath));
541
+ if (stat.isSymbolicLink()) {
542
+ throw createCoreValidationError(
543
+ errorCode,
544
+ `Refusing to delete: target path is a symlink: ${targetPath}`,
545
+ { path: path.resolve(targetPath), artifact_class: artifactClass }
546
+ );
547
+ }
548
+ } catch (error) {
549
+ if (error?.name === "CoreValidationError") throw error;
550
+ if (error?.code !== "ENOENT") throw error;
551
+ }
552
+
553
+ const candidate = resolveCandidateInsideBoundary(targetPath, {
554
+ boundaryRoot,
555
+ boundaryRootReal,
556
+ errorCode,
557
+ errorMessage: "Delete target must be inside the configured boundary.",
558
+ details: { artifact_class: artifactClass },
559
+ });
560
+
561
+ if (!fs.existsSync(candidate.resolvedPath)) {
562
+ if (force) {
563
+ return { deleted: false, missing: true, targetPath: candidate.resolvedPath };
564
+ }
565
+ throw createCoreValidationError(
566
+ errorCode,
567
+ `Delete target does not exist: ${targetPath}`,
568
+ { path: candidate.resolvedPath, artifact_class: artifactClass }
569
+ );
570
+ }
571
+
572
+ fs.rmSync(candidate.resolvedPath, { force, recursive });
573
+ return { deleted: true, targetPath: candidate.resolvedPath };
574
+ }
575
+
576
+ export function moveInsideBoundary(fromPath, toPath, {
577
+ boundaryRoot,
578
+ boundaryRootReal = boundaryRoot,
579
+ artifactClass = FILESYSTEM_ARTIFACT_CLASSES.GENERATED_EXPORT,
580
+ allowCrossDeviceCopyFallback = true,
581
+ errorCode = "INVALID_PATH",
582
+ operations = fs,
583
+ } = {}) {
584
+ const existsSync = operations.existsSync ?? fs.existsSync;
585
+ assertRegularFileReadTarget(path.resolve(fromPath), { errorCode });
586
+ const source = resolveExistingPathInsideBoundary(fromPath, {
587
+ boundaryRoot,
588
+ boundaryRootReal,
589
+ errorCode,
590
+ errorMessage: "Move source must be inside the configured boundary.",
591
+ details: { artifact_class: artifactClass },
592
+ });
593
+ assertRegularFileReadTarget(source.resolvedPath, { errorCode });
594
+
595
+ const target = resolveCandidateInsideBoundary(toPath, {
596
+ boundaryRoot,
597
+ boundaryRootReal,
598
+ errorCode,
599
+ errorMessage: "Move target must be inside the configured boundary.",
600
+ details: { artifact_class: artifactClass },
601
+ });
602
+ ensureDirectoryForBoundaryPath(target.resolvedPath, {
603
+ boundaryRoot,
604
+ boundaryRootReal,
605
+ errorCode,
606
+ label: "move target parent directory",
607
+ });
608
+ assertRegularFileWriteTarget(path.resolve(toPath), { errorCode });
609
+ assertRegularFileWriteTarget(target.resolvedPath, { errorCode });
610
+
611
+ try {
612
+ operations.renameSync(source.resolvedPath, target.resolvedPath);
613
+ return { moved: true, method: "rename", sourcePath: source.resolvedPath, targetPath: target.resolvedPath };
614
+ } catch (error) {
615
+ if (!allowCrossDeviceCopyFallback || error?.code !== "EXDEV") {
616
+ throw error;
617
+ }
618
+ }
619
+
620
+ try {
621
+ operations.copyFileSync(source.resolvedPath, target.resolvedPath);
622
+ } catch (copyError) {
623
+ try {
624
+ if (existsSync(target.resolvedPath)) operations.unlinkSync(target.resolvedPath);
625
+ } catch {
626
+ // Best effort cleanup; report the original copy failure.
627
+ }
628
+ return {
629
+ moved: false,
630
+ method: "copy_unlink",
631
+ warning: {
632
+ code: "move_cross_device_copy_failed",
633
+ message: "Failed to copy file to destination; source file preserved and destination cleanup was attempted.",
634
+ from_path: source.resolvedPath,
635
+ to_path: target.resolvedPath,
636
+ cause: copyError instanceof Error ? copyError.message : String(copyError),
637
+ },
638
+ };
639
+ }
640
+ if (!existsSync(target.resolvedPath)) {
641
+ return {
642
+ moved: false,
643
+ method: "copy_unlink",
644
+ warning: {
645
+ code: "move_copy_verification_failed",
646
+ message: "Failed to verify file copy to destination; source file preserved.",
647
+ from_path: source.resolvedPath,
648
+ to_path: target.resolvedPath,
649
+ },
650
+ };
651
+ }
652
+
653
+ try {
654
+ operations.unlinkSync(source.resolvedPath);
655
+ } catch (unlinkError) {
656
+ try {
657
+ if (existsSync(target.resolvedPath)) operations.unlinkSync(target.resolvedPath);
658
+ } catch {
659
+ // Best effort cleanup; report the original unlink failure.
660
+ }
661
+ return {
662
+ moved: false,
663
+ method: "copy_unlink",
664
+ warning: {
665
+ code: "move_cross_device_unlink_failed",
666
+ message: "Copied destination but failed to remove source; destination cleanup was attempted.",
667
+ from_path: source.resolvedPath,
668
+ to_path: target.resolvedPath,
669
+ cause: unlinkError instanceof Error ? unlinkError.message : String(unlinkError),
670
+ },
671
+ };
672
+ }
673
+
674
+ return { moved: true, method: "copy_unlink", sourcePath: source.resolvedPath, targetPath: target.resolvedPath };
675
+ }
676
+
677
+ export function createRuntimeTempBoundary({
678
+ prefix = "mcp-writing-job-",
679
+ tmpRoot = os.tmpdir(),
680
+ } = {}) {
681
+ const tmpDir = fs.mkdtempSync(path.join(tmpRoot, prefix));
682
+ return {
683
+ tmpDir,
684
+ tmpDirReal: fs.realpathSync.native(tmpDir),
685
+ };
686
+ }
687
+
688
+ export function resolveRuntimeTempPath(tempDir, candidatePath, {
689
+ tempDirReal = tempDir,
690
+ errorCode = "INVALID_RUNTIME_TEMP_PATH",
691
+ } = {}) {
692
+ return resolveCandidateInsideBoundary(candidatePath, {
693
+ boundaryRoot: tempDir,
694
+ boundaryRootReal: tempDirReal,
695
+ errorCode,
696
+ errorMessage: "Runtime temp path must stay inside the job temp directory.",
697
+ details: { artifact_class: FILESYSTEM_ARTIFACT_CLASSES.RUNTIME_TEMP },
698
+ });
699
+ }
700
+
701
+ export function writeRuntimeTempFile(tempDir, filePath, data, {
702
+ tempDirReal = tempDir,
703
+ encoding = "utf8",
704
+ errorCode = "INVALID_RUNTIME_TEMP_PATH",
705
+ } = {}) {
706
+ const { resolvedPath } = resolveRuntimeTempPath(tempDir, filePath, { tempDirReal, errorCode });
707
+ assertRegularFileWriteTarget(path.resolve(filePath), { errorCode });
708
+ assertRegularFileWriteTarget(resolvedPath, { errorCode });
709
+ if (encoding) {
710
+ fs.writeFileSync(resolvedPath, data, encoding);
711
+ } else {
712
+ fs.writeFileSync(resolvedPath, data);
713
+ }
714
+ return resolvedPath;
715
+ }
716
+
717
+ export function cleanupRuntimeTempPath(tempDir, candidatePath, {
718
+ tempDirReal = tempDir,
719
+ recursive = false,
720
+ force = true,
721
+ errorCode = "INVALID_RUNTIME_TEMP_PATH",
722
+ } = {}) {
723
+ try {
724
+ const stat = fs.lstatSync(path.resolve(candidatePath));
725
+ if (stat.isSymbolicLink()) {
726
+ throw createCoreValidationError(
727
+ errorCode,
728
+ `Refusing to cleanup runtime temp path: target path is a symlink: ${candidatePath}`,
729
+ { path: path.resolve(candidatePath), artifact_class: FILESYSTEM_ARTIFACT_CLASSES.RUNTIME_TEMP }
730
+ );
731
+ }
732
+ } catch (error) {
733
+ if (error?.name === "CoreValidationError") throw error;
734
+ if (error?.code !== "ENOENT") throw error;
735
+ }
736
+
737
+ const { resolvedPath } = resolveRuntimeTempPath(tempDir, candidatePath, { tempDirReal, errorCode });
738
+ fs.rmSync(resolvedPath, { recursive, force });
739
+ return { deleted: true, targetPath: resolvedPath };
740
+ }
741
+
742
+ export function probeSyncRootWritable(syncDir, {
743
+ probeFileName = ".mcp-write-check",
744
+ errorCode = "INVALID_SYNC_ROOT_PROBE",
745
+ } = {}) {
746
+ const syncDirAbs = path.resolve(syncDir);
747
+ const syncDirReal = fs.realpathSync.native(syncDirAbs);
748
+ const probePath = path.join(syncDirAbs, probeFileName);
749
+
750
+ writeTextInsideSyncRoot(probePath, "", {
751
+ syncDirAbs,
752
+ syncDirReal,
753
+ artifactClass: FILESYSTEM_ARTIFACT_CLASSES.SYNC_ROOT_PROBE,
754
+ errorCode,
755
+ });
756
+ deleteInsideBoundary(probePath, {
757
+ boundaryRoot: syncDirAbs,
758
+ boundaryRootReal: syncDirReal,
759
+ artifactClass: FILESYSTEM_ARTIFACT_CLASSES.SYNC_ROOT_PROBE,
760
+ errorCode,
761
+ });
762
+
763
+ return true;
764
+ }