@lousy-agents/lint 5.15.7 → 5.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.
Files changed (2) hide show
  1. package/dist/index.js +468 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -566,7 +566,7 @@ module.exports = webpackEmptyAsyncContext;
566
566
 
567
567
 
568
568
  },
569
- 2749(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
569
+ 6179(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
570
570
 
571
571
  // EXPORTS
572
572
  __webpack_require__.d(__webpack_exports__, {
@@ -884,16 +884,24 @@ function createBoundedReadStream(opened, maxBytes) {
884
884
  }
885
885
 
886
886
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/file-identity.js
887
+
887
888
  function isZero(value) {
888
889
  return value === 0 || value === 0n;
889
890
  }
891
+ function sameStatValue(left, right) {
892
+ return typeof left === typeof right ? left === right : BigInt(left) === BigInt(right);
893
+ }
894
+ function sha256Hex(data, encoding) {
895
+ const buffer = typeof data === "string" ? Buffer.from(data, encoding ?? "utf8") : data;
896
+ return (0,external_node_crypto_.createHash)("sha256").update(buffer).digest("hex");
897
+ }
890
898
  function file_identity_sameFileIdentity(left, right, platform = process.platform) {
891
- if (left.ino !== right.ino) {
899
+ if (!sameStatValue(left.ino, right.ino)) {
892
900
  return false;
893
901
  }
894
902
  // On Windows, path-based stat calls can report dev=0 while fd-based stat
895
903
  // reports a real volume serial; treat either-side dev=0 as "unknown device".
896
- if (left.dev === right.dev) {
904
+ if (sameStatValue(left.dev, right.dev)) {
897
905
  return true;
898
906
  }
899
907
  return platform === "win32" && (isZero(left.dev) || isZero(right.dev));
@@ -2287,6 +2295,377 @@ async function runPinnedPathHelper(params) {
2287
2295
  }
2288
2296
  }
2289
2297
 
2298
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/sidecar-lock.js
2299
+
2300
+
2301
+
2302
+
2303
+ const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
2304
+ function getGlobalManagers() {
2305
+ const globalWithState = globalThis;
2306
+ if (!globalWithState[GLOBAL_STATE_KEY]) {
2307
+ globalWithState[GLOBAL_STATE_KEY] = new Map();
2308
+ }
2309
+ return globalWithState[GLOBAL_STATE_KEY];
2310
+ }
2311
+ function resolveManagerState(key) {
2312
+ const managers = getGlobalManagers();
2313
+ let state = managers.get(key);
2314
+ if (!state) {
2315
+ state = { cleanupRegistered: false, held: new Map() };
2316
+ managers.set(key, state);
2317
+ }
2318
+ return state;
2319
+ }
2320
+ async function readLockSnapshot(lockPath) {
2321
+ try {
2322
+ const stat = await promises_.lstat(lockPath);
2323
+ const raw = await promises_.readFile(lockPath, "utf8");
2324
+ try {
2325
+ const parsed = JSON.parse(raw);
2326
+ const payload = parsed && typeof parsed === "object" && !Array.isArray(parsed)
2327
+ ? parsed
2328
+ : null;
2329
+ return { raw, payload, stat };
2330
+ }
2331
+ catch {
2332
+ return { raw, payload: null, stat };
2333
+ }
2334
+ }
2335
+ catch (err) {
2336
+ if (err.code === "ENOENT") {
2337
+ return null;
2338
+ }
2339
+ throw err;
2340
+ }
2341
+ }
2342
+ function snapshotMatches(current, observed) {
2343
+ if (observed.stat && current.stat && !file_identity_sameFileIdentity(observed.stat, current.stat)) {
2344
+ return false;
2345
+ }
2346
+ if (observed.raw !== undefined) {
2347
+ return current.raw === observed.raw;
2348
+ }
2349
+ return observed.stat !== undefined && current.stat !== undefined;
2350
+ }
2351
+ async function removeLockIfUnchanged(lockPath, observed) {
2352
+ const current = await readLockSnapshot(lockPath);
2353
+ if (!current || !observed) {
2354
+ return false;
2355
+ }
2356
+ if (!snapshotMatches(current, observed)) {
2357
+ // The lock changed after we decided it was stale. Leave the fresh holder's
2358
+ // file alone; deleting by path here would break mutual exclusion.
2359
+ return false;
2360
+ }
2361
+ await promises_.rm(lockPath, { force: true }).catch(() => undefined);
2362
+ return true;
2363
+ }
2364
+ async function lockSnapshotStillPresent(lockPath, observed) {
2365
+ const current = await readLockSnapshot(lockPath);
2366
+ return !!current && !!observed && snapshotMatches(current, observed);
2367
+ }
2368
+ async function removeStaleLockIfAllowed(params) {
2369
+ if (!params.shouldRemoveStaleLock) {
2370
+ return "not-approved";
2371
+ }
2372
+ if (params.snapshot.raw === undefined) {
2373
+ return "not-approved";
2374
+ }
2375
+ if (!(await params.shouldRemoveStaleLock({
2376
+ lockPath: params.lockPath,
2377
+ normalizedTargetPath: params.normalizedTargetPath,
2378
+ raw: params.snapshot.raw,
2379
+ payload: params.snapshot.payload,
2380
+ }))) {
2381
+ return "not-approved";
2382
+ }
2383
+ const current = await readLockSnapshot(params.lockPath);
2384
+ if (!current || !snapshotMatches(current, params.snapshot)) {
2385
+ return "changed";
2386
+ }
2387
+ try {
2388
+ await promises_.rm(params.lockPath, { force: true });
2389
+ }
2390
+ catch (err) {
2391
+ if (err.code === "ENOENT") {
2392
+ return "changed";
2393
+ }
2394
+ return "not-approved";
2395
+ }
2396
+ return "removed";
2397
+ }
2398
+ function snapshotMatchesSync(lockPath, observed) {
2399
+ try {
2400
+ const stat = external_node_fs_.lstatSync(lockPath);
2401
+ if (observed.stat && !file_identity_sameFileIdentity(observed.stat, stat)) {
2402
+ return false;
2403
+ }
2404
+ return observed.raw === undefined || external_node_fs_.readFileSync(lockPath, "utf8") === observed.raw;
2405
+ }
2406
+ catch {
2407
+ return false;
2408
+ }
2409
+ }
2410
+ async function resolveNormalizedTargetPath(targetPath) {
2411
+ const resolved = external_node_path_.resolve(targetPath);
2412
+ const dir = external_node_path_.dirname(resolved);
2413
+ await promises_.mkdir(dir, { recursive: true });
2414
+ try {
2415
+ return external_node_path_.join(await promises_.realpath(dir), external_node_path_.basename(resolved));
2416
+ }
2417
+ catch {
2418
+ return resolved;
2419
+ }
2420
+ }
2421
+ function computeDelayMs(retry, attempt) {
2422
+ const minTimeout = retry.minTimeout ?? 50;
2423
+ const maxTimeout = retry.maxTimeout ?? 1000;
2424
+ const factor = retry.factor ?? 1;
2425
+ const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
2426
+ const jitter = retry.randomize ? 1 + Math.random() : 1;
2427
+ return Math.min(maxTimeout, Math.round(base * jitter));
2428
+ }
2429
+ async function defaultShouldReclaim(params) {
2430
+ const createdAt = typeof params.payload?.createdAt === "string" ? params.payload.createdAt : "";
2431
+ const createdAtMs = Date.parse(createdAt);
2432
+ if (Number.isFinite(createdAtMs) && params.nowMs - createdAtMs > params.staleMs) {
2433
+ return true;
2434
+ }
2435
+ try {
2436
+ const stat = await promises_.stat(params.lockPath);
2437
+ return params.nowMs - stat.mtimeMs > params.staleMs;
2438
+ }
2439
+ catch {
2440
+ return true;
2441
+ }
2442
+ }
2443
+ function releaseAllLocksSync(state) {
2444
+ for (const [normalizedTargetPath, held] of state.held) {
2445
+ void held.handle.close().catch(() => undefined);
2446
+ try {
2447
+ if (snapshotMatchesSync(held.lockPath, held.snapshot)) {
2448
+ external_node_fs_.rmSync(held.lockPath, { force: true });
2449
+ }
2450
+ }
2451
+ catch {
2452
+ // Best-effort process-exit cleanup.
2453
+ }
2454
+ state.held.delete(normalizedTargetPath);
2455
+ }
2456
+ }
2457
+ async function releaseHeldLock(state, normalizedTargetPath, held, opts = {}) {
2458
+ const current = state.held.get(normalizedTargetPath);
2459
+ if (current !== held) {
2460
+ return false;
2461
+ }
2462
+ if (opts.force) {
2463
+ held.count = 0;
2464
+ }
2465
+ else {
2466
+ held.count -= 1;
2467
+ if (held.count > 0) {
2468
+ return false;
2469
+ }
2470
+ }
2471
+ if (held.releasePromise) {
2472
+ await held.releasePromise.catch(() => undefined);
2473
+ return true;
2474
+ }
2475
+ state.held.delete(normalizedTargetPath);
2476
+ held.releasePromise = (async () => {
2477
+ await held.handle.close().catch(() => undefined);
2478
+ await removeLockIfUnchanged(held.lockPath, held.snapshot);
2479
+ })();
2480
+ try {
2481
+ await held.releasePromise;
2482
+ return true;
2483
+ }
2484
+ finally {
2485
+ held.releasePromise = undefined;
2486
+ }
2487
+ }
2488
+ function createSidecarLockManager(key) {
2489
+ const state = resolveManagerState(key);
2490
+ function ensureExitCleanupRegistered() {
2491
+ if (state.cleanupRegistered) {
2492
+ return;
2493
+ }
2494
+ state.cleanupRegistered = true;
2495
+ process.on("exit", () => releaseAllLocksSync(state));
2496
+ }
2497
+ async function acquire(options) {
2498
+ ensureExitCleanupRegistered();
2499
+ const normalizedTargetPath = await resolveNormalizedTargetPath(options.targetPath);
2500
+ const lockPath = options.lockPath ?? `${normalizedTargetPath}.lock`;
2501
+ const held = state.held.get(normalizedTargetPath);
2502
+ if (held && options.allowReentrant) {
2503
+ held.count += 1;
2504
+ const release = () => releaseHeldLock(state, normalizedTargetPath, held).then(() => undefined);
2505
+ return {
2506
+ lockPath,
2507
+ normalizedTargetPath,
2508
+ release,
2509
+ [Symbol.asyncDispose]: release,
2510
+ };
2511
+ }
2512
+ const startedAt = Date.now();
2513
+ const retry = options.retry ?? {};
2514
+ const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
2515
+ let attempt = 0;
2516
+ while (true) {
2517
+ let handle = null;
2518
+ try {
2519
+ handle = await promises_.open(lockPath, "wx");
2520
+ const payload = await options.payload();
2521
+ const raw = `${JSON.stringify(payload, null, 2)}\n`;
2522
+ await handle.writeFile(raw, "utf8");
2523
+ const snapshot = { raw, payload, stat: await handle.stat() };
2524
+ const createdHeld = {
2525
+ count: 1,
2526
+ handle,
2527
+ lockPath,
2528
+ snapshot,
2529
+ acquiredAt: Date.now(),
2530
+ metadata: options.metadata ?? {},
2531
+ };
2532
+ state.held.set(normalizedTargetPath, createdHeld);
2533
+ const release = () => releaseHeldLock(state, normalizedTargetPath, createdHeld).then(() => undefined);
2534
+ return {
2535
+ lockPath,
2536
+ normalizedTargetPath,
2537
+ release,
2538
+ [Symbol.asyncDispose]: release,
2539
+ };
2540
+ }
2541
+ catch (err) {
2542
+ if (handle) {
2543
+ const failedSnapshot = { payload: null };
2544
+ try {
2545
+ failedSnapshot.stat = await handle.stat();
2546
+ }
2547
+ catch {
2548
+ // Best-effort cleanup of a failed exclusive create.
2549
+ }
2550
+ const current = state.held.get(normalizedTargetPath);
2551
+ if (current?.handle === handle) {
2552
+ state.held.delete(normalizedTargetPath);
2553
+ }
2554
+ // If payload serialization/write fails, the file may be empty or
2555
+ // partial JSON, so remove while our exclusive handle is still open.
2556
+ await promises_.rm(lockPath, { force: true }).catch(() => undefined);
2557
+ await handle.close().catch(() => undefined);
2558
+ // Windows can refuse removing an open file; retry after close but
2559
+ // only if the path still points at the file identity we created.
2560
+ await removeLockIfUnchanged(lockPath, failedSnapshot);
2561
+ }
2562
+ if (err.code !== "EEXIST") {
2563
+ throw err;
2564
+ }
2565
+ const nowMs = Date.now();
2566
+ const snapshot = await readLockSnapshot(lockPath);
2567
+ if (!snapshot) {
2568
+ continue;
2569
+ }
2570
+ const shouldReclaim = options.shouldReclaim ?? defaultShouldReclaim;
2571
+ if (await shouldReclaim({
2572
+ lockPath,
2573
+ normalizedTargetPath,
2574
+ payload: snapshot?.payload ?? null,
2575
+ staleMs: options.staleMs,
2576
+ nowMs,
2577
+ heldByThisProcess: state.held.has(normalizedTargetPath),
2578
+ })) {
2579
+ if (!(await lockSnapshotStillPresent(lockPath, snapshot))) {
2580
+ continue;
2581
+ }
2582
+ const staleRecovery = options.staleRecovery ?? "fail-closed";
2583
+ if (staleRecovery === "remove-if-unchanged") {
2584
+ const removal = await removeStaleLockIfAllowed({
2585
+ lockPath,
2586
+ normalizedTargetPath,
2587
+ snapshot,
2588
+ shouldRemoveStaleLock: options.shouldRemoveStaleLock,
2589
+ });
2590
+ if (removal === "removed" || removal === "changed") {
2591
+ continue;
2592
+ }
2593
+ }
2594
+ throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
2595
+ code: "file_lock_stale",
2596
+ lockPath,
2597
+ normalizedTargetPath,
2598
+ });
2599
+ }
2600
+ const elapsed = Date.now() - startedAt;
2601
+ if ((options.timeoutMs !== undefined &&
2602
+ options.timeoutMs !== Number.POSITIVE_INFINITY &&
2603
+ elapsed >= options.timeoutMs) ||
2604
+ (maxRetries !== undefined && attempt >= maxRetries)) {
2605
+ throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
2606
+ code: "file_lock_timeout",
2607
+ lockPath,
2608
+ normalizedTargetPath,
2609
+ });
2610
+ }
2611
+ const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
2612
+ ? Number.POSITIVE_INFINITY
2613
+ : Math.max(0, options.timeoutMs - elapsed);
2614
+ const delay = Math.min(computeDelayMs(retry, attempt), remaining);
2615
+ attempt += 1;
2616
+ await new Promise((resolve) => setTimeout(resolve, delay));
2617
+ }
2618
+ }
2619
+ }
2620
+ async function withLock(options, fn) {
2621
+ const lock = await acquire(options);
2622
+ try {
2623
+ return await fn();
2624
+ }
2625
+ finally {
2626
+ await lock.release();
2627
+ }
2628
+ }
2629
+ async function drain() {
2630
+ for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
2631
+ await releaseHeldLock(state, normalizedTargetPath, held, { force: true }).catch(() => undefined);
2632
+ }
2633
+ }
2634
+ function reset() {
2635
+ releaseAllLocksSync(state);
2636
+ }
2637
+ function heldEntries() {
2638
+ return Array.from(state.held.entries()).map(([normalizedTargetPath, held]) => ({
2639
+ normalizedTargetPath,
2640
+ lockPath: held.lockPath,
2641
+ acquiredAt: held.acquiredAt,
2642
+ metadata: held.metadata,
2643
+ forceRelease: () => releaseHeldLock(state, normalizedTargetPath, held, { force: true }),
2644
+ }));
2645
+ }
2646
+ return { acquire, withLock, drain, reset, heldEntries };
2647
+ }
2648
+ async function withSidecarLock(targetPath, options, fn) {
2649
+ const manager = createSidecarLockManager(options.managerKey ?? `fs-safe.sidecar-lock:${targetPath}`);
2650
+ const { managerKey: _managerKey, ...acquireOptions } = options;
2651
+ return await manager.withLock({ ...acquireOptions, targetPath }, fn);
2652
+ }
2653
+
2654
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
2655
+ let test_hooks_fsSafeTestHooks;
2656
+ function allowFsSafeTestHooks() {
2657
+ return false || process.env.VITEST === "true";
2658
+ }
2659
+ function getFsSafeTestHooks() {
2660
+ return test_hooks_fsSafeTestHooks;
2661
+ }
2662
+ function __setFsSafeTestHooksForTest(hooks) {
2663
+ if (hooks && !allowFsSafeTestHooks()) {
2664
+ throw new Error("__setFsSafeTestHooksForTest is only available in tests");
2665
+ }
2666
+ test_hooks_fsSafeTestHooks = hooks;
2667
+ }
2668
+
2290
2669
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-write.js
2291
2670
 
2292
2671
 
@@ -2300,6 +2679,8 @@ async function runPinnedPathHelper(params) {
2300
2679
 
2301
2680
 
2302
2681
 
2682
+
2683
+
2303
2684
  function byteLength(input, encoding) {
2304
2685
  return typeof input === "string"
2305
2686
  ? Buffer.byteLength(input, encoding ?? "utf8")
@@ -2357,6 +2738,12 @@ async function runPinnedWriteHelper(params) {
2357
2738
  validatePinnedOperationPayload({
2358
2739
  relativeParentPath: params.relativeParentPath,
2359
2740
  });
2741
+ // The Python helper deliberately enforces the strict post-rename inode
2742
+ // contract. The explicit compatibility policy therefore uses the guarded
2743
+ // Node fallback, where content verification can replace that one check.
2744
+ if (params.onRenameIdentityMismatch === "verify-content") {
2745
+ return await runPinnedWriteFallback(params);
2746
+ }
2360
2747
  if (getFsSafePythonConfig().mode === "off") {
2361
2748
  return await runPinnedWriteFallback(params);
2362
2749
  }
@@ -2371,8 +2758,11 @@ async function runPinnedWriteHelper(params) {
2371
2758
  throw error;
2372
2759
  }
2373
2760
  }
2761
+ const input = params.input.kind === "stream"
2762
+ ? { kind: "buffer", data: Buffer.from(await inputToBase64(params.input, params.maxBytes), "base64") }
2763
+ : params.input;
2374
2764
  const payload = {
2375
- base64: await inputToBase64(params.input, params.maxBytes),
2765
+ base64: await inputToBase64(input, params.maxBytes),
2376
2766
  basename: params.basename,
2377
2767
  maxBytes: params.maxBytes ?? -1,
2378
2768
  mkdir: params.mkdir,
@@ -2390,11 +2780,32 @@ async function runPinnedWriteHelper(params) {
2390
2780
  }
2391
2781
  catch (error) {
2392
2782
  if (canFallbackFromPythonError(error)) {
2393
- return await runPinnedWriteFallback(params);
2783
+ return await runPinnedWriteFallback({ ...params, input });
2394
2784
  }
2395
2785
  throw error;
2396
2786
  }
2397
2787
  }
2788
+ async function runPinnedWriteWithRenamePolicy(params) {
2789
+ const { targetPath, renameIdentity, ...writeParams } = params;
2790
+ if (renameIdentity !== "verify-content-with-lock") {
2791
+ return await runPinnedWriteHelper(writeParams);
2792
+ }
2793
+ const relativeTargetPath = writeParams.relativeParentPath
2794
+ ? `${writeParams.relativeParentPath}/${writeParams.basename}`
2795
+ : writeParams.basename;
2796
+ const lockPath = external_node_path_.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
2797
+ return await withSidecarLock(writeParams.rootPath, {
2798
+ managerKey: `fs-safe.write:${targetPath}`,
2799
+ lockPath,
2800
+ staleMs: 30_000,
2801
+ timeoutMs: 5_000,
2802
+ payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
2803
+ retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
2804
+ }, async () => await runPinnedWriteHelper({
2805
+ ...writeParams,
2806
+ onRenameIdentityMismatch: "verify-content",
2807
+ }));
2808
+ }
2398
2809
  async function runPinnedCopyHelper(params) {
2399
2810
  assertSafeBasename(params.basename);
2400
2811
  validatePinnedOperationPayload({
@@ -2450,7 +2861,10 @@ async function runPinnedWriteFallback(params) {
2450
2861
  else {
2451
2862
  await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
2452
2863
  }
2864
+ await handle.sync();
2453
2865
  const stat = await handle.stat();
2866
+ await handle.close().catch(() => undefined);
2867
+ await syncDirectoryBestEffort(parentPath);
2454
2868
  created = false;
2455
2869
  return { dev: stat.dev, ino: stat.ino };
2456
2870
  }
@@ -2498,11 +2912,49 @@ async function runPinnedWriteFallback(params) {
2498
2912
  await withAsyncDirectoryGuards([parentGuard], async () => {
2499
2913
  await promises_.rename(tempPath, targetPath);
2500
2914
  renamed = true;
2915
+ await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
2501
2916
  await syncDirectoryBestEffort(parentPath);
2502
2917
  targetStat = await promises_.lstat(targetPath);
2503
- if (targetStat.isSymbolicLink() || !file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
2918
+ if (targetStat.isSymbolicLink()) {
2504
2919
  throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
2505
2920
  }
2921
+ if (!file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
2922
+ // On filesystems like rclone FUSE, rename(2) can give the destination a
2923
+ // different inode from the source temp fd even with zero concurrency. The
2924
+ // caller must ensure mutual exclusion before passing "verify-content";
2925
+ // fall back to a content hash for this rename-boundary check only.
2926
+ if (params.onRenameIdentityMismatch !== "verify-content") {
2927
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
2928
+ }
2929
+ if (params.input.kind !== "buffer") {
2930
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
2931
+ }
2932
+ const expectedHash = sha256Hex(params.input.data, params.input.encoding);
2933
+ const readFlags = external_node_fs_.constants.O_RDONLY |
2934
+ (process.platform !== "win32" && "O_NOFOLLOW" in external_node_fs_.constants
2935
+ ? external_node_fs_.constants.O_NOFOLLOW
2936
+ : 0);
2937
+ const readHandle = await promises_.open(targetPath, readFlags);
2938
+ let actualHash;
2939
+ let readHandleStat;
2940
+ try {
2941
+ // Capture fd-based identity before reading — this is stable across all
2942
+ // subsequent lookups (on FUSE and locally), unlike the lstat-based
2943
+ // targetStat that triggered this fallback.
2944
+ readHandleStat = await readHandle.stat();
2945
+ actualHash = sha256Hex(await readHandle.readFile());
2946
+ }
2947
+ finally {
2948
+ await readHandle.close().catch(() => undefined);
2949
+ }
2950
+ if (actualHash !== expectedHash) {
2951
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
2952
+ }
2953
+ // Replace the unreliable lstat-based targetStat with the fd-based stat so
2954
+ // the returned identity is consistent with what subsequent verifications
2955
+ // (e.g. verifyAtomicWriteResult) will obtain by opening the same file.
2956
+ targetStat = readHandleStat;
2957
+ }
2506
2958
  });
2507
2959
  }
2508
2960
  catch (error) {
@@ -3598,21 +4050,6 @@ function normalizePinnedPathError(error) {
3598
4050
  });
3599
4051
  }
3600
4052
 
3601
- ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/test-hooks.js
3602
- let test_hooks_fsSafeTestHooks;
3603
- function allowFsSafeTestHooks() {
3604
- return false || process.env.VITEST === "true";
3605
- }
3606
- function getFsSafeTestHooks() {
3607
- return test_hooks_fsSafeTestHooks;
3608
- }
3609
- function __setFsSafeTestHooksForTest(hooks) {
3610
- if (hooks && !allowFsSafeTestHooks()) {
3611
- throw new Error("__setFsSafeTestHooksForTest is only available in tests");
3612
- }
3613
- test_hooks_fsSafeTestHooks = hooks;
3614
- }
3615
-
3616
4053
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/json-stringify.js
3617
4054
  function stringifyJsonDocument(value, replacer, space) {
3618
4055
  const text = JSON.stringify(value, replacer, space);
@@ -3948,6 +4385,7 @@ class RootHandle {
3948
4385
  data,
3949
4386
  mkdir: this.defaults.mkdir,
3950
4387
  mode: this.defaults.mode,
4388
+ renameIdentity: this.defaults.renameIdentity,
3951
4389
  ...options,
3952
4390
  denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
3953
4391
  });
@@ -4382,21 +4820,23 @@ async function writeFileInRoot(root, params) {
4382
4820
  async function commitPinnedWriteInRoot(root, pinned, params) {
4383
4821
  let identity;
4384
4822
  try {
4385
- identity = await runPinnedWriteHelper({
4823
+ identity = await runPinnedWriteWithRenamePolicy({
4386
4824
  rootPath: pinned.rootReal,
4387
4825
  relativeParentPath: pinned.relativeParentPath,
4388
4826
  basename: pinned.basename,
4827
+ targetPath: pinned.targetPath,
4828
+ renameIdentity: params.renameIdentity,
4389
4829
  mkdir: params.mkdir !== false,
4390
4830
  mode: params.mode ?? pinned.mode,
4391
4831
  overwrite: params.overwrite,
4392
- input: {
4393
- kind: "buffer",
4394
- data: params.data,
4395
- encoding: params.encoding,
4396
- },
4832
+ input: { kind: "buffer", data: params.data, encoding: params.encoding },
4397
4833
  });
4398
4834
  }
4399
4835
  catch (error) {
4836
+ const errorCode = error?.code;
4837
+ if (errorCode === "file_lock_stale" || errorCode === "file_lock_timeout") {
4838
+ throw error;
4839
+ }
4400
4840
  if (params.overwrite === false && isAlreadyExistsError(error)) {
4401
4841
  throw new errors_FsSafeError("already-exists", "file already exists", {
4402
4842
  cause: error instanceof Error ? error : undefined,
@@ -46550,7 +46990,7 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
46550
46990
  // module factories are used so entry inlining is disabled
46551
46991
  // startup
46552
46992
  // Load entry module and return exports
46553
- var __webpack_exports__ = __webpack_require__(2749);
46993
+ var __webpack_exports__ = __webpack_require__(6179);
46554
46994
  var __webpack_exports__DEFAULT_LINT_RULES = __webpack_exports__.Kd;
46555
46995
  var __webpack_exports__LintValidationError = __webpack_exports__.F5;
46556
46996
  var __webpack_exports__createFormatter = __webpack_exports__.xi;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/lint",
3
- "version": "5.15.7",
3
+ "version": "5.16.0",
4
4
  "description": "Programmatic lint API for validating AI coding assistant configurations — skills, agents, hooks, and instructions",
5
5
  "type": "module",
6
6
  "repository": {