Skip to content

Changelog

All notable changes, by release. Each pull request that changes something for users describes it in a file of its own in changelog.d/, or under Unreleased. The release bot makes that text the section of the next release, or lists the pull requests when there is none.

Unreleased

0.12.0 (2026-10-14)

Features

  • Pull requests no longer conflict over the changelog: every feat, fix or perf pull request wrote its entry under ## Unreleased in CHANGELOG.md, so each merge, and each release, which empties Unreleased, conflicted with every pull request that waited: its auto-merge stopped until someone merged main by hand, and every check ran again. All 12 conflicts that the branch bot ever labeled in the blueprint came from there, and a merge of main once put an entry into the section of a release that was out already (#183). A pull request now writes its entry into a file of its own, a fragment in changelog.d/ named after its branch, such as changelog.d/dark-mode.md, below the heading of its kind of change, such as ### Features or ### Bug fixes; a new file conflicts with nothing. The check changelog accepts it and checks it as the release takes it in: a typo such as ### Fixes fails at the fragment, with the heading it likely meant, and python3 .github/blueprint.py fragments checks the fragments on your machine. Since the release reads every fragment, the check also checks those of a pull request that needs no entry, such as a docs: pull request or one of a bot. The release bot moves every fragment and the text of Unreleased into the section of the next release, each kind of change below one heading, and deletes the fragments in the same commit of the release pull request; a prerelease leaves them to the release, and the notes of a beta show them. Entries under Unreleased keep working, and the link check of a pull request leaves the fragments out, as it does the changelog. Every repository gets the folder changelog.d/ with the update, which a Nextcloud app leaves out of its package, CONTRIBUTING.md shows the format, and the upstream bot of a Nextcloud app writes its entry into a fragment too; the headings that a fragment may use are those of the blueprint, those of Keep a Changelog and every other one that your CHANGELOG.md has already. The check runs blueprint.py of your default branch on the text of the fragments and nothing of the pull request, also for a pull request from a fork.
  • Every check that fails explains itself, also on a pull request from a fork: a contributor whose pull request failed a check saw a red cross and the raw log of a tool. Now the new PR coach says what the check found, why that matters and how to fix it, with the commands to run and a link to read more: in the summary of the check, as its annotation, and after the checks in one comment on the pull request, which follows every push and says so once every check passes; a check that was cancelled, as one that ran out of time, hasn't passed. It works for pull requests from forks as well, whose own runs can't comment, and suggests a title that passes when the title is no Conventional Commit. scripts/check.sh explains the step that stopped it in the same words on your machine, and in the CI the problems of the text files, of Ruff and of actionlint show at their lines in the files of the pull request. The coach reads the checks from the API and runs no code of the pull request; text that a pull request controls appears only as code. The explanations of every check of the blueprint are in .github/checks.toml; explain the checks of your own project, such as end-to-end tests that you made required, in the new .github/checks.project.toml, and python3 .github/blueprint.py coach verify lists any without one. The comment follows the workflows of the checks that the blueprint sets up; to have it follow a workflow of your own, such as one of visual tests, name it in the answer test_workflows of .copier-answers.yml. A GitHub Action keeps its .github/workflows/action-test.yml, which the update leaves alone: to have its check action explain a failure as well, add the last step of the job action of the blueprint's copy to yours, the step Explain the failure with if: failure(), CHECK: action in its env: and python3 .github/blueprint.py coach explain "$CHECK" as its run:.
  • Pages in one column, so that they read well on a phone and in the GitHub app: the GitHub app keeps an HTML table that sets text beside a picture, and squeezes it into narrow columns with a strip of text and a tiny picture. scripts/check.sh now fails on such a table and on a cell with a width, such as <td width="55%">, in a README, a CONTRIBUTING file or a page of docs/; each finding names its file and line and says how to fix it, and in the CI the pull request shows it at that line. python3 .github/blueprint.py pages --fix fixes them for you: it puts the cells of a layout table one below the other, the text before its picture and each caption before its picture, and leaves out the widths of cells. CONTRIBUTING.md describes the convention: tables for tabular data alone, pictures and GIFs at full width below the text that they illustrate, and long lists in <details>. A repository whose pages lay out pictures in tables fails the check after this update; python3 .github/blueprint.py pages --fix and a commit make it pass.
  • Coding agents follow the rules of each repository: every repository gets an AGENTS.md, which Claude Code, Codex, GitHub Copilot and Cursor read: the commands that set up the tools and run the checks and tests of its kind, the titles of pull requests and the versions they make, the fragment of the changelog in changelog.d/, the diagrams as pictures of their sources, the sign-off, and what never to do to get a check green. It also says which files belong to the project and which to the blueprint, so that an agent changes a workflow or scripts/check.sh in the blueprint instead of in one repository; a test keeps that list equal to the files that an update never overwrites. CLAUDE.md brings AGENTS.md to Claude Code; the package of a Nextcloud app leaves both out. The notes of a project, such as its architecture and the commands of its own checks, go into .github/agents.project.md, which the update adds once and then leaves to the project. A repository that already has an AGENTS.md or a CLAUDE.md gets the blueprint's in the update pull request, with conflict markers where the two differ: move its own notes into .github/agents.project.md. Decision 0015 says why.
  • A bot that fails tells you, in one issue, and closes it once it passes again: GitHub mails the failure of a scheduled run only to whoever last changed its schedule, and that of another run to whoever started it, here the release app for most runs, so Findings, Mutation and Renovate failed for days with nobody told. The new bot-health bot (bot-health.yml) looks at the end of every run of a bot or a scheduled workflow on main: a first failure runs its failed jobs once more where a second try can pass, and a failure that stays opens one issue per workflow, such as Release fails on main, with the label bot-failure, the failed jobs, the step that failed and the first error of each. A later failure in the same place updates the issue quietly, one in another place adds a comment, and the next run that passes closes it. Runs of pull requests and forks change nothing. Every night it also looks at the latest run of every workflow, the CI of main and the workflows of your project too, and starts a workflow whose schedule GitHub missed or switched off. The label comes from the bot; to keep its color in .github/labels.toml, which belongs to your project, add the bot-failure label of the blueprint's.
  • Renovate's warnings reach an issue: a lock file that Renovate can't read only cost a warning in its log, in a run that passed. The Renovate workflow of the blueprint now keeps one issue, Renovate logs problems, with the warnings and errors of its latest run by repository, public ones only, and closes it after three runs in a row without one.

