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
66 changes: 66 additions & 0 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
name: release
description: Prepare and publish a GameModeExecutor release (vX.Y.Z), from the checks before the release commit to the field checks after it. Use when the maintainer asks to prepare, cut or publish a release.
---

# Releasing GameModeExecutor

Every push, merge and tag below needs the maintainer's explicit approval, each
time.

## 1. Ready?

- `main` holds every lot meant for the release; nothing left on a branch.
- `CHANGELOG.md`'s `[Unreleased]` lists what the user sees, one short line per
change (the rules are in `AGENTS.md`).
- The user pages are short and current: README, *Getting started*, *How it
works*, the reference, the recipes. The design index's *Today* column says
where each lot stands.
- If the idle look or the in-game wait changed since the last release, measure
the installed watcher against it (`presence-probe cost`, `footprint`,
`menu-cost`) and put the figures in the record and the changelog.
- External links, by hand, FanControl's site and releases first —
`build.ps1` checks only the repository's own links and sections:

```bash
grep -rhoE 'https?://[^ )>`"]+' README.md CHANGELOG.md docs | sed 's/[.,;]$//' | sort -u
```

then `curl -s -o /dev/null -L -w '%{http_code}'` each; expect 200. A URL
ending in `)` is cut by the pattern: check it whole.
- The version, per `docs/design/08-distribution.md`: patch for fixes; minor
for a feature or, while 0.x, a contract change said in the notes. 1.0.0
waits for its criteria.

## 2. The release commit

On `release/X.Y.Z`, branched from `main`:

1. `version = "X.Y.Z"` in `Cargo.toml`; a build updates `Cargo.lock`.
2. `CHANGELOG.md`: `## [Unreleased]` becomes `## [X.Y.Z] - YYYY-MM-DD`, the
commit's date, with a one-sentence summary of the release under it; a new,
empty `## [Unreleased]` above; at the bottom, `[Unreleased]` compares
`vX.Y.Z...HEAD` and `[X.Y.Z]` the previous tag with `vX.Y.Z`.
3. `.\scripts\build.ps1 test`, then commit `Release X.Y.Z`, its body saying
what the release is and why this bump.
4. `.\scripts\build.ps1 release` from that commit: clean tree, stamped commit,
`dist\` with the installer, the zip and the recipes zip. Read the whole
output down to `release OK`.

## 3. Publish

1. Push the branch, open the PR, wait for CI, merge.
2. Tag the merge commit and push the tag alone:
`git tag vX.Y.Z <merge commit>`, `git push origin vX.Y.Z`.
3. The workflow builds and publishes. Check the release page: installer, zip,
recipes zip, `SHA256SUMS.txt`, and the changelog section as the notes.

## 4. After

- This machine updates as a user would: *Check for updates*, *Download and
install*; read the `update` lines and the new version's start in the log.
- The second machine: the maintainer updates it from the menu and brings its
log back.
- The field checks that waited for this release, listed in memory and in the
lot pages.
- One line in `.local/journal.md`.
17 changes: 4 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,10 @@ This file says how to work here. The *why* is in

- Run `test` before every commit and read its result; never commit in the
same block as a check that may fail.
- `release` needs a clean tree: the binaries carry their commit, and
`--version` links to that commit's `docs/getting-started.md`. Build it from
the commit with the final documentation.
- **Publishing a release** is a tag, approved like any push. Bump `version`
in `Cargo.toml`, turn `[Unreleased]` in `CHANGELOG.md` into
`## [x.y.z] - YYYY-MM-DD` dated the release commit, merge, tag `vX.Y.Z`,
push the tag. The workflow publishes the installer, the zips and their
checksums with that section as the notes, and refuses a version the
changelog does not carry, dated. Versions follow
`docs/design/08-distribution.md`; 1.0.0 waits for its criteria.
- Before each release, check the documentation's external links by hand —
FanControl's site and releases above all. `build.ps1` checks only the
repository's own links.
- **Releasing** follows [`.claude/skills/release/SKILL.md`](.claude/skills/release/SKILL.md),
the `release` skill: checks, release commit, build, tag, field checks
after. `release` builds from a clean tree, since the binaries carry their
commit and `--version` links to that commit's `docs/getting-started.md`.
- **The changelog is the agent's to write**, read by the maintainer as a
diff. Each line is something the user sees or does, under *Added*,
*Changed*, *Fixed* or *Removed* — never code, modules or commits, which
Expand Down
68 changes: 29 additions & 39 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,54 +17,44 @@ a section is written.

### Added

