Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ Krasivoe*.ps1
tools/proxmox/stand.config*.json
!tools/proxmox/stand.config.example.json
tools/proxmox/results/

# Mutation-testing scratch backups. Added after a runner's backup and one of its
# mutants were swept into a commit by `git add -A`: *.bak did not match .mutbak.
*.mutbak
87 changes: 87 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,93 @@ Windows Update driver listing, run-to-run delta and HTML report. See CLAUDE.md.

---

## [2.22] - 2026-07-23

A release about knowing when work is finished, and about the run surviving things that
have nothing to do with maintenance. Two of the three defects here are the same mistake in
different places: treating "I could not observe it" as an answer. Disk Cleanup was
declared incomplete because it had not exited, though it had demonstrably stopped working;
the installer accepted a PowerShell whose version it had failed to read.

### Fixed

- **A Disk Cleanup that had stopped doing anything was reported as still deleting, and
cost the full timeout.** Measured on a live workstation: `cleanmgr /sagerun` did its
work in about ten seconds, closed its window, and then stayed resident - no CPU, no I/O
on any of the three counters, all six threads waiting. Because the wait was written
against process exit, the run sat there for the remaining ~890 seconds and then set
`DiskCleanupPending` and warned that the figures were partial, having observed nothing
happening for most of that time. The wait now also ends on total stillness: no CPU and
no I/O across twelve consecutive checks, after activity was seen at least once. That is
reported as what it is - an observation, not proof of completion - so the new status is
called `idle-resident` and the log says what was measured. Not being able to measure
activity resets the counter rather than advancing it, so a machine where WMI is broken
keeps the old, patient behaviour instead of cutting cleanups short.
- **An unusable `-LogPath` killed the run before any maintenance happened.** The log
header was written with two bare `Out-File` calls placed before the block that
guarantees the result JSON and the summary. Six of seven bad paths make `Out-File` throw
a terminating error even at the default `ErrorActionPreference` (missing directory, path
is a directory, invalid characters, a colon in the name, an over-long path, an
unreachable UNC share), and the exception escaped the whole function. A log that cannot
be written is now a degraded run, as it always was everywhere else - never a failed one.
- **`install.ps1` accepted a PowerShell 7 whose version it could not read.** The check was
`if ($pwshVersion -and $pwshVersion -lt '7.1')`, so an unreadable version left the
comparison unevaluated and the installer carried on to pin an elevated desktop shortcut
to a binary nobody had established was suitable. This is the same fail-open shape as the
SHA256 verification that hid inside `if ($hashAsset)` until v2.17.
- **The banner's frame depended on the version being four characters long.** It was a
single literal block whose padding had been counted by hand; the title row measured 70
columns only because "2.21" happens to be four characters, so a version like 2.5 or
2.100 would have pushed its border out of line. The banner is now composed.

### Changed

- **Console output follows the window.** The desktop shortcut opens conhost at its
120-column default while winget's upgrade table needs about 140 on a localised system,
so the table wrapped and every wrapped row landed across WinClean's own output. The
window is now widened best-effort at startup (never beyond what the screen can show,
never failing the run if the host refuses - Windows Terminal does), and the frames are
derived from the actual console width instead of a literal 70 repeated in four places.
Bounded at both ends: never narrower than the historical 70, never wider than 90.
- **Every run ends through one code path.** A successful self-update used to call `exit`
itself, bypassing the block that writes the result JSON, shows the summary and releases
the log handle - so it hand-copied the parts someone remembered at the time. That is how
v2.21 came to ship two separate fixes to the same few lines. The list now exists once.
Exit codes are unchanged.
- **Storage Sense: the log now says which of two things happened** when it returns success
having freed nothing measurable - it is switched off, or it ran and there was simply
nothing left. On a regularly maintained machine the second happens every time and looked
like a malfunction. Deliberately reported rather than acted on: skipping Disk Cleanup on
that basis would silently stop cleaning Update Cleanup, memory dumps, Language Pack, old
ChkDsk files and Windows Error Reporting, none of which Storage Sense touches.