Bug fixes

  • scripts/check.sh finds CRLF line endings on Windows too: the grep of Git for Windows drops the carriage return at the end of each line, so the step Text files passed a file with CRLF line endings on Windows that it fails on Linux and macOS. It now fails there as well, so the checks on your machine are the same everywhere, and stricter than the CI for a file that Git would still commit with LF line endings, as .gitattributes asks. sed -i 's/\r$//' FILE, or an editor set up by .editorconfig, gives the file LF line endings again.
  • Fragments of the changelog reach the release as the check takes them in: a heading with one space too many, such as ### Features, passed the check changelog, but the release gave it a second Features heading of its own, after every other kind of change; it now joins the entries of its kind. A title such as # Notes in a fragment, which the release copied into CHANGELOG.md and into the release notes, now fails the check, as a heading ## … does. The check asks GitHub up to three times for the files of a pull request, so a moment in which GitHub fails no longer fails it, also for the pull requests of bots, which need no entry. CONTRIBUTING.md now says to name a fragment after the branch without its type, such as changelog.d/dark-mode.md for the branch feat/dark-mode, since a name with a slash is no fragment, and the upstream bot of a Nextcloud app says in its description that it writes a fragment. blueprint.py unreleased, which no bot uses any more since the upstream bot writes a fragment, is gone.
  • The diagram bot keeps its pull request current when main changes a workflow, and says when it can't help: the Diagrams workflow moves the branch bot/diagrams of its open pull request to a commit on the newest main, and GitHub refused that move when main had changed a workflow since, as every update of the blueprint does. The bot then failed every week and never fixed main, until a person closed its pull request. Its token may now move the branch across changes of workflows, while its commit still holds only diagrams and pages, and the branch on which it makes the commit starts anew in every run, so that one which a failed run left behind can't stop the next. In a repository without the release app, the run now warns that no bot opens the pull request and names the command that fixes main, where it found the diagrams and said nothing. When the diagram bot can't commit onto a pull request, it now says so in a comment, with the command, where the check promised its commit and nothing came. The CONTRIBUTING.md of every repository now names the colors of the diagrams, and that a diagram in a README with a German twin comes in both languages.
  • The blueprint bot updates only to a release that the release workflow of the blueprint made: it took the latest published release of the blueprint as it was, so anyone who could push a tag to the blueprint, with the token of a job that runs a third-party action or with a leaked token, could have brought workflows of their own to every repository made from it, which the update would then have merged. Before every update, the bot now checks that the release is immutable, that its tag points at a commit of the blueprint's main, and that the release workflow of the blueprint signed the tag with gitsign as the tag of that very version; any other release fails its run before Copier renders anything, and the summary of the run says why and what to do. A question that GitHub or Sigstore doesn't answer is asked three times; then the run fails without refusing the release, and the next run checks it again. A blueprint that .copier-answers.yml names elsewhere than on GitHub can't be verified there, so the bot no longer updates from it. The update that brings this release still runs the bot before it; every later update is verified. Decision 0020 says why.
  • Only the release app creates release tags: the rules of the release tags blocked their update and deletion, not their creation, so any token that may push could create the tag of a new version. The settings bot now adds the ruleset Release tag creation, which lets only the release app create a v*.*.* tag, where the new variable RELEASE_AUTOMATION_APP_ID in .github/repository.toml names the app, as it does from this release on; the app, like everyone else, still can't move or delete one. Releases and betas create their tags with the app as before. A project with a release app of its own sets RELEASE_AUTOMATION_APP_ID and RELEASE_AUTOMATION_CLIENT_ID to that app in .github/repository.project.toml; if it sets only the client ID there, the settings bot stops and says so, instead of a ruleset that would let only the blueprint's app create its tags. SECURITY.md now says how a repository made from the blueprint receives a fix, and how to check the signature of a release tag yourself.

0.11.1 (2026-10-13)

Performance

  • ci: narrow Psalm's extraFiles and cache Composer, php-cs-fixer and pip (#192) (510c461)

0.11.0 (2026-10-13)

Features

  • Diagrams that every reader sees, also in the GitHub app: the GitHub app for iOS and Android, HACS, the Nextcloud App Store, PyPI and the notification e-mails draw no Mermaid and showed every diagram as code. A diagram is now a Mermaid source in docs/diagrams/, and the new bash scripts/diagrams.sh draws it with mermaid-cli in Docker as an SVG for the light theme, on a white card that stays readable in a dark app, and one for the dark theme. The Markdown shows them with a <picture>, whose text for screen readers is the title and the description of the diagram, and the website still draws the source live, in the colors of its theme. The required check diagrams of the Lint workflow draws the pictures anew in every pull request that changes a diagram or adds a mermaid block; on a branch of the repository, the diagram bot commits the sources and pictures onto it, with a comment that names the pages it changed, so a contributor needs no Docker, and a new version of mermaid-cli from Renovate is drawn on its branch the same way. Any other pull request passes the check at once. On main, the new Diagrams workflow draws every diagram after each change of a diagram or of the renderer and every week, and finds a new mermaid block after any other change of a page without Docker. It keeps one pull request from bot/diagrams with what is still missing, which merges itself and closes when main is fixed otherwise, and a failed run opens an issue, which the weekly run closes once the bot passes again. scripts/check.sh checks without Docker that every source has its pictures and every picture its source, and that no mermaid block is left once a page shows a picture of a diagram. Until then, a mermaid block is a warning, and sources that a repository already keeps in docs/diagrams/ and draws its own way stay as they are. Your diagrams move by themselves: after the update, the Diagrams workflow opens the pull request that turns every mermaid block of the Markdown into a source, named after its accTitle, and its picture; a diagram without accTitle takes the heading above it for its name and, with the labels of its nodes, for the text of its picture; a diagram of a German page takes the name of the English one with .de. To move them yourself, run bash scripts/diagrams.sh --extract with Docker and commit what it changes. The labels of a picture are text, so --extract turns <b> and <i> into Markdown and drops other HTML such as <code>, and <br> still breaks a line. A picture is a path from its page, which github.com and the GitHub app follow; HACS and PyPI, which show a README outside of the repository, don't, so a README published there needs absolute links to its pictures, which the checks and the website accept. CONTRIBUTING.md describes the diagrams, and decision 0016 says why.

