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
68 changes: 68 additions & 0 deletions .claude/skills/prepare-release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
name: prepare-release
description: Prepare a new release of CleanMyPosts (version bump, release notes, release PR to main). Use this whenever the user mentions releasing, publishing, shipping, cutting a release, bumping the version, or preparing release notes — even if they don't say "release" explicitly. Decides the next SemVer version automatically and proceeds when there are new commits since the last tag.
---

# Prepare release

Prepare a new release of the CleanMyPosts desktop app. You decide the next
version yourself. You **prepare the repository and open the release PR — then
stop.** Reviewing, merging, and publishing are **admin-only** and not part of
your job:

- You do **not** merge the release PR (or enable auto-merge). The admin reviews
and merges every PR manually. `main` is a protected branch — direct pushes are
blocked, so every change ships through a PR.
- You do **not** trigger `deploy-release.yml`. The GitHub Release (installer
build, `v<version>` git tag, AutoUpdater feed) is a manual `workflow_dispatch`
the admin runs **after** merging the release PR.

## Workflow

1. **Check whether there is anything to release**
- Run the helper: `bash .claude/skills/prepare-release/scripts/check-updates.sh`
- Exit code **3** = nothing to release (no new commits since the last tag) → **stop**.
- Exit code **0** = new commits exist → continue.

2. **Determine current version & last tag**
- Read `<Version>` in `src/CleanMyPosts/CleanMyPosts.csproj`.
- Latest tag: `git tag --sort=-v:refname | head -1`.

3. **Decide the next version (SemVer)** from `git log --oneline <tag>..HEAD`:
- **MAJOR** breaking / behavior-removing changes · **MINOR** new features ·
**PATCH** fixes/docs/dependency bumps.
- Must be strictly greater than the current version. State it + a one-line reason.

4. **Confirm clean tree** — `git status --short`; if dirty, ask how to proceed.

5. **Branch from up-to-date `main`** — `git checkout main && git pull` then
`git checkout -b release/v<version>`. Release branches always cut from `main`.

6. **Build & test (green required)**
- `dotnet build CleanMyPosts.slnx -c Release`
- `dotnet test src/Tests/Tests.csproj -c Release --filter "TestCategory!=Long-Running"`
(same filter CI uses; long-running tests are excluded).

7. **Bump `<Version>`** in `src/CleanMyPosts/CleanMyPosts.csproj`.

8. **Extend docs** — update `README.md` etc. if the changes warrant it.

9. **Release notes** — copy `.claude/skills/prepare-release/assets/release-notes.md`
to `release-notes/v<version>.md` and fill in the `### What's Changed` bullets
with **user-facing** notes (not raw commit subjects). This file becomes the
GitHub Release body verbatim — `deploy-release.yml` reads it via `body_path`.

10. **Commit** (no tag) — stage the csproj, docs, and release notes; message
`release: v<version>`. Do **not** create a git tag — `deploy-release.yml`
creates `v<version>` at publish time.

11. **Push & open PR to main — then stop. Do NOT merge it.**
- `git push -u origin release/v<version>`
- `gh pr create --base main --head release/v<version> --title "release: v<version>" --body <notes>`
- The admin reviews and merges the PR. You never merge it or enable auto-merge.

12. **Report** the PR link and remind the admin: after **they** merge it, **they**
trigger **Deploy Release** manually (Actions → Deploy Release → Run workflow).
That workflow builds the installer, tags `v<version>`, publishes the GitHub
Release from `release-notes/v<version>.md`, and updates the AutoUpdater feed.
You do not trigger it.
9 changes: 9 additions & 0 deletions .claude/skills/prepare-release/assets/release-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### What's Changed

* {{ ... user-facing change ... }}

<!--
Write user-facing notes, not raw commit subjects.
Prefix bullets when it helps: "New Feature:", "Fix:", "Refactoring:".
This file becomes the GitHub Release body verbatim (see deploy-release.yml).
-->
27 changes: 27 additions & 0 deletions .claude/skills/prepare-release/scripts/check-updates.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Decides whether there is anything worth releasing.
#
# A release is warranted when there are new commits since the last tag
# (features, fixes, breaking changes).
#
# Exit 0 = something to release, 3 = nothing to release, 1 = error.
set -euo pipefail

# Run from the repository root so the tag/log lookups are unambiguous.
cd "$(git rev-parse --show-toplevel)"

last_tag="$(git tag --sort=-v:refname | head -1)"
if [[ -n "$last_tag" ]]; then
commits="$(git log "$last_tag"..HEAD --oneline 2>/dev/null || true)"
else
commits="$(git log --oneline 2>/dev/null || true)"
fi

if [[ -z "$commits" ]]; then
echo "No new commits since ${last_tag:-the start} — nothing to release."
exit 3
fi

echo "Releasable changes since ${last_tag:-the start}:"
echo "$commits"
exit 0
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
*.sh text eol=lf

###############################################################################
# Set default behavior for command prompt diff.
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: "CI"
on:
push:
branches: [ develop, main ]
pull_request:
branches: [ main ]