### Added

- `DiskCleanupStatus` in the result JSON - twelve values covering every way that step can
end, replacing a boolean that covered two different truths. `idle-resident` is named
after what is measured (a process seen working, then completely still) rather than after
the conclusion drawn from it, because that conclusion can be wrong and nothing downstream
is built to depend on it. `running`, `skipped-report-only` and `skipped-cleanup-group`
exist because "not-run" is documented as "the run aborted before reaching it", which is
untrue of a healthy preview, a deliberate skip, or a cleanup still in flight. The old
`failed` was split into `not-armed`, `start-failed` and `exit-nonzero` - only the last
means the machine may be partially cleaned.
- `SUPPORT.md`, a release-notes template in `docs/release-process.md`, and a section in
`CONTRIBUTING.md` describing what gets accepted and on what criteria.

### Tests

- 573 -> 702. Four mutation runs over the new logic (79 mutations); every one is caught.
Eight of them survived at first and each exposed a real gap rather than a wrong fix -
among them that `Should -Invoke -Times N` means *at least* N and cannot see a duplicate,
that the "unreadable activity" branch was never exercised, and that nothing pinned the
logo's alignment or the title's centring.
- Frame geometry is now checked at 70, 74, 80 and 90 columns. The smoke test runs with
output redirected, where the width is unknown and the frame falls back to 70 - so on its
own it could only ever have verified the width that existed before this release.

---

## [2.21] - 2026-07-23

A release about telling the truth on the way out. The self-update could change a file
Expand Down
55 changes: 45 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# WinClean - Инструкции для Claude

> Последнее обновление: 2026-07-23
> Текущая версия скрипта: 2.21 (ВЫПУЩЕНА 23.07.2026: GitHub Release с обоими ассетами + PSGallery 2.21 + e2e через опубликованный one-liner)
> Текущая версия скрипта: 2.22 (В РАБОТЕ, ветка `feature/v2.22`; последняя ВЫПУЩЕННАЯ - 2.21 от 23.07.2026)

---

Expand All @@ -11,7 +11,7 @@