- **GameModeExecutor** in the Start menu, from the installer: it starts the
watcher again after *Quit*, and does nothing if one is already running.
- The recipes as a separate zip on each release,
`GameModeExecutor-recipes-<version>.zip`.
- The FanControl recipe links FanControl's site and its GitHub project, and
its scripts have a `.cmd` to double-click, which runs them even where
Windows refuses scripts.
- A warning at start when the last watcher ended during a game without
stopping: ended from Task Manager, crashed, or the machine lost power.
- **GameModeExecutor** in the Start menu, from the installer: starts the
watcher again after *Quit*.
- The recipes as their own zip on each release.
- The FanControl recipe links FanControl's site and GitHub, and its scripts
have a `.cmd` to double-click, which works even where Windows refuses
scripts.
- A warning at start when the last watcher ended mid-game without stopping:
Task Manager, a crash, a power cut.

### Changed

- The log turns over each day: one file a day,
`gamemode-executor.YYYY-MM-DD.log`, the last seven kept, or as many as
`log_days` in the configuration says. The day changes at midnight UTC;
the lines keep local time. *Open log* opens today's file, `purge` removes
them all, and the old `gamemode-executor.log` goes with the oldest.
- Telling a game from its launcher by what the graphics card is drawing no
longer adds some 3.5 MB to the watcher's memory for the rest of its run:
the counters are read the lighter way Windows offers for them.
- *Edit configuration*, *Open log* and *Documentation* no longer leave some
1.2 MB in the watcher for the rest of its run, and more for each kind of
file: a short-lived helper opens them and takes that cost with it when it
ends.
- At a logoff or shutdown mid-game the watcher no longer starts the stop
commands, which Windows refuses at that point, nor warns that they failed;
they run at the next logon, as they already did.
- One log file a day, `gamemode-executor.YYYY-MM-DD.log`, seven kept or
`log_days`. The day changes at midnight UTC; lines keep local time. *Open
log* opens today's.
- Naming a game from the graphics card no longer leaves 3.5 MB in the
watcher.
- *Edit configuration*, *Open log* and *Documentation* no longer leave 1.2 MB
or more in the watcher.
- At a logoff or shutdown mid-game, no stop command is tried (Windows refuses
them then) and no warning is written; they run at the next logon, as
before.
- The documentation is shorter: each page says what you need once.

### Fixed

- `purge` leaves nothing behind any more: the lines the uninstall itself
writes to the log are deleted once it is over, and the folder updates
are downloaded to goes too, where it used to stay, empty, and keep
`%LOCALAPPDATA%\GameModeExecutor` with it.
- A game still on its loading screen twenty seconds in no longer keeps its
launcher's name for the whole session: the watcher asks again every
twenty seconds, for two minutes at most.
- Windows ending the session is written in the log at the default level. A
shutdown could leave no line at all, and look like a watcher that had
vanished.
- A stop command that fails when a game ends runs again at the next start.
Windows makes it fail when the game closes with the shutdown, and the
gaming configuration then stayed on.
- `purge` leaves nothing behind: the uninstall's own log lines and the
updates folder go too.
- A game still loading twenty seconds in no longer keeps its launcher's name:
the watcher asks again every twenty seconds, two minutes at most.
- Windows ending the session is written in the log at the default level, so a
shutdown no longer looks like a vanished watcher.
- A stop command that fails when a game ends, as at a shutdown, runs again at
the next start instead of leaving the gaming configuration on.

### Removed

- The *Writing your own* recipe: the starter configuration's comments and the
reference already list every key and placeholder.
- The *Writing your own* recipe: the starter configuration and the reference
already list every key and placeholder.

## [0.3.0] - 2026-09-23

Expand Down
79 changes: 24 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,71 +1,40 @@
# GameModeExecutor

A small Windows program, written in Rust, that runs the executables you
configure when a game starts and when it stops.
A small Windows program, written in Rust, that runs the commands you configure
when a game starts and when it stops — fan curves, a power plan, RGB
lighting, anything with a command line.

```
game starts -> your commands
game ends -> your other commands
```
**No list of games to maintain.** Windows decides when a game is running: the
watcher follows the Game Bar's own signal and the games you marked in the Game
Bar. It does not poll while you play, needs no administrator rights, connects
to nothing unless you ask for an update, and shows only an icon beside the
clock.

It knows nothing about any particular tool. It launches programs with
arguments, which is enough to switch fan profiles, a power plan, RGB lighting,
or anything else with a command line.
## Install