permissions:
contents: read
Expand All @@ -15,7 +17,7 @@ jobs:
runs-on: windows-latest

env:
Solution: "src/CleanMyPosts.slnx"
Solution: "CleanMyPosts.slnx"
Test_Project: "src/Tests/Tests.csproj"
FORCE_COLOR: "true"
DOTNET_LOGGING__CONSOLE__COLORBEHAVIOR: Enabled
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: windows-latest

env:
Solution: "src/CleanMyPosts.slnx"
Solution: "CleanMyPosts.slnx"
UI_Project: "src/CleanMyPosts/CleanMyPosts.csproj"
Test_Project: "src/Tests/Tests.csproj"
Installer_Script: "installer/Installer.iss"
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ MigrationBackup/
FodyWeavers.xsd

# local cmd files
*.cmd
*local.cmd

# Installer releases
installer/Output
Expand Down
56 changes: 56 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# CLAUDE.md

CleanMyPosts is a **.NET 10 WPF Windows desktop app** that bulk-deletes posts,
reposts, replies, likes and followings on X (Twitter) and comments/likes on
YouTube, by driving an embedded WebView2 browser with injected JavaScript.

## Layout

```
CleanMyPosts.slnx # solution (repo root)
src/CleanMyPosts/CleanMyPosts.csproj # WPF app (WebView2 + MahApps.Metro + CommunityToolkit.Mvvm)
Scripts/ # injected JS (delete-all-*.js)
Services/ ViewModels/ Views/ # MVVM
src/Tests/Tests.csproj # xUnit tests (Xunit.StaFact for UI)
installer/Installer.iss # Inno Setup installer script
release-notes/vX.Y.Z.md # GitHub Release body per version
.github/workflows/ci.yml # build + test on push/PR to main
.github/workflows/deploy-release.yml # manual: installer, tag, GitHub Release, AutoUpdater feed
```

## Build & test

```bash
dotnet build CleanMyPosts.slnx -c Release
dotnet test src/Tests/Tests.csproj -c Release --filter "TestCategory!=Long-Running"
```

The filter mirrors CI, which excludes long-running tests.

## Branching

`main` is **protected** — direct pushes are blocked (including for admins) and
the `build` status check must pass. Every change ships through a PR against
`main`. Use `feature/<name>` for functionality, `fix/<name>` for bug fixes, and
`release/vX.Y.Z` for release prep.

## Versioning & release

- Version lives in `src/CleanMyPosts/CleanMyPosts.csproj` (`<Version>`).
- Do not bump `<Version>` without also adding `release-notes/v<version>.md` —
that file becomes the GitHub Release body (`deploy-release.yml` reads it via
`body_path`).
- **Do not create git tags manually.** `deploy-release.yml` creates the
`v<version>` tag when it publishes.
- The full release-prep flow (decide SemVer, branch, build/test, bump version,
write release notes, open the PR to `main`) is encoded in the
**`prepare-release`** skill at `.claude/skills/prepare-release/`. Run it via
Claude Code (`/prepare-release`) when cutting a release; it prepares the PR
only. Merging the PR and running **Deploy Release** (`workflow_dispatch`) are
manual, admin-only steps.

## Code style

