feat(shared): add fsync-skip tracker and path-environment classifier

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-05-08 15:08:05 +09:00
parent 7735b2abd5
commit 20ae3f8ba5
4 changed files with 266 additions and 0 deletions
@@ -0,0 +1,56 @@
import { describe, expect, it } from "bun:test"
import {
classifyPathEnvironment,
describePathClassification,
} from "./classify-path-environment"
describe("classifyPathEnvironment", () => {
it("classifies macOS iCloud path as icloud", () => {
expect(
classifyPathEnvironment(
"/Users/x/Library/Mobile Documents/com~apple~CloudDocs/project/file.txt",
),
).toBe("icloud")
})
it("classifies OneDrive path on unix style", () => {
expect(classifyPathEnvironment("/Users/x/OneDrive/foo")).toBe("onedrive")
})
it("classifies OneDrive path on windows style", () => {
expect(classifyPathEnvironment("C:\\Users\\x\\OneDrive\\foo")).toBe("onedrive")
})
it("classifies macOS Desktop path as desktop-sync", () => {
expect(classifyPathEnvironment("/Users/x/Desktop/foo")).toBe("desktop-sync")
})
it("classifies /Volumes path as network-drive", () => {
expect(classifyPathEnvironment("/Volumes/NetworkShare/foo")).toBe("network-drive")
})
it("classifies random path as unknown", () => {
expect(classifyPathEnvironment("/tmp/foo")).toBe("unknown")
})
it("classifies empty string as unknown", () => {
expect(classifyPathEnvironment("")).toBe("unknown")
})
it("matches OneDrive case-insensitively", () => {
expect(classifyPathEnvironment("/Users/x/oNeDrIvE/foo")).toBe("onedrive")
})
})
describe("describePathClassification", () => {
it("returns human-readable descriptions", () => {
expect(describePathClassification("icloud")).toBe("iCloud Drive")
expect(describePathClassification("onedrive")).toBe("OneDrive")
expect(describePathClassification("desktop-sync")).toBe("Desktop sync (macOS)")
expect(describePathClassification("network-drive")).toBe("Network drive")
expect(describePathClassification("unknown")).toBe(
"filesystem that does not support fsync",
)
})
})
+68
View File
@@ -0,0 +1,68 @@
import { homedir } from "node:os"
import path from "node:path"
export type PathClassification =
| "icloud"
| "onedrive"
| "desktop-sync"
| "network-drive"
| "unknown"
function normalizeInputPath(absolutePath: string): string {
return absolutePath.replaceAll("\\", "/")
}
function isUnderPath(normalizedPath: string, normalizedParentPath: string): boolean {
return normalizedPath === normalizedParentPath || normalizedPath.startsWith(`${normalizedParentPath}/`)
}
export function classifyPathEnvironment(absolutePath: string): PathClassification {
if (absolutePath.length === 0) return "unknown"
const normalizedPath = normalizeInputPath(absolutePath)
const lowercasePath = normalizedPath.toLowerCase()
if (lowercasePath.includes("/onedrive") || lowercasePath.includes("/onedrive/")) {
return "onedrive"
}
if (normalizedPath.includes("/Library/Mobile Documents/")) {
return "icloud"
}
if (isUnderPath(normalizedPath, "/Volumes")) {
return "network-drive"
}
if (
normalizedPath.startsWith("/Users/")
&& (normalizedPath.includes("/Desktop/") || normalizedPath.endsWith("/Desktop")
|| normalizedPath.includes("/Documents/") || normalizedPath.endsWith("/Documents"))
) {
return "desktop-sync"
}
const normalizedHome = normalizeInputPath(homedir())
const desktopPath = normalizeInputPath(path.join(normalizedHome, "Desktop"))
const documentsPath = normalizeInputPath(path.join(normalizedHome, "Documents"))
if (isUnderPath(normalizedPath, desktopPath) || isUnderPath(normalizedPath, documentsPath)) {
return "desktop-sync"
}
return "unknown"
}
export function describePathClassification(pathClassification: PathClassification): string {
switch (pathClassification) {
case "icloud":
return "iCloud Drive"
case "onedrive":
return "OneDrive"
case "desktop-sync":
return "Desktop sync (macOS)"
case "network-drive":
return "Network drive"
case "unknown":
return "filesystem that does not support fsync"
}
}
+100
View File
@@ -0,0 +1,100 @@
import { beforeEach, describe, expect, it } from "bun:test"
import {
clearAllSkips,
drainSkipsAfter,
recordFsyncSkip,
} from "./fsync-skip-tracker"
type PathClassification =
| "icloud"
| "onedrive"
| "desktop-sync"
| "network-drive"
| "unknown"
function recordSkip(index: number, pathClassification: PathClassification = "unknown"): void {
recordFsyncSkip({
filePath: `/tmp/file-${index}.txt`,
contextLabel: `atomicWrite:/tmp/file-${index}.txt`,
errorCode: "EPERM",
message: "operation not permitted",
pathClassification,
})
}
describe("fsync-skip-tracker", () => {
beforeEach(() => {
clearAllSkips()
})
it("recordFsyncSkip adds entry with timestamp", () => {
const before = Date.now()
recordSkip(1)
const entries = drainSkipsAfter(0)
expect(entries).toHaveLength(1)
expect(entries[0]?.filePath).toBe("/tmp/file-1.txt")
expect(entries[0]?.timestamp).toBeGreaterThanOrEqual(before)
})
it("drainSkipsAfter(timestamp) returns entries strictly after the timestamp", async () => {
recordSkip(1)
const firstTimestamp = Date.now()
await Bun.sleep(2)
recordSkip(2)
const drained = drainSkipsAfter(firstTimestamp)
expect(drained).toHaveLength(1)
expect(drained[0]?.filePath).toBe("/tmp/file-2.txt")
})
it("drainSkipsAfter removes drained entries from buffer", () => {
recordSkip(1)
recordSkip(2)
const drained = drainSkipsAfter(0)
expect(drained).toHaveLength(2)
expect(drainSkipsAfter(0)).toEqual([])
})
it("buffer is bounded to max 200 entries and drops oldest on overflow", () => {
for (let index = 1; index <= 205; index += 1) {
recordSkip(index)
}
const drained = drainSkipsAfter(0)
expect(drained).toHaveLength(200)
expect(drained[0]?.filePath).toBe("/tmp/file-6.txt")
expect(drained[199]?.filePath).toBe("/tmp/file-205.txt")
})
it("multiple records with same path are kept", () => {
recordSkip(1)
recordFsyncSkip({
filePath: "/tmp/file-1.txt",
contextLabel: "acquireLock:/tmp/file-1.txt",
errorCode: "EPERM",
message: "second",
pathClassification: "unknown",
})
const drained = drainSkipsAfter(0)
expect(drained).toHaveLength(2)
expect(drained[0]?.filePath).toBe("/tmp/file-1.txt")
expect(drained[1]?.filePath).toBe("/tmp/file-1.txt")
})
it("drainSkipsAfter(0) returns all entries", () => {
recordSkip(1)
recordSkip(2)
const drained = drainSkipsAfter(0)
expect(drained).toHaveLength(2)
})
it("empty buffer returns empty array", () => {
expect(drainSkipsAfter(0)).toEqual([])
})
})
+42
View File
@@ -0,0 +1,42 @@
import type { PathClassification } from "./classify-path-environment"
export type FsyncSkipEntry = {
filePath: string
contextLabel: string
errorCode: string
message: string
pathClassification: PathClassification
timestamp: number
}
const MAX_SKIPS = 200
const fsyncSkips: FsyncSkipEntry[] = []
export function recordFsyncSkip(entry: Omit<FsyncSkipEntry, "timestamp">): void {
fsyncSkips.push({ ...entry, timestamp: Date.now() })
if (fsyncSkips.length > MAX_SKIPS) {
fsyncSkips.splice(0, fsyncSkips.length - MAX_SKIPS)
}
}
export function drainSkipsAfter(timestampMs: number): FsyncSkipEntry[] {
const drainedEntries: FsyncSkipEntry[] = []
const retainedEntries: FsyncSkipEntry[] = []
for (const entry of fsyncSkips) {
if (entry.timestamp > timestampMs) {
drainedEntries.push(entry)
continue
}
retainedEntries.push(entry)
}
fsyncSkips.splice(0, fsyncSkips.length, ...retainedEntries)
return drainedEntries
}
export function clearAllSkips(): void {
fsyncSkips.length = 0
}