**There is no list of games to maintain.** Windows itself decides when a game
is running — the watcher observes the Game Bar presence writer that Windows
starts for one, and follows the games you marked yourself in the Game Bar —
and it does not poll while you play. It runs as you, with no
administrator rights, connects to nothing unless you ask it to look for an
update, and shows nothing but a small icon in the notification area.
Run the `.msi` from the
[latest release](https://github.com/Geeooff/GameModeExecutor/releases/latest):
no administrator prompt, and the icon appears beside the clock. Right-click
it, *Edit configuration*, and say what to run. [Getting started](docs/getting-started.md)
covers the zip, updates and removal.

## Documentation

| Page | What it is for |
| Page | For |
| --- | --- |
| [Getting started](docs/getting-started.md) | Running in five minutes. Start here. |
| [Recipes](docs/recipes/) | Worked examples, one folder each with a ready-made `config.toml` — including fan profiles with FanControl, which needs one extra step because it requires administrator rights. Also on each release as a zip. |
| [How it works](docs/how-it-works.md) | For the curious: how it knows a game is running, why there are two executables, why the wait after you quit. No programming needed. |
| [Reference](docs/reference.md) | Commands, configuration fields, exit codes, the log contract, building. |
| [Changelog](CHANGELOG.md) | What each release changed for you, in plain sentences. |
| [Design record](docs/design/) | Why it is built this way: the decisions, the measurements behind them, and what is still open. |
| [AGENTS.md](AGENTS.md) | How to work in this repository — for coding agents, and for people. |
| [Recipes](docs/recipes/) | Worked examples with a ready-made `config.toml`, FanControl among them. |
| [How it works](docs/how-it-works.md) | How it knows a game is running, and why it behaves as it does. |
| [Reference](docs/reference.md) | Commands, settings, exit codes, the log, building. |
| [Changelog](CHANGELOG.md) | What each release changed. |
| [Design record](docs/design/) | The decisions and the measurements behind them. |
| [AGENTS.md](AGENTS.md) | How to work in this repository. |

## Status

In daily use on its author's machine, and released from
[the releases page](https://github.com/Geeooff/GameModeExecutor/releases).
Developed and measured on Windows 11 25H2. It relies on the Game Bar
component Windows ships by default, so a machine where Game Bar has been
removed will not detect anything.

## Install

Two self-contained executables, no runtime dependencies. `gamemode-executor.exe`
is the one you type commands into; `gamemode-executorw.exe` is the same program
with no console, started at logon by a task it registers for you.

From the [latest release](https://github.com/Geeooff/GameModeExecutor/releases/latest),
take the **`.msi`** and run it: per user, no administrator prompt, into
`%LOCALAPPDATA%\Programs\GameModeExecutor`. It writes a starter
configuration, registers the logon task and starts the watcher — the icon
appearing beside the clock is the confirmation. Right-click it, *Edit
configuration*, and say what to run.

The **`.zip`** holds the same executables for anyone who would rather unpack
them by hand; then:

```powershell
gamemode-executor init # write the starter config.toml
gamemode-executor install-task # start the watcher now and at every logon
```

Later releases install themselves: right-click the icon, **Check for
updates**, and the menu offers the newer version — nothing is checked
unless you ask. To remove it, *Programs and Features* takes the executables
and the logon task away and leaves your configuration; `gamemode-executor
purge` removes every trace. Building
from source is in the [reference](docs/reference.md#building-and-releasing).
In daily use, released on [GitHub](https://github.com/Geeooff/GameModeExecutor/releases).
Developed on Windows 11 25H2. It needs the Game Bar component Windows ships by
default.

## License

Expand Down
2 changes: 1 addition & 1 deletion config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ stop_delay = "2s"
# This far into a session, the per-process GPU counters are read and the
# candidate that is actually rendering wins. While there is no answer yet --
# nothing rendering, no match -- they are read again at the same interval, six
# times at most. Set to "0s" to skip it and keep whichever process matched
# attempts in all. Set to "0s" to skip it and keep whichever process matched
# first.
#
# This only ever changes the *name*: detection never depends on it, and being
Expand Down
10 changes: 3 additions & 7 deletions docs/design/08-distribution.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# Lot 8 — Distribution

**Status: done 2026-09-18.** `v0.1.0` was published that night by the
workflow alone, from the tag's commit, with the installer, the zip and their
checksums — which is the "done when" below. The lot was taken 2026-09-17/18
in the order listed; `scripts/build.ps1` was written first on purpose, since
a release that cannot be made by hand is not one CI can make either. The
last field check, the first install on a second machine, came in on the
evening of 2026-09-18 with its log; what it taught is below.
**Status: done 2026-09-18**, when the workflow alone published `v0.1.0`
from its tag: installer, zip and checksums. The first install on a second
machine came in the same evening; what it taught is below.

- [x] Create the public GitHub repository and push — done 2026-09-17, with approval
- [x] The three measurements below, on a minimal package, before any table is written — done 2026-09-17
Expand Down
18 changes: 5 additions & 13 deletions docs/design/13-updating.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
# Lot 13 — Updating

**Status: done 2026-09-23**, on the first update from a published 0.2.0
to the published 0.3.0, through the menu, below. Closed on the
maintainer's machine alone, by the maintainer's decision the same day —
*an acceptable shortcut*; the second machine updates from its own 0.2.0
that evening, and its log is recorded here if it says anything new.
Shipped in 0.2.0 on 2026-09-18. Decided 2026-09-17
to be a lot of its own rather than a tail of [Lot 8](08-distribution.md):
updating touches the "no network" non-goal, the tray menu and the running
process, and each of those deserves its own measurement. The shape below
was agreed with the maintainer on 2026-09-18, against `v0.1.0`, before a
line was written. The lot closes on the first update *from* 0.2.0, which
is the first version that carries it: 0.1.0 to 0.2.0 is done by hand, and
meets the Restart Manager's dialog once, as recorded below.
**Status: done 2026-09-23**, on the first update through the menu, from a
published 0.2.0 to 0.3.0 on the maintainer's machine. Shipped in 0.2.0. A lot
of its own rather than part of [Lot 8](08-distribution.md), because it
touches the "no network" non-goal, the menu and the running process. The
second machine updated itself the same way on 2026-10-01.

- [x] The session handed from one watcher to the next: `stop --handover`, and a start that resumes an open session instead of closing it — three scenarios in `engine/tests.rs`, 2026-09-18
- [x] `update`: the state machine, tested whole through a scripted feed; the WinHTTP feed and the CNG hash behind it — 2026-09-18, and no test ever calls GitHub: the network path is measured by hand, below
Expand Down
11 changes: 4 additions & 7 deletions docs/design/14-release-notes.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
# Lot 14 — Release notes people can read

**Status: done 2026-09-18, the day it was proposed**, on the maintainer's
remark after the first update ran through the menu: *What changed in
0.1.0* opened the release page, and the page said *First public release*
over a list of commits. That is a changelog for the people who wrote the
commits, not for the person who clicked. Taken before 0.2.0, so that
release read well from the moment it was published — it did, and 0.1.0's
page was rewritten the same evening.
**Status: done 2026-09-18, the day it was proposed.** The first release
page listed commits under *First public release*: notes for those who wrote
them, not for the person who clicked *What changed*. Done before 0.2.0, and
0.1.0's page rewritten the same evening.

- [x] `CHANGELOG.md`, with the sections for 0.1.0 and what 0.2.0 will carry — 2026-09-18
- [x] `release-notes.ps1` takes the section for the tag and refuses a version without one; `build.ps1 release` refuses it first, on the machine that can still write it — 2026-09-18
Expand Down
16 changes: 6 additions & 10 deletions docs/design/17-log-rotation.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
# Lot 17 — A log that turns over each day

**Status: done 2026-09-26.** Proposed 2026-09-24 by the maintainer, and
decided the same day: `tracing-appender`, with its constraints. Taken on
2026-09-25, after Lots 8 and 9, on the condition that a spike show the file
turning over without a restart — it does, below. Closed with three things
left to see, none a reason to reopen it: the updater's zip path at the next
release, the old single file pruned around 2026-10-01, and the next real
`purge` taking the updater's folder. The second was seen on 2026-10-16, in
the folder: `gamemode-executor.log` gone, eight dated files left, from
2026-10-06 to 2026-10-15 — ten calendar days, since a day the watcher wrote
nothing has no file.
**Status: done 2026-09-26**, with `tracing-appender`, once a spike showed
the file turning over without a restart. Left to see: the updater's zip path
at the next release, and the next real `purge` taking the updates folder. The
old single file was seen pruned on 2026-10-16: eight dated files left, from
2026-10-06 to 2026-10-15, ten calendar days, since a day with no line has no
file.

- [x] A spike, reproducible, of the turnover inside a running process and of what the library prunes, and when — 2026-09-25, below
- [x] The log written through `tracing-appender`'s `RollingFileAppender`, daily, synchronously, with every line as it is today — built 2026-09-25, below
Expand Down
Loading