Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,6 @@ If your candidate site already uses any of these, the issue is fixed.
## 2025-05-15 - Array Materialization via sorted() on Lazy Sequences
**Learning:** `LazyFilterSequence` does not have a custom `sorted()` implementation. Calling `sorted()` on a `LazyFilterSequence` relies on the default `Sequence` extension, which completely consumes the sequence and materializes it into an array immediately. While chaining `.lazy.filter` before `.sorted()` avoids allocating a separate intermediate array just for the filtered results, `sorted()` itself forces array materialization regardless. Adding `.lazy` specifically *just* before `.sorted()` creates confusion because it suggests the sort operation itself is lazy (which is impossible, sorting requires the whole collection). The codebase considers `.lazy.filter.sorted` an anti-pattern.
**Action:** Do not propose adding `.lazy` before `.sorted()`. Ensure `.lazy` is only proposed when preceding operations that natively support lazy consumption like `.reduce`, `.count`, `.first`, `.contains`, or lazy `prefix`.
## 2025-05-16 - Refactoring Filter Methods for Iteration Optimization
**Learning:** Functions that encapsulate `.filter` logic and return a filtered array force intermediate array allocation when called within a loop, especially for potentially large datasets like process lists (~400-600 items). The `for ... where` idiom cannot be applied when the condition logic is hidden inside a method that returns the already filtered array.
**Action:** When a method exists purely to filter an array for subsequent iteration, refactor the method into a `static func` (or regular function) that returns a `Bool` evaluating a single element. This allows the caller to use an efficient `for element in collection where isCondition(element)` pattern, avoiding intermediate memory allocations.
20 changes: 9 additions & 11 deletions Sources/Cacheout/Memory/PredictiveEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/// engine.recordAvailableMB(mb, at: timestamp)
/// // Query predictions:
/// let tte = engine.predictTimeToExhaustion()
/// let growers = await engine.detectHighGrowthProcesses()
/// // Use PredictiveEngine.isHighGrowthProcess(entry) to filter growers
/// ```

import CacheoutShared
Expand Down Expand Up @@ -188,23 +188,21 @@ actor PredictiveEngine {

// MARK: - High-Growth Process Detection

/// Detect processes that have grown to and remain at their lifetime peak.
/// Check if a process has grown to and remains at its lifetime peak.
///
/// Filters for processes where:
/// Matches processes where:
/// - `leakIndicator < 1.05` (within 5% of lifetime peak)
/// - `physFootprint > 500MB` (significant memory consumer)
///
/// This is NOT leak detection -- it identifies processes with sustained growth
/// that are currently at or near their maximum footprint.
///
/// - Parameter processes: Process entries to evaluate.
/// - Returns: Processes matching high-growth criteria.
func detectHighGrowthProcesses(from processes: [ProcessEntryDTO]) -> [ProcessEntryDTO] {
processes.filter { entry in
entry.leakIndicator < Self.highGrowthMaxLeakIndicator
&& entry.leakIndicator > 0 // Exclude zero (no footprint data)
&& entry.physFootprint > Self.highGrowthMinFootprint
}
/// - Parameter entry: The process entry to evaluate.
/// - Returns: True if the process matches high-growth criteria.
static func isHighGrowthProcess(_ entry: ProcessEntryDTO) -> Bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the removed high-growth API or update tests

Replacing detectHighGrowthProcesses(from:) removes the only symbol that CacheoutTests still compiles against; Tests/CacheoutTests/PredictiveEngineTests.swift has multiple calls to await engine.detectHighGrowthProcesses(from:), and Package.swift includes that directory as the CacheoutTests target. As committed, any test build that includes this target fails before running, so either retain a compatibility wrapper or update those tests to use isHighGrowthProcess(_:).

Useful? React with πŸ‘Β / πŸ‘Ž.

entry.leakIndicator < Self.highGrowthMaxLeakIndicator
&& entry.leakIndicator > 0 // Exclude zero (no footprint data)
&& entry.physFootprint > Self.highGrowthMinFootprint
}

// MARK: - Process Scan Cache
Expand Down
4 changes: 2 additions & 2 deletions Sources/Cacheout/Memory/RecommendationEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ struct RecommendationEngine {
}

// high_growth_process
let highGrowth = await predictiveEngine.detectHighGrowthProcesses(from: scanResult.processes)
for proc in highGrowth {
// ⚑ Bolt: Use for-where clause to prevent intermediate array allocation
for proc in scanResult.processes where PredictiveEngine.isHighGrowthProcess(proc) {
let footprintGB = Double(proc.physFootprint) / (1024 * 1024 * 1024)
recommendations.append(Recommendation(
type: .highGrowthProcess,
Expand Down
Loading