Global conventions in `~/.claude/CLAUDE.md` apply: comments only when the *why*
is non-obvious, `var` when the type is evident, expression-bodied members,
`is null` / `is not null`, `_camelCase` private fields, no `#region`.
28 changes: 28 additions & 0 deletions CleanMyPosts.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Solution>
<Folder Name="/Solution Items/">
<File Path=".editorconfig" />
<File Path=".gitattributes" />
<File Path=".gitignore" />
<File Path="CLAUDE.md" />
<File Path="CODE_OF_CONDUCT.md" />
<File Path="CONTRIBUTING.md" />
<File Path="LICENSE.txt" />
<File Path="README.md" />
<File Path="SECURITY.md" />
<File Path="THIRD_PARTY_LICENSES.txt" />
</Folder>
<Folder Name="/Solution Items/.claude/prepare-release/">
<File Path=".claude/skills/prepare-release/SKILL.md" />
<File Path=".claude/skills/prepare-release/assets/release-notes.md" />
<File Path=".claude/skills/prepare-release/scripts/check-updates.sh" />
</Folder>
<Folder Name="/Solution Items/installer/">
<File Path="installer/Installer.iss" />
</Folder>
<Folder Name="/Solution Items/workflows/">
<File Path=".github/workflows/ci.yml" />
<File Path=".github/workflows/deploy-release.yml" />
</Folder>
<Project Path="src/CleanMyPosts/CleanMyPosts.csproj" />
<Project Path="src/Tests/Tests.csproj" />
</Solution>
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
![Banner](https://raw.githubusercontent.com/thorstenalpers/CleanMyPosts/main/src/CleanMyPosts/Assets/banner.png)

[![Windows](https://img.shields.io/badge/platform-Windows-blue)](#)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE.txt)
[![CI Tests](https://github.com/thorstenalpers/CleanMyPosts/actions/workflows/ci.yml/badge.svg)](https://github.com/thorstenalpers/CleanMyPosts/actions/workflows/ci.yml)
[![Donate](https://img.shields.io/badge/donate-PayPal-yellow)](https://www.paypal.com/donate/?hosted_button_id=QYHGE9LA9SNAN)
[![Star this repo](https://img.shields.io/github/stars/thorstenalpers/CleanMyPosts.svg?style=social&label=Star&maxAge=60)](https://github.com/thorstenalpers/CleanMyPosts)
[![CI](https://img.shields.io/github/actions/workflow/status/thorstenalpers/CleanMyPosts/ci.yml?branch=main&style=flat-square&logo=githubactions&logoColor=white&label=CI)](https://github.com/thorstenalpers/CleanMyPosts/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/thorstenalpers/CleanMyPosts?style=flat-square&logo=github&label=release)](https://github.com/thorstenalpers/CleanMyPosts/releases/latest)
[![Downloads](https://img.shields.io/github/downloads/thorstenalpers/CleanMyPosts/total?style=flat-square&logo=github&label=downloads)](https://github.com/thorstenalpers/CleanMyPosts/releases)
[![Platform](https://img.shields.io/badge/platform-Windows-0078D6?style=flat-square&logo=windows&logoColor=white)](https://github.com/thorstenalpers/CleanMyPosts/releases)
[![Donate](https://img.shields.io/badge/donate-PayPal-00457C?style=flat-square&logo=paypal&logoColor=white)](https://www.paypal.com/donate/?hosted_button_id=QYHGE9LA9SNAN)
[![Stars](https://img.shields.io/github/stars/thorstenalpers/CleanMyPosts?style=flat-square&logo=github&label=stars)](https://github.com/thorstenalpers/CleanMyPosts)

**CleanMyPosts** is a lightweight Windows desktop app that securely deletes all posts, reposts, replies, likes, and followings from your X (formerly Twitter) account, as well as YouTube comments, in bulk using browser automation.

Expand Down
22 changes: 22 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Security Policy

## Reporting a vulnerability

Please report security vulnerabilities **privately** — do not open a public
issue or pull request for them.

Use GitHub's private reporting flow:
[**Report a vulnerability**](https://github.com/thorstenalpers/CleanMyPosts/security/advisories/new).

Please include enough detail to reproduce the issue (affected version, a minimal
repro or proof of concept, and the impact). You can expect an initial response
within a few days. Once a fix is available it will be published as a new release.

## Supported versions

Only the latest release receives security fixes.

| Version | Supported |
| ------- | --------- |
| latest | ✅ |
| older | ❌ |
14 changes: 14 additions & 0 deletions build.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cd C:\Sources\CleanMyPosts\src\UI

REM dotnet clean ..\CleanMyPosts.sln
REM dotnet restore ..\CleanMyPosts.sln

REM dotnet build ..\CleanMyPosts.sln --configuration Release --no-restore

REM dotnet publish UI.csproj -c Release -r win-x64 --self-contained true

dotnet clean ..\CleanMyPosts.sln
REM dotnet publish ..\CleanMyPosts.sln -c Release -r win-x64 --self-contained true
dotnet publish ..\CleanMyPosts.sln -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishTrimmed=false

cd C:\Sources\CleanMyPosts\
57 changes: 57 additions & 0 deletions clean.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
@echo off
setlocal enabledelayedexpansion

taskkill /im dotnet.exe /f


set "rootDir=C:\Sources\CleanMyPosts"
set "vsDir=%rootDir%\.vs"
set "testresultDir=%rootDir%\TestResults"

rd /s /q "%vsDir%"

rd /s /q "%testresultDir%"


:: Iterate over all subdirectories under %rootDir%
for /d /r "%rootDir%" %%a in (*) do (
if /i "%%~nxa"=="bin" (
call :DeleteBinObj "%%a" "bin"
) else if /i "%%~nxa"=="obj" (
call :DeleteBinObj "%%a" "obj"
)
)

echo Finished.
REM pause
exit /b

:DeleteBinObj
setlocal
set "currentDir=%~1"
set "skipDelete="

:: Check for .git, node_modules, or _archive in the current directory path
for %%b in ("%currentDir%") do (
for %%c in (".git" "node_modules" "_archive") do (
echo %%b | findstr /i "%%c" >nul
if not errorlevel 1 (
set "skipDelete=yes"
goto :skipDeletion
)
)
)

:skipDeletion
if not defined skipDelete (
if /i "%~2"=="bin" (
echo Deleting folder: "%currentDir%"
rd /s /q "%currentDir%"
) else if /i "%~2"=="obj" (
echo Deleting folder: "%currentDir%"
rd /s /q "%currentDir%"
)
)

endlocal

3 changes: 3 additions & 0 deletions create-licenses.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
echo ["Tests.csproj"] > exclude.json
dotnet-project-licenses -i src/CleanMyPosts.sln --projects-filter exclude.json -o --outfile THIRD_PARTY_LICENSES.txt
rm exclude.json
19 changes: 0 additions & 19 deletions src/CleanMyPosts.slnx

This file was deleted.

Loading