Sorty takes periodic snapshots of directory state:
public struct DirectorySnapshot: Codable { public let directoryPath: String public let timestamp: Date public let totalFiles: Int public let totalSize: Int64 public let filesByExtension: [String: Int] public let unorganizedCount: Int public let averageFileAge: TimeInterval}
Up to 52 snapshots per directory are retained (~1 year of weekly snapshots).
Compare snapshots over time:
public struct DirectoryGrowth { public let previousSnapshot: DirectorySnapshot public let currentSnapshot: DirectorySnapshot public var fileCountChange: Int public var sizeChange: Int64 public var percentageGrowth: Double public var growthRate: GrowthRate}
Growth Rates:
Stable: ≤0MB
Slow: <100MB
Moderate: 100MB-1GB
Rapid: >1GB
Identifies which file types are accumulating:
public var topGrowingTypes: [(extension: String, count: Int)] { var changes: [String: Int] = [:] for (ext, count) in currentSnapshot.filesByExtension { let previousCount = previousSnapshot.filesByExtension[ext] ?? 0 let change = count - previousCount if change > 0 { changes[ext] = change } } return changes.sorted { $0.value > $1.value }.prefix(5)}
Detects: Files not accessed in 30+ daysSmart Detection:
Skips files in active projects (checks for .git, package.json, etc.)
Uses last access date, falls back to creation date
Configurable threshold
Quick Action: Archive Old Downloads
let oldDownloads = files.filter { file in guard !isFileInProject(file) else { return false } let relevantDate = file.lastAccessDate ?? file.creationDate guard let date = relevantDate else { return false } let age = now.timeIntervalSince(date) return age > config.downloadClutterThreshold}
let largeFiles = files.filter { file in if isFileInProject(file) { return false } return file.size > config.largeFileSizeThreshold}
Unorganized Files
Detects: ≥10 files in root directoryIdentifies:
Files not in subfolders
Excludes hidden files (.DS_Store, etc.)
Quick Action: Organize Root
let rootFiles = files.filter { file in let relativePath = file.path.replacingOccurrences(of: path + "/", with: "") return !relativePath.contains("/") && !file.isDirectory && !file.name.hasPrefix(".")}
Very Old Files
Detects: Files not accessed in 1+ yearSmart Detection:
Skips active project files
Uses last access date when available
Quick Action: Archive Very Old Files
let veryOldFiles = files.filter { file in if isFileInProject(file) { return false } guard let accessDate = file.lastAccessDate ?? file.modificationDate else { return false } let age = now.timeIntervalSince(accessDate) return age > config.oldFileThreshold // 365 days}
Empty Folders
Detects: Directories with no contentsQuick Action: Prune Empty Folders
private func findEmptyFolders(at path: String) async -> [String] { var emptyFolders: [String] = [] let fm = FileManager.default guard let enumerator = fm.enumerator(atPath: path) else { return [] } for case let itemPath as String in enumerator { let fullPath = (path as NSString).appendingPathComponent(itemPath) var isDir: ObjCBool = false if fm.fileExists(atPath: fullPath, isDirectory: &isDir), isDir.boolValue { if let contents = try? fm.contentsOfDirectory(atPath: fullPath), contents.isEmpty { emptyFolders.append(fullPath) } } } return emptyFolders}
private func findBrokenSymlinks(at path: String) async -> [String] { var brokenLinks: [String] = [] let fm = FileManager.default guard let enumerator = fm.enumerator(atPath: path) else { return [] } for case let itemPath as String in enumerator { let fullPath = (path as NSString).appendingPathComponent(itemPath) if let destination = try? fm.destinationOfSymbolicLink(atPath: fullPath) { if !fm.fileExists(atPath: destination) { brokenLinks.append(fullPath) } } } return brokenLinks}
public struct CleanupHistoryItem: Codable { public let date: Date public let actionName: String public let affectedFilePaths: [String] public let type: ActionType // .move, .trash, .delete public let destinationPaths: [String]? // For undo}
private struct ScanCache { let signature: ScanSignature let directoryModDate: Date? let files: [FileItem] let timestamp: Date}private struct ScanSignature { let fileCount: Int let totalSize: Int64 let latestModified: Date?}
Cache Validity:
TTL: 30 seconds
Invalidated if directory modification date changes
Invalidated if signature changes (file count, size, latest mod)
Customize health checks in Settings → Workspace Health:
public struct WorkspaceHealthConfig: Codable { public var largeFileSizeThreshold: Int64 = 100_000_000 // 100MB public var oldFileThreshold: TimeInterval = 365 * 86400 // 1 year public var downloadClutterThreshold: TimeInterval = 30 * 86400 // 30 days public var minScreenshotCount: Int = 10 public var minDownloadCount: Int = 5 public var minUnorganizedCount: Int = 10 public var minOldFileCount: Int = 10 public var enabledChecks: Set<OpportunityType> public var ignoredPaths: [String]}
Automated insights notify you of important changes:
public struct HealthInsight: Codable { public let message: String public let details: String public let type: InsightType public let actionPrompt: String?}public enum InsightType { case growth // "Downloads grew by 2GB this week" case opportunity // "Found 50 duplicate files" case milestone // "Organized 1000 files this month" case suggestion // "Consider archiving old screenshots" case warning // "Rapid clutter growth detected"}