| Параметр | Значение |
|----------|----------|
| **Версия** | 2.21 |
| **Версия** | 2.22 |
| **Язык** | PowerShell 7.1+ |
| **Платформа** | Windows 11 (23H2/24H2/25H2) |
| **Лицензия** | MIT |
Expand Down Expand Up @@ -43,12 +43,12 @@ CleanScript/
│ └── logo.svg # Логотип проекта
├── get.ps1 # Bootstrap: разовый запуск одной командой (irm | iex)
├── install.ps1 # Bootstrap: установка/обновление + ярлык (RunAs admin)
├── tests/ # Pester тесты (573 всего; счётчик - прогоном, не грепом)
│ ├── Helpers.Tests.ps1 # Unit-тесты helper-функций (280, дот-сорсят продукт - нужны права админа)
│ ├── Fixes.Tests.ps1 # Валидационные тесты исправлений (183)
├── tests/ # Pester тесты (702 всего; счётчик - прогоном, не грепом)
│ ├── Helpers.Tests.ps1 # Unit-тесты helper-функций (378, дот-сорсят продукт - нужны права админа)
│ ├── Fixes.Tests.ps1 # Валидационные тесты исправлений (206)
│ ├── Integration.Tests.ps1 # Интеграционные тесты в песочнице ФС (67, требуют admin)
│ ├── StandHelpers.Tests.ps1 # Чистые хелперы стенда: dead-man + версионный гейт ассертов (17, без admin)
│ └── Docs.Tests.ps1 # Гварды документации: нет тире + нет битых внутренних ссылок (26, без admin)
│ ├── StandHelpers.Tests.ps1 # Чистые хелперы стенда: dead-man + версионный гейт + таблица истинности report-гварда (23, без admin)
│ └── Docs.Tests.ps1 # Гварды документации: нет тире + нет битых ссылок (28, без admin; файлы ПЕРЕЧИСЛЯЮТСЯ, а не задаются списком)
├── tools/ # Тестовая инфраструктура (не публикуется в PSGallery)
│ ├── Invoke-ReleaseCheck.ps1 # 🔴 Единая проверка перед релизом (fail-closed)
│ ├── Invoke-SmokeTest.ps1 # Смоук: ReportOnly + JSON + геометрия рамок
Expand Down Expand Up @@ -198,7 +198,7 @@ Publish-PSResource -Path .\WinClean.ps1 -Repository PSGallery -ApiKey $env:PSGAL
**Проверки (4 job'а, счёт сверять gh run view --json jobs, а не этой строкой):**
1. **lint** - PSScriptAnalyzer (Error+Warning) через общий ools/Invoke-Lint.ps1 - тот же, что зовёт релиз-гейт
2. **syntax** - Проверка синтаксиса PowerShell
3. **test** - Pester тесты (573; интеграционные требуют admin - на GitHub runners это выполняется)
3. **test** - Pester тесты (702; интеграционные требуют admin - на GitHub runners это выполняется)
4. **smoke** - прогон -ReportOnly + геометрия рамок + result JSON

**Исключения PSScriptAnalyzer** (допустимые для CLI):
Expand All @@ -208,7 +208,7 @@ Publish-PSResource -Path .\WinClean.ps1 -Repository PSGallery -ApiKey $env:PSGAL

### Pester тесты (v2.13+)

- `tests/Helpers.Tests.ps1` - 280 unit-тестов (дот-сорсят WinClean.ps1), `tests/Fixes.Tests.ps1` - 183, `tests/Integration.Tests.ps1` - 67 (песочница ФС, требуют admin), `tests/StandHelpers.Tests.ps1` - 17 (без admin), `tests/Docs.Tests.ps1` - 26 (гварды доков, без admin)
- `tests/Helpers.Tests.ps1` - 378 unit-тестов (дот-сорсят WinClean.ps1), `tests/Fixes.Tests.ps1` - 206, `tests/Integration.Tests.ps1` - 67 (песочница ФС, требуют admin), `tests/StandHelpers.Tests.ps1` - 23 (без admin), `tests/Docs.Tests.ps1` - 28 (гварды доков, без admin)
- Особенности: функции в BeforeAll (не AST), regex для locale-независимости, отдельные It блоки

---
Expand All @@ -218,6 +218,41 @@ Publish-PSResource -Path .\WinClean.ps1 -Repository PSGallery -ApiKey $env:PSGAL
### В работе
- [ ] Публикация статьи на Хабре (`docs/_habr/habr-article.md` - текст готов, ждёт скриншоты). ⚠️ Текст писался под старую версию - сверить с v2.21 (появились get.ps1/install.ps1, стенд, ночные прогоны, очистка Driver Store, пофазное выполнение, публичный docs/, `-SkipDiskCleanup`)

### v2.22 - В РАБОТЕ (ветка `feature/v2.22`)

Эпик **`MyAI-bt2h`**. Свои задачи: `MyAI-zfwv` (P1), `MyAI-r6cd` (P2), `MyAI-1qtn` (P3).
Внешние замечания двух команд разобраны критически, подтверждённые заведены как
`MyAI-bt2h.1` (лог), `.2` (install.ps1), `.3` (единый выход), `.4` (доки).

🔴 **Два замечания ОТКЛОНЕНЫ проверкой, а не мнением - не переоткрывать:**

| Замечание | Чем опровергнуто |
|---|---|
| Приоритет `-in`/`-and` в `Invoke-StandTest.ps1` якобы пропускает `ReportNoCleanup` | AST показал группировку `($Mode -in @(...)) -and (-not ...)`, таблица истинности верна. Скобки добавлены только для читаемости, поведение прежнее |
| Публичный README на GitHub якобы отстаёт (6 браузеров, v2.16) | Запрошен `README.md` с `main` через GitHub API: 7 браузеров, `-SkipDiskCleanup`, диаграмма v2.21. Аудитор смотрел устаревший снимок. Вдобавок покрыто гейтом транзитивно (чистое дерево + синк с origin) |

🔴 **Отклонено и предложение `MyAI-1qtn`** (доверять включённому Storage Sense и не
запускать cleanmgr): проверка показала, что вооружаемые категории включают **Update
Cleanup, дампы памяти, Language Pack, старые ChkDsk, WER** - ничего из этого Storage Sense
не делает. Регулярно обслуживаемая машина молча перестала бы их чистить. Реализована
наблюдаемость (лог различает «выключен» и «включён, но чистить нечего»), решение не
тронуто; на это есть тест-страж в `Fixes.Tests.ps1`.

🔴 **Durable-уроки этого релиза:**
1. **Мутационная проверка нашла то, чего не нашли 600+ тестов.** Три прогона, 30 мутаций;
восемь сначала ВЫЖИЛИ, и каждая вскрыла реальный пробел, а не требовала подгонки:
`Should -Invoke -Times N` при N>0 означает **«не менее N»** и не видит дубля (`-Times 0`
при этом строгий - проверено экспериментом, поэтому тесты v2.21 править не пришлось);
ветка `catch` (сломанный WMI) не была покрыта вовсе; ничто не пиновало выравнивание
логотипа и центрирование заголовка. Одна мутация оказалась **эквивалентной** - записана
как таковая, а не «починена» подгонкой теста.
2. **Мои гипотезы опровергались замером трижды.** Ожидал, что `Out-File` даст
non-terminating error - **шесть из семи** плохих путей бросают terminating. Ожидал, что
`-Times 0` вакуумен - он строгий. Ожидал, что `Show-Banner` использует общую ширину - он
рисовал жёсткие 70.
3. **Греп-тест дважды поймал паттерн в собственном комментарии.** Оба раза чинилось
фильтрацией строк-комментариев, а не переписыванием комментария.

### v2.21 - ВЫПУЩЕНА 23.07.2026

Задачи `MyAI-3y5i` (P1), `MyAI-iodp`, `MyAI-7iej`. Релиз про честность на выходе: авто-обновление меняло не тот файл, который выполняется, и печатало «Update complete», а код возврата объявлял провалом успешный прогон, если на машине не было winget или сети. Каналы синхронны: `main`, GitHub Release v2.21 с обоими ассетами, **PSGallery 2.21**, CHANGELOG. Гейт **14/14** с `-VerifyPublished`, стенд Full на обеих VM (RU 1219 МБ, EN 2724 МБ), e2e через опубликованный one-liner (режим подтверждён из result JSON). 452 -> 573 теста, 21 мутация - все пойманы.
Expand Down Expand Up @@ -399,7 +434,7 @@ pwsh tools/Invoke-ReleaseCheck.ps1 # версия во всех
pwsh tools/Invoke-ReleaseCheck.ps1 -IncludeStand # + боевой прогон на VM (минуты)
pwsh tools/Invoke-ReleaseCheck.ps1 -VerifyPublished # ПОСЛЕ выпуска: ассеты релиза и SHA256

Invoke-Pester ./tests -Output Detailed # 573 Pester тестов (считать прогоном, не грепом)
Invoke-Pester ./tests -Output Detailed # 702 Pester тестов (считать прогоном, не грепом)
pwsh tools/Invoke-SmokeTest.ps1 # Смоук: ReportOnly + геометрия UI
pwsh tools/proxmox/Invoke-StandTest.ps1 -Mode Report # Стенд на Proxmox (RU=VM 190, EN: -ConfigPath ...en.json = VM 191)
# Ночная матрица: cron 03:30 на proxmos (/opt/winclean-stand, /etc/cron.d/winclean-stand), отчёт в Telegram
Expand Down
Loading
Loading