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
32 changes: 28 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,35 @@ tree because the binaries carry the commit they were built from, and
from the commit that carries the final documentation, never before it.

**Publishing a release** is a tag, and the tag needs explicit approval like
any push: bump `version` in `Cargo.toml` in the release commit, merge it,
any push: bump `version` in `Cargo.toml` in the release commit, turn the
`[Unreleased]` section of `CHANGELOG.md` into that version's -- headed
`## [x.y.z] - YYYY-MM-DD`, the date of that commit, so the file reads on
its own without the release page -- merge it,
tag that commit `vX.Y.Z`, push the tag. The release workflow runs the same
script on a runner and publishes the installer, the zip and their checksums.
Versions follow `docs/design/08-distribution.md`: the number moves only in
a release commit, and 1.0.0 waits for the criteria written there.
script on a runner and publishes the installer, the zip and their checksums,
with the changelog section as the notes; `build.ps1 release` and the
workflow both refuse a version the changelog does not carry, dated. Versions
follow `docs/design/08-distribution.md`: the number moves only in a release
commit, and 1.0.0 waits for the criteria written there.

**The changelog is written here, by the agent, and read by the maintainer
as a diff.** The workflow cannot summarise, and the release commit is made
on the maintainer's machine anyway, with the agent present. Rules for a
section, so that two agents write it the same way:

- Every line is something the person running the program can see or do,
in a sentence, under *Added*, *Changed*, *Fixed* or *Removed*. Never the
code, the module or the commit; those are on the release page under
*For the curious*, which the workflow writes from `git log`.
- One line per thing that changed for them, however many commits it took;
no line for what changed only for the people working here — tests,
refactors, the design record, this file.
- Written from the commits since the previous tag and the design pages
they touched, in `[Unreleased]` as the work lands or at the latest in the
release commit. A link to a lot page is allowed when the reasoning is
worth a click; no line needs one to make sense.
- Once a version is published its section is history: corrected in place
only for an error of fact, never rewritten for taste.

Three tests read this machine's registry — the Known Game List, the Game Bar
registration, the real sensor — which a GitHub-hosted Windows Server runner
Expand Down
92 changes: 92 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Changelog

