@@ -27,25 +27,39 @@ public struct Cleaner: Sendable {
2727 private let deleter : FileDeleting
2828 private let inspector : ProcessInspecting
2929 private let logStore : CleanLogStore
30+ private let homeDirectory : String
3031 private let availableBytesReader : @Sendable ( URL ) -> Int64
3132 private let now : @Sendable ( ) -> Date
33+ private let ownerStoreProbe : @Sendable ( ) -> Result < OwnerCommandTarget , OwnerCommandFailure >
34+ private let knownOwnerStorePaths : [ String ]
35+ private let ownerHistoryOverflowed : Bool
3236
3337 public init (
3438 evaluator : RuleEvaluator ,
3539 deleter : FileDeleting ,
3640 inspector : ProcessInspecting ,
3741 logStore : CleanLogStore ,
42+ homeDirectory : String = NSHomeDirectory ( ) ,
3843 availableBytesReader : @escaping @Sendable ( URL ) -> Int64 = {
3944 VolumeReader . read ( fileURL : $0 ) . availableBytes
4045 } ,
41- now : @escaping @Sendable ( ) -> Date = { Date ( ) }
46+ now : @escaping @Sendable ( ) -> Date = { Date ( ) } ,
47+ ownerStoreProbe : ( @Sendable ( ) -> Result < OwnerCommandTarget , OwnerCommandFailure > ) ? = nil ,
48+ knownOwnerStorePaths : [ String ] = [ ] ,
49+ ownerHistoryOverflowed : Bool = false
4250 ) {
4351 self . evaluator = evaluator
4452 self . deleter = deleter
4553 self . inspector = inspector
4654 self . logStore = logStore
55+ self . homeDirectory = homeDirectory
4756 self . availableBytesReader = availableBytesReader
4857 self . now = now
58+ self . ownerStoreProbe = ownerStoreProbe ?? {
59+ OwnerCommandRunner ( recipe : . pnpmStorePrune , home : homeDirectory ) . probe ( )
60+ }
61+ self . knownOwnerStorePaths = knownOwnerStorePaths
62+ self . ownerHistoryOverflowed = ownerHistoryOverflowed
4963 }
5064
5165 /// 清理底线兜底:任何删除决定都必须经过可清理性校验。
@@ -82,6 +96,7 @@ public struct Cleaner: Sendable {
8296 }
8397 let availableBefore = availableBytesReader ( scan . volumeURL )
8498 let candidates = scan . items
99+ . filter { $0 . recipeID != OwnerCommandRecipe . pnpmStorePrune . id }
85100 . filter { ! RuleEvaluator . isPathProtected ( item : $0 , whitelistPaths : config . whitelistPaths ) }
86101 // 应用无法删除的手动项(Xcode/Finder)不进入自动/强制清理候选
87102 . filter { ! CleanupRationale . make ( for : $0 ) . isManual }
@@ -126,6 +141,21 @@ public struct Cleaner: Sendable {
126141 }
127142 return left . id < right . id
128143 }
144+ let knownStorePaths = knownOwnerStorePaths + scan . items
145+ . filter { $0 . recipeID == OwnerCommandRecipe . pnpmStorePrune . id }
146+ . map ( \. path )
147+ let needsOwnerProbe = candidates . contains { item in
148+ item . category == . packageManager
149+ || ( item . paths . isEmpty ? [ item . path ] : item . paths ) . contains { path in
150+ OwnerManagedPathGuard . isRecognizablePnpmLocation (
151+ path , homeDirectory : homeDirectory
152+ ) || knownStorePaths . contains {
153+ OwnerManagedPathGuard . overlaps ( path , storePath : $0 )
154+ }
155+ }
156+ }
157+ let ownerStore : Result < OwnerCommandTarget , OwnerCommandFailure > = needsOwnerProbe
158+ ? ownerStoreProbe ( ) : . failure ( . unavailable )
129159 var entries : [ CleanLogEntry ] = [ ]
130160 var freedTotal : Int64 = 0
131161 var below = true
@@ -167,6 +197,18 @@ public struct Cleaner: Sendable {
167197 let targetPaths = item . paths . isEmpty ? [ item . path ] : item . paths
168198 var itemFreed : Int64 = 0
169199 for target in targetPaths {
200+ guard OwnerManagedPathGuard . mayDelete (
201+ path : target , recipeID : item . recipeID , probe : ownerStore ,
202+ homeDirectory : homeDirectory , knownStorePaths : knownStorePaths ,
203+ historyOverflowed : ownerHistoryOverflowed
204+ ) else { continue }
205+ if item . recipeID == PackageManagerRecipes . familyID ,
206+ ! PackageManagerRecipes . isApprovedDefaultCachePath ( target , homeDirectory : homeDirectory ) {
207+ continue
208+ }
209+ guard ! PackageManagerRecipes . isLegacyPnpmPath ( target , homeDirectory : homeDirectory ) else {
210+ continue
211+ }
170212 // 单项失败(如 TCC 权限)不影响后续项:尽力而为,继续清理其他目标
171213 guard let deletion = try? deleter . deleteReturningResult (
172214 url : URL ( fileURLWithPath : target ) ,
0 commit comments