Bug fixes

  • Pull requests no longer fail the sign-off check for commits that are already on main: the check dco read the commits since the base that GitHub named for the pull request, and that base can be older than the commit of main that the branch starts on, as with the release pull request, which the release bot builds again on a newer main (Dennis-Otto/paperless-sync#96). The check then also read squash commits of main, which GitHub authors with the noreply address of the account while their sign-off names another, and failed. It now reads only the commits of the pull request that the base branch doesn't have; a commit of the pull request without a sign-off still fails it. The hints at the spelling and the style and the link check of a pull request now look the same way at the documents that the pull request changes, no longer at those that only main changed.

Performance

  • The checks of a Nextcloud app take a fraction of the time: Psalm and its taint analysis read all of vendor/, some 5,000 files of Psalm, PHPUnit and php-cs-fixer, before they looked at the app, and took more than half of check (php), 23 seconds each in paperless-sync. psalm.xml now reads ahead only vendor/nextcloud/ocp, the public API of Nextcloud, whose package has no autoloader, and Psalm finds everything else through the autoloader of Composer when the code uses it: a tenth of the time, with the same findings. Both still run without a cache, so that no result depends on one. The CI now keeps the packages of Composer in its cache, and the cache of php-cs-fixer, which then checks only the files that changed; the check of the PHP syntax runs one PHP for each processor. The update brings all of this.
  • The website and the hints at the spelling install their tools from a cache: the check docs and the build of the website take the packages of pip from the cache of the CI, and the hints at the spelling and the style those of npm. A pull request that changes no document no longer sets up Node for the hints at all.

0.10.3 (2026-10-13)

Bug fixes

  • The hook runs the checks before every push on Linux, macOS, in the dev container and in Codespaces too: .githooks/pre-push came as a plain file, and Git runs a hook only when it is executable, so outside Windows it skipped the hook with a hint and pushed without the checks. New repositories now get the hook executable, and .devcontainer/setup.sh, .github/sign-tag.sh and .github/blueprint.py too. scripts/check.sh now fails while Git has a hook in .githooks/ as a plain file, as Windows adds a new one, and names the fix: git update-index --chmod=+x .githooks/pre-push, and a commit. In your repository, the blueprint bot of 0.10.2 and later makes the four files executable with its next update, or names the command where GitHub can't carry the mode. The bot of 0.10.1 and older can't: if your repository is on such a release, commit git update-index --chmod=+x .githooks/pre-push .devcontainer/setup.sh .github/sign-tag.sh .github/blueprint.py before the update, or its pull request keeps the hook a plain file and fails its checks until you do. Where Git ignores the modes of the files, the dev container makes the hook executable on disk.
  • A Nextcloud app adopted without the sample code passes Psalm and PHPUnit: psalm.xml loads tests/stubs/Emitter.php and phpunit.xml starts with tests/bootstrap.php, but both files came only with the sample code. An existing app that took the blueprint with sample_code=false failed both checks, with "Cannot resolve stubfile path" and "Cannot open bootstrap script", unless it carried copies of its own. Every Nextcloud app now gets tests/bootstrap.php and tests/stubs/; the next update adds the files that an app lacks and keeps the ones it has. The README now shows, under An existing repository, where the checks of a Nextcloud app and of a Node package look for the code and the tests.

0.10.2 (2026-10-13)

Changes

  • Every job runs on ubuntu-24.04, named with its version: GitHub moves ubuntu-latest to Ubuntu 26.04 from 19 October 2026, one job at a time over a month, so that the runs of one workflow would have landed on either image at random, with Python 3.14 instead of 3.12, ShellCheck 0.11 and Docker 29, and failed like flaky tests. The workflows of the blueprint now name ubuntu-24.04 and keep it until the checks of every kind of project pass on Ubuntu 26.04, which the blueprint tries every week; then one release moves every job. The workflows of the issue assistant follow with its next release. A workflow of your own, such as one of end-to-end tests, and the action test .github/workflows/action-test.yml of a GitHub Action, which the update leaves alone, keep ubuntu-latest until you change their runs-on: to ubuntu-24.04 as well.

Bug fixes

  • The blueprint bot keeps its pull request open, and your commits on it: with every new release, the bot put its branch back on main before it committed the update, so GitHub closed the open update pull request and the bot opened a new one, as with Dennis-Otto/ha-autodarts#225 and Dennis-Otto/ha-autodarts#226; commits with which a maintainer had resolved conflicts on the branch were lost. The bot now moves its branch straight from the update before to the new one: the pull request stays open with its number and its auto-merge, and an auto-merge that you switched off stays off. Once someone else has committed to the branch, other than with a plain merge of main such as the button Update branch makes, the bot leaves it alone and says in one comment that a newer release is out; merge or close the pull request, and its next run brings the rest.
  • Conflicts no longer merge on their own: an update with conflict markers now turns off the auto-merge that an earlier update without conflicts switched on, before it moves the pull request on, and adds the label merge-conflict, which the next update without conflicts takes off again. Only the files that the update touched count, so lines like markers that a project has on purpose no longer hold an update up.
  • The bot updates to the latest published release, not to the newest tag, whose release may still be a draft or have failed; a repository made from a fork of the blueprint takes the fork's. BLUEPRINT_AUTOMERGE set to off now holds an open update pull request too, when a newer release moves it on.
  • An update that changes only .copier-answers.yml comes as a pull request too, so that a repository no longer stays at the release before and the dashboard no longer counts it as behind.
  • New modes of files reach existing repositories: the bot commits through the Git Data API, which carries the mode of every file, such as a script that becomes executable; GitHub still signs the commit. Where GitHub doesn't, the bot commits as before and lists the modes to set in the pull request.
  • The bot's code moved from the workflow into blueprint.py update-commit, with tests, and runs from main, not from the update it brings. The update that brings this release still runs the bot before it, which may close an open update pull request one last time.
  • The preview of the website works on Windows, and scripts/check.sh builds the website: the preview that CONTRIBUTING.md shows failed on Windows twice. pip refused all of .github/docs-requirements.txt, because the plugin for the dates needs the time zones of tzdata there, which the lock left out, and the strict build stopped at the preview cards for social media, which need the Cairo library. The lock now pins tzdata for every system, and only the CI draws the cards: GitHub's runners set CI to true, so the check docs still builds them. bash scripts/check.sh now builds the website strictly as well, with the tools of the preview in .venv-docs or those of an activated virtual environment, so that a link or an anchor that leads nowhere fails before the push, and a failure names the MkDocs that ran; without them it says so and leaves the website to the CI, which doesn't slow down. A MkDocs from elsewhere on the PATH, which lacks the theme and the plugins, doesn't count. The dev container sets these tools up. The first lines of your mkdocs.yml, which belongs to the project, still show the old preview; CONTRIBUTING.md has the new one.
  • The checks of the blueprint pass on Windows: bash scripts/check.sh failed in Git Bash on Windows in four tests of the bot for cancelled checks, because Windows started their bare bash from System32, where it is the launcher of WSL, which ran the step in Linux without its variables. The tests now run Git Bash, in Git Bash as well as from PowerShell or the test runner of an IDE, and the new Windows workflow runs the checks on Windows every week and for each pull request that changes the code, the template or the tests. The repositories made from the blueprint don't change.
  • Checks and releases that stall get going again without you: a job that ran out of time ended cancelled, not failed, and the flaky-test bot ran only failed jobs again, so a pull request of the blueprint waited 2.5 hours for a person; the bot now runs a first attempt once more whose job hit its time limit too. A release that a merge started and that failed, as at a push of its tag that GitHub refused for a moment, now runs its failed jobs once more by itself, where they stopped, instead of holding the release and its rollout until someone runs it again. The quick checks of a pull request, its title and changelog, the dependency review, the secret scan, the area labels and the website, now run once for each commit, so that a late run for an older commit no longer cancels the required checks of the head: the release bot edits the release pull request while release-please pushes to it, which cancelled its checks 7 times on one day and blocked it for 44 minutes. A repository whose other workflows run tests, such as visual tests, names them with copier update --data "test_workflows=Visual check, Screenshot bot", and the flaky-test bot watches them as well.
  • The flaky-test bot says what failed, and no longer runs real failures again: the issue of flaky tests now gets a table for each run, with every job that failed at first, a link to its log, the step that broke, the tests that pytest names in the log and the first error, so that a download that failed no longer reads like a flaky test; a name or a line of a log stays plain code there, which a pull request can't use to end the table or mention anybody. A new issue gets the label maintenance. When the same jobs broke for the commit before on the same branch, after they ran once more, the bot no longer runs them again, so that a test that a change broke doesn't keep a runner busy for nothing.
  • The link check of a pull request reads only the documents that it changes, and every document when it changes the rules, or moves or deletes a file that a document may link to; main and the weekly run still check every document. lychee now keeps the answers of the sites for a day and asks again for a link that failed, so that a site that is down for a moment fails only the pull requests that link to it.
  • The weekly mutation tests measure again: they had not reported a score since they came. For Python, the Mutation workflow gave mutmut the code to change as one line with commas, which mutmut 3 takes for a single path that doesn't exist, so it changed nothing and the run failed; it now gets one path on each line. For a Nextcloud app, Infection needs PHP 8.3 and got 8.2, and the run still ended green, because its error was lost in the pipe to tail; it now runs on PHP 8.3 with the bootstrap of the tests, which loads the classes of Nextcloud, and an error of Infection fails the run. A low score still fails nothing, not even below a minimum in the project's own configuration of Infection, but a run that measures nothing now fails, with the end of the output of the tool in its summary. A pull request that changes how the mutants run tries them and shows a defect as a red check; the check isn't required, so a pull request that merges itself doesn't wait for it. The dashboard shows the share of the mutants caught again, for both tools that of the code that the tests run. It also no longer takes the coverage, the mutation score or the time of the CI from a pull request whose branch in a fork is called main. A test that can't run in the copy of the repository in which mutmut tests the mutants, because it reads the code as text or asks Git, gets the marker @pytest.mark.mutation_unsafe and stays out of the mutation tests. New projects register the marker; an existing Python project that needs it adds the line of the blueprint's pyproject.toml to markers under [tool.pytest.ini_options] in its own.
  • Every release gets its signed tag, even when the run of its merge is lost: the release workflow signed the tag of a release only in the run that the merge of the release pull request started. When that run was cancelled or failed before it got there, the next run could create the release on a tag without a signature, which the immutable release then kept for good. Every run of the Release workflow, the hourly one and one started by hand too, now signs the tag of a merged release pull request before the release is created on it, and creates a release only on a tag that it signed: a release pull request merged in the seconds after a run looked, or in a repository without the release app, waits for the next run instead of getting a tag without a signature. Signing and pushing the tag, and every question to GitHub on the way, try three times each, because GitHub fails for a moment now and then, as with the fatal error in commit_refs that held up 0.10.0 until someone ran it again; a tag that GitHub took although the push failed counts as pushed.
  • A release is built only on its own signed tag: a token that may push could create the tag of the coming version first, at a commit of its own, and the release was then built, signed and published from it. A tag that exists already must now be the one that the release workflow signed, at the commit of the release; any other stops the release before release-please creates it, and the isolated build checks the tag again before it builds or signs anything.
  • The release check is strict with every new release: called by an hourly run of the Release workflow, it took the event of that run for its own weekly run: it only noted a tag without a signature, and it skipped a release without the bundle of its provenance altogether. It now checks every new release in full; only its own weekly run of the latest release accepts what older releases lack.
  • Findings no longer fails when hassfest or HACS move on: the CI of a Home Assistant integration pinned the actions of hassfest and HACS to a commit of their branches, and zizmor reports such a pin as soon as the branch moves on, as home-assistant/actions did twice on 2026-10-12. From then on, the Findings workflow failed every run, and Renovate, which waits a week after each commit, couldn't catch up. The CI now runs their images with docker run, as the actions did, without a pin that falls behind; hassfest still shows its errors as annotations. Renovate follows an action that a project pins to a branch in its next run and merges the update once every check passes.
  • Every release reaches every repository, one after the other, and the Rollout names each repository that it misses: the Rollout started the blueprint bots in one loop, which ended at the first repository whose bot GitHub didn't start, so the repositories after it got nothing; an error of GitHub in the look for a repository's bot, such as an exhausted rate limit, left that repository out without a word. Each repository now has a job of its own: only GitHub's answer that a repository has no blueprint bot leaves it out, the look and the start are tried three times, and a repository whose bot can't be started, or fails, fails the run by its name while the others still get the release. The repositories go one at a time, the quickest checks first and ha-autodarts last, each with up to 15 minutes for its bot and the checks of its update pull request, so that the update pull requests no longer all wait for the 20 runners of the account at once, and other pull requests keep runners free. A release that the hourly run of the Release workflow or a run started by hand finished now rolls out as well, and the merges of other pull requests no longer start a runner of the Rollout. The repositories made from the blueprint don't change.
  • A passing error of a download no longer fails the checks or a release: the checks of a Nextcloud app failed on main because GitHub's download of krankerl answered with a 504 for a moment, which the flaky-test bot then reported as a flaky test (Dennis-Otto/paperless-unified-search#94); the same error in the build of a release leaves it a draft to finish by hand. Every download of the workflows now tries again after an error of the network or of the server, with pauses that grow, and a connection that hangs fails and tries again instead of holding the job until its time limit: krankerl, the certificate of the app and the server of Nextcloud for signing it, Infection, gitsign, and what the release check reads from PyPI and the App Store. Each one writes into a file, which curl empties before it tries again. The checks of a Nextcloud app take krankerl from the cache of the CI after their first run, checked against its checksum as before, so that most runs don't download it at all; a release still downloads it. A test holds every download of the workflows and scripts to this.
  • The CI checks appinfo/info.xml in any case: when the schema of the App Store was out of reach, scripts/check.sh passed with "the CI checks it", in the CI as well, so a broken info.xml could merge while apps.nextcloud.com was down. The download of the schema now tries again, and in the CI a schema that stays out of reach fails the check and says to run it again later; locally, the check still passes without it.
  • The Upstream bot of a Nextcloud app can be tried: it runs only when Nextcloud has a new major version, so its first real run would come unobserved. Started by hand with dry run, it now shows the change and the pull request that it would make, and writes nothing to GitHub.
  • Renovate keeps every lock file current again: it skipped the test tools of the Home Assistant integrations (requirements-test.txt) since 0.8.8 and the lock file of mutmut since it came, with no more than a warning in its log, because it couldn't read the command in their headers: --python-version 3.14.8 lacked the = before its value, and --unsafe-package is an option of pip-compile that Renovate doesn't know. Neither file got an update, not even one that fixes a vulnerability. Both are compiled again with uv pip compile and an = before every value; mutmut's leaves out the pytest of the project with --no-emit-package= and now holds for every Python from 3.12 on. A Home Assistant integration made before this release has the old header in its own requirements-test.txt: compile it once with uv pip compile --generate-hashes --python-version=3.14.8 --output-file=requirements-test.txt requirements-test.in. A test now reads the header of every lock file as Renovate does, and in the blueprint, Renovate itself reads every file of a pull request that changes its rules or a lock file.
  • The weekly refresh of the lock files waits for you: it moves every indirect dependency to its newest version, also one released minutes ago, and so skipped the week that Renovate waits after a release, as when pydantic 2.14.1 reached every repository 17 hours after its upload. The refresh now waits for the maintainer instead of merging on its own, and it covers every package manager, also composer.lock and package-lock.json, which moved only with their direct dependencies before. Renovate builds it anew only in its weekly window, so the changes you reviewed are the ones that merge. Decision 0012 says why a cooldown of the package managers isn't possible for the refresh alone.

Performance

  • Pull requests wait less for a runner: a check now runs only where it can change a result, since an account runs about 20 jobs at once. The scanners run in the pull requests that change what they read: zizmor for a workflow, an action or the settings of Dependabot, OSV-Scanner for a lock file, the fuzzing for Python code, and the link check for a document, but not for the changelog alone. The SBOM comes every week and with every release, no longer with every pull request. The release pull request, which changes only the changelog and the version, runs no scanner. After a merge, main runs only what keeps a baseline or publishes: Lint, the secret scan, the SBOM and the fuzzing no longer run there, and the secret scan runs every week over the whole history instead. The checks with the newest Python move into a workflow of their own, Newest Python, which runs after every change of main and every week instead of in every pull request, so that a new Python no longer turns the CI of main red; a failure there keeps an issue open until the checks pass again. In the blueprint itself, a merge now starts 11 to 14 jobs instead of 36, the release pull request 17 instead of 37, and one required check, variants, stands for the 14 variants and updates, which the release pull request skips.

0.10.1 (2026-10-12)

Bug fixes

  • A cancelled check no longer blocks a pull request: a newer run of a workflow cancels the one still running for the same pull request, but GitHub doesn't always create the runs in the order of the commits. A late run for an older commit could cancel the run for the latest commit, whose required checks then stayed cancelled until someone ran them again by hand, as with the release pull requests of 0.8.8 and 0.10.0. The bots after the checks now start such a run again: when the concurrency of its workflow cancelled it, it is for the latest commit of an open pull request and no newer run of its workflow for that commit exists. A run that a person cancelled stays cancelled. For this they watch the workflows of every required check, CodeQL, Dependency review, Docs, Lint, Pull request title and Secret scan as well as the tests; the flaky-test bot still looks at the tests only.

0.10.0 (2026-10-12)

Features

  • Renovate's updates merge without waiting for its next run: main only takes pull requests that are up to date with it, and Renovate brought its own up to date only when it ran, every two hours and often later. An update whose checks had passed waited behind every other merge. The branch bot now brings Renovate's pull requests up to date as well, as the release app that Renovate commits as, so that Renovate still takes the branch for its own; one that conflicts with main Renovate builds anew itself, without a label or a comment of the bot.

0.9.1 (2026-10-12)

Bug fixes

0.9.0 (2026-10-12)

Features

  • Every release of the blueprint goes out at once: the new Rollout workflow starts the blueprint bot in every repository of the release app as soon as the Release workflow has published and verified a release, so that the update pull requests come within minutes instead of with the bot's weekly run. The release app needs the permission Actions to read and write for it: change it in the settings of the app, then accept the new permissions on its installation.

0.8.8 (2026-10-12)

Bug fixes

  • Renovate compiles every lock file for its own Python: Renovate installs pip-tools once per repository and run, for the Python of the first lock file it compiles, so it compiled a lock file for another Python with the wrong one. The lock file of the Home Assistant stack, for Python 3.14, came out for Python 3.12 with Home Assistant 2025.1. It is now compiled with uv pip compile --python-version 3.14.8, which doesn't depend on the Python it runs on, and its requirements-test.in pins the test plugin, so that a Home Assistant that needs a newer Python fails its update instead of an older Home Assistant being chosen. A project with lock files for two Pythons compiles the one for the other Python with uv pip compile --python-version as well.
  • Ruff and its pre-commit hook move together: Renovate now keeps the hooks of .pre-commit-config.yaml current as well, in the group of the Python tools, where Ruff and its hook also move to a new major version together.
  • The test plugin of Home Assistant moves on its own: it pins a new Home Assistant, now and then a beta, and when its lock file didn't resolve, it held up the other Python tools of the integration.
  • The weekly refresh of the lock files merges itself: it has no release date, so its age check stayed pending for good and it waited for the maintainer. It now merges once every check passes, like a routine update.
  • The Nextcloud apps get only the releases that run on their PHP: Renovate offered phpunit 13 and nextcloud/ocp 35, which need PHP 8.4 and 8.3, to apps that run on PHP 8.2, and Composer couldn't update their lock file. Renovate now leaves out the releases that don't run on the platform PHP of composer.json. A project on a newer PHP sets constraints: { php: "8.3.*" } for Composer in its .github/renovate.json5.
  • Renovate updates mutmut again: the lock file of the mutation tests recorded --no-index, which pip-tools 7.6.1 added by mistake with click 8.5, and Renovate skipped a file with it.
  • The dependency review accepts regex, which Home Assistant's test base brings in and whose license reads Apache-2.0 AND CNRI-Python since 2026.9.29. An existing Home Assistant integration adds pkg:pypi/regex to allow-dependencies-licenses in its .github/dependency-review.yml, which updates leave alone.

0.8.7 (2026-10-12)

Bug fixes

  • deps: update davidanson/markdownlint-cli2 docker tag to v0.23.3 (#120) (84d2d85)

0.8.6 (2026-10-11)

Bug fixes

  • The blueprint bot no longer fails now and then: since version 2.47, Git starts its maintenance in the background after a commit, and copier update deletes the repositories it commits to right away, so an update failed at times with Directory not empty: '/tmp/copier._main.new_copy…/.git'. The bot now runs Copier with maintenance.auto off.

0.8.5 (2026-10-10)

Bug fixes

  • The link check passes while GitHub refuses the page of a file: github.com answers the page of a file, such as https://github.com/<owner>/<repo>/blob/main/LICENSE, with 503 Service Unavailable at times while its other pages work, and the Links workflow failed for every such link. It now asks raw.githubusercontent.com for the same file, which answers 404 for a missing one as well.

0.8.4 (2026-10-10)

Bug fixes

  • The website builds when a download is slow: while the website is built, the privacy plugin of Material downloads the fonts and scripts that the pages use, such as those of Google Fonts, and the social plugin the fonts of the preview cards. They tried every file once, so a single font that didn't come within 5 seconds failed the strict build of the Docs workflow. The hook of the blueprint now has them try a download up to three times, after a second and then two, when the network or the server fails.

0.8.3 (2026-10-08)

Bug fixes

  • No labels and no merge step for the updates that Dependabot no longer makes: Renovate keeps the actions, the dependencies and the images current, so Dependabot updates only the Features of the dev container and sets just the label dependencies, and Renovate sets none. New projects no longer get the labels github_actions, python, php, javascript and docker, and the workflow Dependabot maintenance loses its step for updates of the HACS and hassfest actions to a new commit, which Renovate brings and merges itself. An existing project removes the group Dependabot from its .github/labels.toml, which updates leave alone. The Labels workflow never deletes a label, so the project deletes them on GitHub as well, each with gh label delete github_actions --yes, and with them those that Dependabot created itself, such as devcontainers_package_manager.
  • The social preview names Renovate instead of Dependabot. An existing project changes it in its .github/social-preview.html, renders it again with bash scripts/social-preview.sh and uploads it.
  • The language switch finds its sitemap on every page: on a page in a folder, such as a decision record in docs/decisions/, the switch of Material asked for the sitemap in the folder of the page in each language, such as decisions/sitemap.xml and de/decisions/sitemap.xml, and the browser logged two errors. The hook of the blueprint now copies the sitemap into every folder that holds a page. The switch already led to the same page in the other language.
  • The 404 page stays in the default language: on a website in more than one language, the build of each further language wrote 404.html at the root again, so a missing page showed the header of the German website, and its language switch led to the page built last, such as a decision record. The hook of the blueprint now keeps the 404 page of the default language, whose switch leads to the start page of each language.

0.8.2 (2026-10-08)

Changes

  • The code of .github/blueprint.py is in modules: the tool keeps its commands, and its code is now in .github/blueprint/, one module for each task. The folder brings its own Ruff settings, so that those of a project don't apply to it.

Bug fixes

  • The coverage comment names every file by its path from the root of the repository, such as lib/AppInfo/Application.php instead of AppInfo/Application.php, where the report has one source folder, as the report of PHPUnit and of a project that measures one folder with coverage.py have. A project that measures several folders names its packages under source_pkgs instead of source in [tool.coverage.run], as the blueprint does now, since such a report doesn't say which folder holds a file.
  • blueprint.py settings check and settings apply name the settings as .github/repository.toml on Windows too, as the documentation does, instead of .github\repository.toml.
  • One run after the checks, not two: the coverage bot and the flaky-test bot share the new workflow After the checks, after-checks.yml, which replaces coverage.yml and flaky.yml. A run of the checks starts one run after it instead of two that mostly had nothing to do, the coverage bot no longer starts for checks that were skipped or cancelled, and the workflow watches only the checks of the repository. A project that added a workflow of its own to the list of flaky.yml adds it to after-checks.yml.

0.8.1 (2026-10-08)

Bug fixes

  • The language switch finds its sitemap: on a website in more than one language, the switch of Material asked for a sitemap next to the pages of each further language, such as de/sitemap.xml, and got nothing. A hook of the blueprint now copies the sitemap there. A project with hooks of its own adds .github/mkdocs_blueprint.py to its hooks in mkdocs.yml.

0.8.0 (2026-10-08)

Bug fixes

  • Websites in more than one language build: the dates of the pages no longer stop the build of a website that turns on its German pages, and the language switch leads to the same page in the other language. A website turns them on with enabled: true under i18n in mkdocs.yml; pages load with a full reload now.
  • One heading for each kind of change: when two pull requests each start a heading such as Features under Unreleased, the release puts their entries under one heading, in the changelog and in the notes.

Features

  • The issue forms point to the documentation: with a homepage, the first link of the forms leads to it, so that a question finds its answer before it becomes an issue. The blueprint's own homepage is its website now.
  • The website of the owner itself: a repository named like dennis-otto.github.io may use the blueprint, and its documentation website lives at the root of the owner's address, such as https://dennis-otto.github.io/.

0.7.0 (2026-10-08)

Bug fixes

  • GitHub no longer asks the maintainer to review every pull request of a bot: the blueprint brings no .github/CODEOWNERS, which named the owner of every file and so requested their review on every pull request of someone else, although no rule asks for one. A repository whose update keeps the file can delete it.

Features

  • The settings that every website shares come from the blueprint. .github/mkdocs-blueprint.yml holds the theme, the plugins and the Markdown of the documentation website, and the mkdocs.yml of a project inherits it (INHERIT), so that the updates of the blueprint bring the website new features. It adds tabs for the sections of the menu and the path above a page, fonts and pictures served with the website instead of from other servers (privacy plugin), a preview card of every page for where a link to it is shared (social plugin), pictures that open enlarged (glightbox), the date of the last change of a page, tabs on a page, tooltips, and the plugin for a website in more than one language with a switch in the header (mkdocs-static-i18n). An existing project puts INHERIT: .github/mkdocs-blueprint.yml at the top of its mkdocs.yml and removes what the file now holds; its dependency review allows the license MIT-CMU of Pillow.
  • A designated successor: the new answer successor says that the owner has designated a successor through GitHub's account successor setting. GOVERNANCE.md then says how the successor can carry the project on, with a bus factor of 2, and what doesn't pass with the repository, such as an app in the Nextcloud App Store. An existing repository turns it on with copier update --data successor=true.

0.6.2 (2026-10-08)

Bug fixes

  • The isolated build of a release gets the secrets of the environment release again, so a Nextcloud app is signed: since 0.6.0 its release stopped at the build for want of the signing key.
  • A release that a failed run left as a draft, with its signed tag, is built and published by starting the Release workflow by hand.

0.6.1 (2026-10-08)

Bug fixes

  • The settings bot applies every part of the settings even when GitHub refuses one, and names the permission of the release app that a refusal points to. Before, a missing permission for the repository variables kept GitHub Pages and the labels from being set.

0.6.0 (2026-10-08)

Bug fixes

  • The package of a Nextcloud app leaves out the files of the Markdown lint, .markdownlint-cli2.jsonc and .markdownlint.jsonc, which its package check rejected, and the style rules .vale.ini. A test checks every file of the repository against the package now.
  • Renovate updates an image of a script also where a variable holds it in quotes, such as IMAGE="name:tag@sha256:…"; it found only an image after a space before.

Features

  • SLSA Build Level 3: a release is built in a reusable workflow of its own, build-release.yml, isolated from the rest of the release workflow, and the signed provenance of every asset names it as the build. The release check verifies that; releases from before are accepted by its weekly run only.
  • Every repository gets a documentation website: MkDocs with the Material theme makes the pages of docs/ a website with search and a light and a dark theme, German pages under docs/de/ included, and the new Docs workflow publishes it on GitHub Pages with every change of main. Every pull request builds it strictly, as the new required check docs, so that a link or an anchor that leads nowhere fails; a repository without a website passes it. The settings bot turns GitHub Pages on from the new table [pages] of the settings as code. mkdocs.yml belongs to the project, and a page can include a file of the repository, such as the README, with --8<--, so that nothing is written twice.
  • The weekly report: every Monday, the blueprint posts in its discussions what the public repositories of the owner released, merged and fixed in the week, from public data alone (weekly.py, Weekly report workflow).
  • The blueprint's own website is its documentation now, and the dashboard moved to dashboard/ of it.
  • The dashboard shows the health of every repository: the line coverage and the share of mutants that the tests catch, from the reports of the last runs on main, the stable releases and the median time to the first answer to a new issue over 90 days, and the median time of the CI, each with an arrow when it moved since a week ago. The history of one entry a day lives in the published dashboard itself, in history.json.
  • Signed release tags: the tag of every release and beta is an annotated tag, signed without a key by the release workflow with gitsign: Sigstore certifies the identity of the workflow, and the release check verifies it. There is no key to keep secret.
  • Mutation tests every week: mutmut for Python and Infection for the PHP of a Nextcloud app change the code in thousands of small ways and run the tests against each change. The summary shows the share of mutants that a test notices and the first that survive; a low score fails nothing.
  • The clean-up bot deletes every week the branches that main holds completely and that no open pull request uses, and the caches of closed pull requests. A branch with commits that main doesn't have stays and is listed in its summary after a month without a pull request.
  • A Nextcloud app runs the taint analysis of Psalm in scripts/check.sh: input from a request or a user that reaches HTML, SQL, a shell, a file or a header without escaping fails the check, as CodeQL does for the other languages, which has no PHP.
  • Every repository documents what the Silver level of the OpenSSF Best Practices badge asks for. SECURITY.md says how to verify a release with the GitHub CLI and gives the assurance case of the repository and its releases: the threat model, the trust boundaries, the secure design principles and how the OWASP Top 10 CI/CD security risks are countered. A new repository starts with docs/security.md, the security design of its software, which belongs to the project from then on. GOVERNANCE.md names the roles and their responsibilities and says honestly how the project continues with one maintainer, and CONTRIBUTING.md the coding standards of each kind of project and the rule that new functionality and every fix come with tests.
  • Hints at the prose of the documents that a pull request changes: the spelling in English and German (cspell, with the words of the project in .github/cspell-words.txt) and the style of the English (Vale, with write-good and proselint), as warnings next to the lines, never as a failure. The dependency review allows the two dictionaries of cspell whose licenses it would reject, since only the checks use them; an existing repository adds them to its own .github/dependency-review.yml, which the update doesn't change.

0.5.0 (2026-10-07)

Features

  • The coverage bot keeps one comment on every pull request: the coverage of its tests against that of main, and every file whose coverage changes, from the reports that the checks keep. It never runs the code of a pull request. A Nextcloud app keeps its report now too.
  • Every repository records the decisions that shape it in docs/decisions/, from a template, with an index and a first record; they belong to the project. The blueprint records its own there, such as Copier with an update bot, the curated changelog, the settings as code, Renovate and the beta channel.
  • The settings bot keeps the community profile at 100 %: when GitHub's community standards miss a file of a repository, its run fails and names what to add, with the link to the profile.
  • Every release carries its SBOM as CycloneDX too, the format that many tools of companies read, beside SPDX, and an OpenVEX document that states every advisory that osv-scanner.toml accepts, with its reason, as one that doesn't affect the release, so that the scanners of the users stop reporting it. The release check verifies both.
  • The flaky-test bot: when the tests or end-to-end tests fail for the first time, their failed jobs run once more. A job that passes then is flaky; the bot lists it in one issue, Flaky tests, so that its test gets fixed, and the pull request is no longer blocked by chance. A job that fails again stays red.
  • A Nextcloud app covers every line of lib/ with its tests: scripts/check.sh measures the coverage with pcov in the CI and Xdebug in the dev container, and fails below 100 %, as the other kinds of projects do.
  • Renovate keeps every repository current, instead of Dependabot. The blueprint runs it every two hours for each repository with a .github/renovate.json5, as the release app, with a token that can't change any setting. It updates what Dependabot did and also the images that the workflows and scripts start by digest, the end-to-end tests and the Renovate of the bot itself. An update waits a week after its release, one that fixes a vulnerability comes at once; routine updates merge on their own once every check passes, major ones wait, and the release bot ships them as before: fix(deps) for what the users run, chore(deps) for the tools. The rules of the blueprint are in .github/renovate-blueprint.json5; .github/renovate.json5 extends them and belongs to the project, whose own rules come last. Dependabot keeps only the Features of the dev container, whose lock file Renovate can't update; Renovate also takes over the security updates.
  • A beta channel: where the repository variable BETA_CHANNEL is true, every feat, fix or perf that reaches main becomes a beta of the next release, such as 1.3.0-beta.2, with the text of Unreleased as its notes. It is built, signed and verified like a release and published as a prerelease, which HACS, npm and PyPI offer only to those who ask for betas, and it announces nothing.
  • The Markdown of every document is linted by markdownlint, as the new required check markdown of the Lint workflow. The rules of the blueprint are in .github/markdownlint.jsonc; a project changes them in its own .markdownlint.jsonc.
  • Every release is announced in the discussions, in their category Announcements, with its notes; prereleases are not. New repositories have discussions, with forms for questions and ideas that belong to the project; an existing repository turns them on with copier update --data discussions=true.

Bug fixes

  • The release check of a Nextcloud app looks for the release on the page of the app in the App Store, which links its signed package at once; the list of all apps, which it read before, shows a new release only hours later, so the check failed after every release.

0.4.0 (2026-10-07)

Features

  • The settings bot applies the settings as code itself, after every change of repository.toml, repository.project.toml or labels.toml on main and every week, as the release app, which may now write the administration of the repositories. So a new required check of a blueprint update is in place without anyone running settings apply; only a missing secret still waits for the maintainer, with the command that sets it.
  • OSV-Scanner checks every lock file against the OSV database of known vulnerabilities: on every pull request the ones it brings in, on main and every week all of them. Its findings go to code scanning, where the Findings workflow keeps them fixed or accepted.
  • Area labels: a bot labels every pull request with the areas whose files it changes, by the paths in .github/labeler.yml, which each repository fills with its own areas.
  • The changelog check asks every feat, fix or perf pull request for its entry under Unreleased, so that the notes of a release are always written; bots and dependency updates are exempt. It is a new required check, changelog.
  • Every release carries the licenses of its third-party components as THIRD_PARTY_NOTICES.md, from the dependency graph of GitHub, grouped by license.

Bug fixes

  • The release pull request shows its introduction as text above a line, not as a large heading: Markdown read the line under it as the underline of a heading.
  • The pin of shivammathur/setup-php names its tag as it is, 2.37.2, so that zizmor no longer reports the comment as a mismatch and the Findings workflow passes.

0.3.2 (2026-10-07)

Bug fixes

  • The tools of the blueprint bot pass the dependency review of every project: Copier's Jinja filters (GPL-3.0-only) and typing-extensions, whose license the review reads wrongly, are no part of any release.
  • blueprint.py writes UTF-8, so that its checklist shows its marks in a console of Windows too.

0.3.1 (2026-10-07)

Bug fixes

  • The release bot no longer loses a release: its step that has the release pull request rebuilt on main could replace the description of a pull request that had just been merged, and without its notes release-please made no release of it. The step now checks that the pull request is still open and only adds a marker at the end of the description.
  • An existing repository keeps out the samples of the template: copier update brought back the sample code and tests that an adopted repository had deleted. The new question sample_code, which an existing repository answers with no, leaves them out.
  • The link check leaves out the badges of workflows, which GitHub serves only once a workflow is on main.

0.3.0 (2026-10-07)

Features

  • The release bot writes the changelog that the pull requests write. What they describe under ## Unreleased of CHANGELOG.md becomes the section of the next release, and its notes show that text followed by every pull request. Without a text, the section lists the pull requests as before. The version still follows from their titles (fix a patch, feat a minor, ! a major version), decided anew with every merge, and the release pull request says so.
  • The release pull request stays up to date with main, also after merges that change none of its notes, such as docs: or chore: pull requests.
  • The upstream bot of a Nextcloud app raises max-version in appinfo/info.xml when Nextcloud has a new major version, with an entry under Unreleased, in a pull request that merges itself once every check passes against it. blueprint.py unreleased lets every bot write such an entry.
  • The registration of a Nextcloud app in the App Store is a workflow of every app (register-app.yml): it checks the key of the environment against the certificate of the app id and registers it, confirmed with the app id.
  • The release verification bot checks every release after its delivery and the latest one every week, as its users can: the signed provenance of every asset in GitHub and in the attached bundle, the SBOM, an immutable release with a signed commit, and the same release on PyPI, npm, GHCR, the Nextcloud App Store or the major tag of an action.
  • Prereleases: a version such as 2.0.0-beta.1, set with a line Release-As: 2.0.0-beta.1 in a pull request, becomes a prerelease. It keeps the text of Unreleased for the release that follows, leaves the major tag of an action where it is, and goes to the tag next on npm.
  • The dashboard of every public repository of the owner on GitHub Pages: the release of the blueprint it is on, its releases, its open pull requests and conflicts, the last runs of its main workflows and its Scorecard, every six hours.
  • Harden-Runner records the network traffic of every job of the workflows, so that a connection that doesn't belong there shows; SECURITY.md says so.
  • The issue forms belong to the project after the first copy, like its labels, because their areas and fields are its own. They start richer: an introduction with what happens with an issue and where vulnerabilities go, steps to reproduce, an area for feature requests too, and a checklist.
  • Releases attach their signed provenance also as in-toto JSON lines (provenance.intoto.jsonl), the format of SLSA that OpenSSF Scorecard and other tools look for.
  • A dev container for VS Code and GitHub Codespaces in every project: the image of its kind by digest, which Dependabot keeps current, the tools of the checks and the pre-push hook set up once it is created.
  • Every project gets the docker label, for the Dependabot updates of the image of its social preview.
  • Nextcloud apps check more on every change: composer validate and composer audit, appinfo/info.xml against the schema of the App Store, the PHP of templates/, and the package that krankerl builds, with the project's scripts/check-package.sh, which the release runs before and after signing as well. Lines of info.xml marked x-release-please-version, such as screenshot URLs at a tag, follow each release.
  • The dev container has Docker for every project, for the end-to-end tests and the social preview, and Node for a Home Assistant integration or a Nextcloud app with a frontend; its Features are locked and kept current by Dependabot. The CI of a Home Assistant integration with a package.json sets up Node for the tests of its frontend.
  • The README describes how an existing repository takes the blueprint.
  • Every .gitignore leaves out the local settings of Claude Code, and .gitattributes marks the lock files as generated, so that GitHub folds them in diffs.

Bug fixes

  • Updates reach every file of the blueprint again: the patterns of the files that belong to a project matched at any depth, so that .github/blueprint.py, the Dockerfile of the dev container and that of the social preview never changed with an update.
  • A release follows only from changes for users: pull requests of the type docs no longer make one on their own; what they wrote under Unreleased comes with the next release.
  • A Nextcloud app keeps its icons in img/, and its package leaves out the dev container, the hooks, the link check, screenshots/ and LICENSES/, whose text LICENSE carries. psalm's JSON mapper (OSL-3.0) passes the dependency review as a tool of the checks. Dependabot leaves the major version of nextcloud/ocp, which follows the min-version of the app, to the maintainer and groups the major updates of composer.
  • Every .gitignore keeps secrets out: .env, keys, certificates and credential files.
  • The weekly release verification waits for the next release when the latest one is older than the release workflow of the blueprint.
  • The first release no longer drops the introduction of the changelog.
  • The settings bot no longer fails on the bypass list of the rulesets: the token of the release app gets the rulesets without it.
  • LICENSE ends with one line break, also for the SPDX texts that end with a blank line, such as MIT.

0.2.0 (2026-10-07)

Features

  • leave the action test to the project, check the newest Python and sign every commit (#13) (a0118dd)

0.1.0 (2026-10-07)

Features

  • catch the merges of workflows, report conflicts, welcome and guard pushes (#9) (e5b8b03)
  • check workflow security, links, licenses and settings (#10) (2066a37)
  • fuzz the Python projects with Atheris and the Node packages with fast-check (#3) (81c1359)
  • keep pull requests that wait for auto-merge up to date (#6) (a7ad6a3)
  • let a project add its own settings and copyright holders (#12) (b05400d)
  • settings: create the labels of labels.toml with the settings (#5) (5f76ffb)
  • start the blueprint (d6bad3b)
  • test properties every night, show the coverage and suit HACS (#11) (b53ec72)

Bug fixes

  • deps-dev: Bump typescript from 5.9.3 to 7.0.2 in /stacks/node in the node-stack group (#8) (c55cc32)
  • deps: Bump alpine from 3.22 to 3.24 in /stacks/container (#1) (3d44918)
  • leave the fuzz targets and their requirements to the project (#7) (82455ec)
  • tag the license choice for REUSE and quote a path in the variants (9385ca5)