What changed for the person running the program, one section per release,
newest first. Each line is something you can see or do; the commits behind
it are listed on the release page, under *For the curious*, and the
reasoning is in the [design record](docs/design/README.md). The shape is
[Keep a Changelog](https://keepachangelog.com/): *Added*, *Changed*,
*Fixed*, *Removed*.

Each released section is headed with its version and the date of its
release commit, so this file reads on its own; the release page has the
rest. The release workflow takes the section for the tag it is given and
refuses to publish without one, or without its date. `AGENTS.md` says how
a section is written.

## [Unreleased]

### Added

- **Check for updates**, in the icon's menu: asks GitHub whether a newer
release exists — the only time the program ever connects to anything,
and only when you click. The answer comes as a silent notification and
waits in the menu: *Up to date*, or **Download and install** beside
**What changed**, which opens the release page.
- **Download and install** fetches the new release, checks it against the
checksums the release publishes, and installs it — the installer for an
installed copy, the zip for one unpacked by hand. The icon disappears for
a second and comes back on the new version, which says so with a
notification at its first start.
- Updating in the middle of a game keeps your commands out of it: the old
watcher hands the game session to the new one, which picks it up where it
was. Nothing runs twice, and your gaming configuration is never switched
off and on again mid-game.
- `gamemode-executor update` does the same from a terminal; `update --check`
only asks.
- `gamemode-executor stop --handover` stops the watcher and leaves an open
game session to the next one, for anyone restarting it by hand during a
game.
- The log gains an `update` category: every request, every verdict and
every step of an install, with the reason when something fails.

### Changed

- The installer and the zip now carry the same four files: the two
executables, `LICENSE.txt` and `README.txt`. The zip no longer ships a
copy of the documentation; the readme links the pages and the recipes for
the exact build you have.
- Upgrading with the installer no longer closes a game session: the new
watcher resumes it.
- The recipes say to restart the watcher after copying a configuration —
`stop`, then `install-task` — which an installed copy needs to read the
new file.
- The release page says where the program was measured, Windows 11, and
what Windows 10 shares with it, rather than claiming both.

## [0.1.0] - 2026-09-18

The first release. Runs the executables you configure when a game starts
and when it stops, on Windows' own signal that a game is running.

### Added

- Detection through the Game Bar presence writer Windows starts for a game
and stops when it is gone: no list of games to maintain, nothing polled
while you play.
- A configuration file, `config.toml`, naming the commands to run on each
edge, in series or in parallel, with placeholders for the game's name and
path; `validate` to check it, `trigger start|stop` to try the commands
without a game.
- The game's name in the log and in the placeholders, refined a little way
into the session by asking the GPU which process is really drawing.
- A notification area icon — grey without a game, green with one — with a
menu: edit the configuration, open the log, open the documentation for
the exact build running, quit.
- A windowless twin, `gamemode-executorw.exe`, started at logon by a task
`install-task` registers; the console executable is the one you type
commands into.
- Recovery after a logoff, shutdown or crash mid-game: the stop commands
run at the next start, so you are not left on a gaming configuration.
- A log for two readers: plain sentences at `info`, the technical detail as
fields at `debug`.
- A per-user installer, `.msi`, that needs no administrator rights, writes a
starter configuration if you have none, registers the logon task and
starts the watcher; a `.zip` for anyone who would rather not run an
installer. Uninstalling removes the executables and the task and leaves
your configuration; `purge` removes every trace on request.
- Recipes with ready-made configurations: fan profiles with FanControl,
including the scheduled tasks a program that needs administrator rights
requires, and a Windows power plan.

[Unreleased]: https://github.com/Geeooff/GameModeExecutor/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/Geeooff/GameModeExecutor/releases/tag/v0.1.0
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ update, and shows nothing but a small icon in the notification area.
| [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. |
| [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. |

Expand Down
71 changes: 39 additions & 32 deletions docs/design/14-release-notes.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
# Lot 14 — Release notes people can read

**Status: proposed 2026-09-18**, 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.
**Status: in progress since 2026-09-18, the same 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 reads well from the moment it is published.

- [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
- [x] The rules of a section, in `AGENTS.md` — 2026-09-18
- [ ] `v0.1.0`'s notes rewritten once with its section, on approval
- [ ] The first release published this way: 0.2.0

**Goal.** Every release page reads, in a few lines, what changed for the
person running the program — and the same lines are what the updater's
Expand Down Expand Up @@ -42,41 +49,41 @@ way.
- **Published releases are rewritten once**, by hand, with the new shape,
when this lot lands — `v0.1.0` and whatever follows it before then.

## Who writes it — the maintainer's questions, 2026-09-18
## Who writes it — the maintainer's questions, and the answer

The page above says *by hand, in the release commit*. The maintainer's
reading, the same day, is that this is a task for the coding agent, not
for a person: a person forgets, and an agent can establish everything a
changelog needs from the commits between two tags and the design pages
they touch, and turn it into something coherent that a lay user can read.
Two constraints shape how, and both are noted here to be settled when the
lot is taken:
The first draft of this page said *by hand, in the release commit*. The
maintainer's reading, the same day, was that this is a task for the coding
agent, not for a person: a person forgets, and an agent can establish
everything a changelog needs from the commits between two tags and the
design pages they touch, and turn it into something coherent that a lay
user can read. Two constraints were raised with it:

- **The release workflow cannot call the agent.** A GitHub runner has
Copilot, not Claude, and the agent's memory of this project is local to
the maintainer's machine. So the notes cannot be written *by* the
release. What the workflow can do is publish a **placeholder** — the
install lines, the checksums, the commits — and mark the notes as
pending; the agent then rewrites them on the maintainer's request, from
the commits and the design record, and the maintainer publishes the
result. Whether that rewrite goes through `gh release edit` by the agent
on approval, or through a `CHANGELOG.md` the next release picks up, is
the choice to make.
- **The rules of the summary belong in `AGENTS.md`.** What a changelog
line is made of — what the user sees, in sentences, never the code;
which commits are one line and which are none; when a lot page is
linked and when nothing is; how the *For the curious* list relates to
the lines above it — has to be written down before an agent is asked to
follow it twice the same way. A framework there, the way the log
contract and the commit-message shape already are.
release; a placeholder the agent fills afterwards was one shape
considered.
- **The rules of the summary belong in `AGENTS.md`**, written down before
an agent is asked to follow them twice the same way.

**Settled the same evening, by noticing where the release commit is made:**
on the maintainer's machine, with the agent present. Nothing needs the
runner to summarise. The agent writes the section in `CHANGELOG.md` — in
`[Unreleased]` as the work lands, at the latest in the release commit —
from the commits since the previous tag and the design pages they touch;
the maintainer reads it as a diff in the release pull request, the way any
change is read; the workflow copies it onto the release page and refuses a
version without one, so no release is ever published with nothing to say.
No placeholder, no edit after publication. The rules are in `AGENTS.md`
beside the release procedure: what a line is, what earns none, when to
link, and that a published section is history.

Left open on purpose until then. What the page decided above stands where
it does not depend on the author: the notes sit first, the commits stay
below, published releases are rewritten once.
The one exception is the release published before this lot: `v0.1.0`'s
notes are rewritten once, by `gh release edit` on the maintainer's
approval, with the section the changelog now carries for it.

## To settle when it is taken
## To settle later

- The two questions above.
- Whether the updater should show the notes itself one day, through the
API's `body`, rather than open the page. Not before the notes are worth
showing.
2 changes: 1 addition & 1 deletion docs/design/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ session rather than when the code compiles. Each has its own page.
| 11 | [Documentation for the people who use it](11-user-documentation.md) | done |
| 12 | [Editing the configuration without breaking it](12-editing-on-a-copy.md) | proposed |
| 13 | [Updating](13-updating.md) | in progress |
| 14 | [Release notes people can read](14-release-notes.md) | proposed |
| 14 | [Release notes people can read](14-release-notes.md) | in progress |

**Dependency order:** 1 → 2 → 4 → 5 → 6 → 7, with 3 independent and 7 needing
both 3 and 6. Logging sits before the icon deliberately — the icon logs too,
Expand Down
12 changes: 7 additions & 5 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,13 @@ header**: a console program and a windowless one cannot be the same file, and
getting that backwards is invisible until someone sees a black window at logon.

A release proper is a tag. The version is bumped in `Cargo.toml` in the
release commit, that commit is tagged `vX.Y.Z`, and pushing the tag makes
the release workflow run this same script on a GitHub runner, then publish
the installer, the zip and their SHA-256 checksums as a GitHub release, with
notes listing the commits since the previous tag. Nothing is built or
uploaded by hand.
release commit, `CHANGELOG.md` gains that version's section, that commit
is tagged `vX.Y.Z`, and pushing the tag makes the release workflow run
this same script on a GitHub runner, then publish the installer, the zip
and their SHA-256 checksums as a GitHub release, with the changelog section
as the notes and the commits since the previous tag below it. A version
the changelog does not carry is refused, by `release` here and by the
workflow. Nothing is built or uploaded by hand.

`release` refuses a dirty tree, checks the commit stamped into the binaries is
the commit being built, checks the version block each executable carries,
Expand Down
21 changes: 20 additions & 1 deletion scripts/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ function Invoke-Tests {

Step "Documentation links resolve"
$broken = @()
$pages = @((Join-Path $root 'README.md'), (Join-Path $root 'AGENTS.md')) +
$pages = @((Join-Path $root 'README.md'), (Join-Path $root 'AGENTS.md'), (Join-Path $root 'CHANGELOG.md')) +
(Get-ChildItem (Join-Path $root 'docs') -Recurse -Filter '*.md' |
ForEach-Object { $_.FullName })
foreach ($page in $pages) {
Expand Down Expand Up @@ -263,11 +263,30 @@ function Invoke-Build {
}
}

# The section of CHANGELOG.md for one version, heading excluded, or nothing.
# The heading must carry the date of the release commit -- `## [x.y.z] -
# YYYY-MM-DD` -- so the file reads on its own, without the release page.
# The workflow's notes script reads it the same way, so a version without
# its dated section fails here first, on the machine that can still write it.
function Get-ChangelogSection([string] $Version) {
$text = Get-Content (Join-Path $root 'CHANGELOG.md') -Raw
$pattern = "(?ms)^## \[$([regex]::Escape($Version))\] - \d{4}-\d{2}-\d{2}[^\r\n]*\r?\n(.*?)(?=^## |\z)"
$match = [regex]::Match($text, $pattern)
if ($match.Success) { $match.Groups[1].Value.Trim() } else { $null }
}

function Invoke-Release {
$version = Get-Version
$stage = Join-Path $root "dist\GameModeExecutor-$version"
$zip = Join-Path $root "dist\GameModeExecutor-$version.zip"

Step "The changelog carries $version"
$section = Get-ChangelogSection $version
if (-not $section) {
Fail "CHANGELOG.md has no dated section for $version -- a release commit carries ``## [$version] - YYYY-MM-DD``, see AGENTS.md"
}
Write-Host " $((($section -split "`n") | Where-Object { $_ -match '^- ' }).Count) lines"

Step "Staging $version"
if (Test-Path $stage) { Remove-Item -Recurse -Force $stage }
New-Item -ItemType Directory -Force -Path $stage | Out-Null
Expand Down
Loading