# PR#125: Artifact Store Pruning Split into Its Steps > **WARNING:** This document describes only the change applied in this PR. > It may already be outdated once the next PR is merged. > Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation. ## The Problem `FileArtifactStore.prune` is above the Kotlin target of 15, measured by the nightly mutation build of 2026-10-05: | Method | Mutation-CRAP | Complexity | Mutation score | |---------------------------|---------------|------------|----------------| | `FileArtifactStore.prune` | 23.50 | 7 | 0.30 | Most of its complexity and most of its 23 mutants were not its own. `lock.write` and `use` are inline functions, so their code is compiled into `prune`: - `lock.write` gives back every read hold before it takes the write lock, and takes them again afterwards, for a caller that upgrades from the read lock; a prune never holds it, so those loops never run and no test can tell their mutants apart; - `use` carries the closing of the stream with its exception handling. The few mutants that were its own — the journal line and its condition — no test looked at. ## Non-Goals - No change in behaviour: the same directories are deleted under the same lock, with the same journal line. - No change to `persist`, which takes the read lock as before. ## The Scenarios ### Feature: pruning removes what no result keeps, and says so #### Scenario#125.01: prune says in the journal what it removed, and is silent when nothing goes So that the journal tells which artifacts went, without a line for every poll that removed nothing. - **Given** an artifact directory with nested reports that no kept result references - **When** the store is pruned - **Then** the directory is deleted with everything in it, and its key is returned - **and** the journal says `pruned 1 artifact dir(s): []` - **When** the store is pruned again - **Then** nothing is returned and the journal says nothing - **When** the store never persisted anything - **Then** a prune removes nothing and creates no `branches` directory ##### Verified by - [FileArtifactStoreUnitTest](../../src/test/kotlin/de/hoennig/werkator/artifacts/FileArtifactStoreUnitTest.kt) `prune says in the journal what it removed, and is silent when nothing goes (Scenario#125.01)`, `prune of a store that never persisted anything removes nothing and creates nothing (Scenario#125.01)`, and the earlier prune tests, including `concurrent persists and a prune do not interfere` ## The Solution `prune` takes the write lock itself, `lock()` and `unlock()` in `finally`, and hands the work to `pruneUnreferenced`. `pruneUnreferenced` lists the directory completely through `childrenOf`, then deletes what no kept key names, then writes the journal line. Listing first and deleting afterwards also stops deleting entries out of a directory stream that is still being read. A local PIT run of `FileArtifactStore` afterwards: | Method | Mutation-CRAP | Complexity | Mutation score | |---------------------------------------|---------------|------------|----------------| | `FileArtifactStore.prune` | 1.04 | 1 | 0.67 | | `FileArtifactStore.pruneUnreferenced` | 5.05 | 5 | 0.88 | | `FileArtifactStore.childrenOf` | 1.30 | 1 | 0.33 | The highest score left in the class is `copyArtifactDirs` at 5.93. The project maximum is now `UiFormats.repoStatusLine` at 16.03, so the gate goes down from 24 to 17. ## Additional Changes - `maxMutationCrap` in `build.gradle.kts` lowered from 24 to 17. - Version 1.37.6 with its entry in `releases.html`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)