From 93c104bb69466e09198052c470b80ec60d09c69a Mon Sep 17 00:00:00 2001 From: "dilux-bot[bot]" <333187390+dilux-bot[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 14:27:16 +0000 Subject: [PATCH 1/4] chore(autopilot): start on #688 The draft pull request opens before any code: the Spec as its checklist. From 8d3067f438f0d13296bcad3799601d1b249dd5fb Mon Sep 17 00:00:00 2001 From: "dilux-bot[bot]" <333187390+dilux-bot[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 14:33:45 +0000 Subject: [PATCH 2/4] docs(kinds): say what a kind's battery is (C1) kinds/README.md gets a section, "The battery", whose one paragraph says what a battery is, the pack key it comes from, the wordpress-plugin example, how it differs from the workflows every repository gets, and how a repository runs it through its callers. The earlier mentions on the page link to it. Assisted-by: Claude Opus 5.5 (Anthropic) --- kinds/README.md | 8 +++-- tests/workflows/test_battery_doc.py | 53 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/workflows/test_battery_doc.py diff --git a/kinds/README.md b/kinds/README.md index 1a9cf797..6b6f20d9 100644 --- a/kinds/README.md +++ b/kinds/README.md @@ -3,7 +3,7 @@ The organisation's workflows and scripts know nothing about WordPress, or about any other stack. What is specific to a kind of project lives in a **pack**, a directory here, as data: its review profile, its rules, its -settings, the list of workflows that make up its battery. A repository says +settings, the list of workflows that make up its [battery](#the-battery). A repository says which kind it is, once, in its `.github/review-policy.yml`: ```yaml @@ -35,7 +35,7 @@ kinds// | --- | --- | | `name` | The kind's name for people. | | `aliases` | Other names a caller's `profile:` may use for this kind (one line, `[a, b]`). `plugin-wp` is the wordpress-plugin kind's: the name of its profile before packs existed, still accepted. | -| `workflows` | The reusable workflows in `.github/workflows/` that make up the battery. | +| `workflows` | The reusable workflows in `.github/workflows/` that make up the [battery](#the-battery). | | `pre-pr` | What the repository's `make pre-pr` must run (#479, [`scripts/pre-pr.py`](../scripts/pre-pr.py)), one item per line: every check its pull request and its merge queue run, one after the other, stopping at the first failure, starting and stopping what it needs (a plugin's wp-env sites) and relying on nothing started before it. `make pre-pr` is the one check before a pull request in every repository, whatever its stack: `dx check` runs it before the local review, and so does the autopilot before it marks a pull request ready. A repository of no kind lists what its own covers in its `.github/review-policy.yml` (`pre-pr:`). The daily drift check lists a repository whose Makefile has no `pre-pr` target, unless `repos.yml` says it has none by design (`pre-pr: {NAME: false}`); with org.yml `pre-pr: required`, the conventions check fails its pull requests. | | `pre-pr-make` | How the `make pre-pr` of the kind's repositories runs (#479, `scripts/pre-pr.py render`): `run`, bash lines from the checkout, run as one script that stops at the first failure, and `finally`, run after it either way (the wp-env sites it started). `scripts/sync-repos.py pre-pr` writes it into the repository's `dx.mk`, a managed file its Makefile includes (`include dx.mk`, last, so a bare `make` keeps running the repository's own first target), so the organisation owns `pre-pr` and the repository's own targets stay its own: it adds its own checks as prerequisites (`pre-pr: my-check`), never in `dx.mk`. A Makefile whose own `pre-pr` has a recipe is left as it is, and the pull request says to make it a prerequisite. `DX_CENTRAL` is the organisation's checkout (`dx check` sets it; the autopilot's `.dx-central` is the default). The daily drift check lists a repository whose `dx.mk` is missing or differs. A kind with none: its repositories write their own `pre-pr`. | | `pre-pr-tools` | The commands the kind's `pre-pr-make` runs that a bare runner may not have, and the apt package of each (#682, `scripts/pre-pr.py tools`): each a block map, `command:` and `package:` (`- command: msgcat` and `package: gettext` under it; the PHP reader takes no flow maps), `[]` for none. The autopilot's `work` job (before the agent, so its own `check` runs on them) and its `verify` job (before `make pre-pr` in full) install the packages of the commands not on the runner's PATH, with `sudo apt-get install` when sudo is there; an install that fails fails the job, and `verify` then reads as `lost`, not as the agent's failure. A command `make pre-pr` still does not find makes its failure an engine failure, which starts no fix round: a maintainer adds it here (or to the runner's image) and continues the issue. | @@ -63,6 +63,10 @@ kind-settings: ignore-codes: [some_code_this_plugin_cannot_avoid] # added to the pack's list ``` +## The battery + +A kind's **battery** is the set of reusable workflows in `.github/workflows/` that check, test, release and publish a repository of that kind, the ones its pack lists under its `workflows` key: for wordpress-plugin, `plugin-checks-wp.yml`, `plugin-tests-wp.yml`, `plugin-release-wp.yml` and `plugin-publish-wp.yml`. It differs from the workflows every repository gets whatever its kind (`repos.yml` `callers: every-repository`, `issues.yml` and `pull-request-comments.yml`, and `org-pull-request.yml`, which the organisation's ruleset requires) in that only a repository of that kind gets it. A repository runs it through its callers: the files from [`workflow-templates/`](../workflow-templates/) that `scripts/sync-repos.py callers` writes into its `.github/workflows/` (`plugin-checks-wp.yml` and `release-wp.yml` for a plugin) and that name the battery's workflows, since GitHub cannot pick a reusable workflow from data ("What a pack cannot do", below). + ## How the pieces find the pack - [`scripts/policy.py`](../scripts/policy.py) reads `kind:` and fails on a diff --git a/tests/workflows/test_battery_doc.py b/tests/workflows/test_battery_doc.py new file mode 100644 index 00000000..ba47e4da --- /dev/null +++ b/tests/workflows/test_battery_doc.py @@ -0,0 +1,53 @@ +"""What a battery is (#688): kinds/README.md defines it in one paragraph +under `## The battery`, and the first "battery" of each page that uses the +word links to that definition.""" +import re +from pathlib import Path + +import yaml + +ROOT = Path(__file__).resolve().parents[2] +KINDS = ROOT / "kinds" / "README.md" + + +def section(text, heading): + start = text.index(f"\n{heading}\n") + len(heading) + 2 + end = text.find("\n## ", start) + return text[start:] if end < 0 else text[start:end] + + +def definition(): + paragraphs = [p for p in section(KINDS.read_text(encoding="utf-8"), "## The battery").split("\n\n") if p.strip()] + assert len(paragraphs) == 1, paragraphs + return paragraphs[0] + + +def test_one_paragraph_says_what_a_battery_is(): + assert "**battery**" in definition() + + +def test_it_names_the_pack_key_it_comes_from(): + assert "`workflows` key" in definition() + + +def test_it_gives_the_wordpress_plugin_battery(): + pack = yaml.safe_load((ROOT / "kinds" / "wordpress-plugin" / "pack.yml").read_text(encoding="utf-8")) + for workflow in pack["workflows"]: + assert f"`{workflow}`" in definition(), workflow + + +def test_it_says_how_it_differs_from_what_every_repository_gets(): + text = definition() + assert "every-repository" in text + repos = yaml.safe_load((ROOT / "repos.yml").read_text(encoding="utf-8")) + for caller in repos["callers"]["every-repository"]: + assert f"`{caller}`" in text, caller + assert "`org-pull-request.yml`" in text + assert "only a repository of that kind" in text + + +def test_it_says_a_repository_runs_it_through_its_callers(): + text = definition() + assert "through its callers" in text + assert "workflow-templates/" in text + assert "sync-repos.py callers" in text From 5f7a2a648ae332a2829dec0dd7b1e3872d180b1d Mon Sep 17 00:00:00 2001 From: "dilux-bot[bot]" <333187390+dilux-bot[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 14:33:51 +0000 Subject: [PATCH 3/4] docs: link the first battery of each page to its definition (C2) The first "battery" in docs/configuration.md, docs/adopting.md and docs/maintaining.md links to the definition in kinds/README.md. The configuration page is generated, so its link is in the `says:` of the `workflows` setting in policy/settings-schema.yml, and the page is written again from it. Assisted-by: Claude Opus 5.5 (Anthropic) --- docs/adopting.md | 2 +- docs/configuration.md | 2 +- docs/maintaining.md | 2 +- policy/settings-schema.yml | 2 +- tests/workflows/test_battery_doc.py | 21 +++++++++++++++++++++ 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/adopting.md b/docs/adopting.md index 2f6064ca..3b2d6202 100644 --- a/docs/adopting.md +++ b/docs/adopting.md @@ -6,7 +6,7 @@ How a repository joins: its kind of project, what it adds of its own, and what t Nothing here is hard-wired to one stack. What is specific to a kind of project (its review profile, the rules its reviewers taught us, the settings -of its gates, the workflows of its battery) lives in a pack under +of its gates, the workflows of its [battery](../kinds/README.md#the-battery)) lives in a pack under [`kinds/`](../kinds/), as data, and a repository declares its kind with `kind:` in its `.github/review-policy.yml`. Every rejection a review sends back becomes one entry in the kind's `rules.yml`, with fixtures, never a new diff --git a/docs/configuration.md b/docs/configuration.md index ade98b2f..cb54bf24 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -89,7 +89,7 @@ What a level may do: `any` value; `lower-only`; `stricter` (switch off, narrow, |---|---|---|---|---|---|---| | `name`: The kind's name. | each one's own, in `kinds/*/pack.yml` | `any` | `never` | the kind's name for people | [`detect.py`](../scripts/detect.py) | #191 | | `aliases`: Other names of the kind. | each one's own, in `kinds/*/pack.yml` | `any` | `never` | names a profile may still use | [`policy.py`](../scripts/policy.py) | #191 | -| `workflows`: The workflows that make up the kind's battery. | each one's own, in `kinds/*/pack.yml` | `any` | `never` | reusable workflows of the engine | [`sync-repos.py`](../scripts/sync-repos.py) | #191 | +| `workflows`: The workflows that make up the kind's [battery](../kinds/README.md#the-battery). | each one's own, in `kinds/*/pack.yml` | `any` | `never` | reusable workflows of the engine | [`sync-repos.py`](../scripts/sync-repos.py) | #191 | | `own-checks`: What a repository of the kind requires of its own jobs. | each one's own, in `kinds/*/pack.yml` | `any` | `never` | a ruleset that exists is never rewritten | [`sync-repos.py`](../scripts/sync-repos.py) | #552 | | `pre-pr`: What a repository of the kind's `make pre-pr` runs, in words. | each one's own, in `kinds/*/pack.yml` | `any` | `never` | every check the pull request and the merge queue run | [`pre-pr.py`](../scripts/pre-pr.py) | #479 | | `pre-pr-make`: How `make pre-pr` runs them (dx.mk). | each one's own, in `kinds/*/pack.yml` | `any` | `never` | bash, one command a line | [`pre-pr.py`](../scripts/pre-pr.py), [`sync-repos.py`](../scripts/sync-repos.py) | #479 | diff --git a/docs/maintaining.md b/docs/maintaining.md index ccf88a22..f9974fe3 100644 --- a/docs/maintaining.md +++ b/docs/maintaining.md @@ -14,7 +14,7 @@ job after `v7` moved re-runs the old workflow. Reopen the pull request, or push to it, for a run on the new one. `v5`, `v4`, `v3`, `v2` and `v1` stay where they are. -A change to a template in [`workflow-templates/`](../workflow-templates/) reaches no repository by itself: each one has its own copy, its caller. The daily drift check lists every caller that differs from its template, with the job and the template's path (it compares what the jobs do, never their comments, so a template's comment can change without putting any repository out of step), in the issue "Repositories out of step", and every caller `sync-repos.py callers` would add to it (#671: the ones `every-repository` names and those of its kind's battery; the caller of its kind's release only when its entry in `repos.yml` gives its `slug` or its kind's place, wordpress.org for a plugin, publishes it). Before it, the same daily run opens, for each repository `repos.yml` lists whose callers fell behind, the issue and the pull request that bring it in step (`scripts/sync-repos.py callers`, #672), none while one is open; `python3 scripts/sync-repos.py callers --repo NAME` does the same for one by hand. A callers pull request closed without merging is opened again the next day: `repos.yml` `exclude` keeps a repository out. Run from a checkout that is `main` as it is now, the `org-sync` lane of [`policy/autonomy.yml`](../policy/autonomy.yml) accepts the issue, and the pull request merges on its own when every job it changes is its template's and none changes its permissions or secrets (a new job always waits); otherwise the maintainer accepts and merges them there. A new input a template asks to fill in goes into `repos.yml` (`callers`, `repository-owned`) in the same pull request, or the check would hold every repository to the template's example value, and `callers` writes no caller that would still hold it: that file is left out and named, with the repository and the input, and the others go in (#671). A template never says "Fill in": its header says where a value comes from, so a new caller is its template whole. +A change to a template in [`workflow-templates/`](../workflow-templates/) reaches no repository by itself: each one has its own copy, its caller. The daily drift check lists every caller that differs from its template, with the job and the template's path (it compares what the jobs do, never their comments, so a template's comment can change without putting any repository out of step), in the issue "Repositories out of step", and every caller `sync-repos.py callers` would add to it (#671: the ones `every-repository` names and those of its kind's [battery](../kinds/README.md#the-battery); the caller of its kind's release only when its entry in `repos.yml` gives its `slug` or its kind's place, wordpress.org for a plugin, publishes it). Before it, the same daily run opens, for each repository `repos.yml` lists whose callers fell behind, the issue and the pull request that bring it in step (`scripts/sync-repos.py callers`, #672), none while one is open; `python3 scripts/sync-repos.py callers --repo NAME` does the same for one by hand. A callers pull request closed without merging is opened again the next day: `repos.yml` `exclude` keeps a repository out. Run from a checkout that is `main` as it is now, the `org-sync` lane of [`policy/autonomy.yml`](../policy/autonomy.yml) accepts the issue, and the pull request merges on its own when every job it changes is its template's and none changes its permissions or secrets (a new job always waits); otherwise the maintainer accepts and merges them there. A new input a template asks to fill in goes into `repos.yml` (`callers`, `repository-owned`) in the same pull request, or the check would hold every repository to the template's example value, and `callers` writes no caller that would still hold it: that file is left out and named, with the repository and the input, and the others go in (#671). A template never says "Fill in": its header says where a value comes from, so a new caller is its template whole. Who signs what (#491) reaches the repositories in two steps. The release brings the rule (the `AGENTS.md` block, through `sync-repos.py files`), the conventions check at org.yml `attribution: warn` and the merge's trailers everywhere at once. Then each repository's part is a sub-issue of #491, accepted with it: remove any text of its own that contradicts the rule, give a public repository's `CONTRIBUTING.md` a "Credits" paragraph that points to [CONTRIBUTING.md, "Who signs what"](../CONTRIBUTING.md#who-signs-what), and in DiluxOne/dilux-agentic-workflow make `scripts/check_commits.py` and `ddw/rules/commits.instructions.md` accept `Assisted-by:` beside `AI-assisted:` and `AI-full:` and refuse a `Co-authored-by` only when it names a model, never a person: the cases `scripts/credits.py --test` marks C8 are the reference its tests mirror. Once every repository is in step, set `attribution: required`. On your own machine, `"includeCoAuthoredBy": false` in Claude Code's settings stops the trailer it adds by itself. diff --git a/policy/settings-schema.yml b/policy/settings-schema.yml index 48ca449c..8ff7b558 100644 --- a/policy/settings-schema.yml +++ b/policy/settings-schema.yml @@ -470,7 +470,7 @@ files: limit: reusable workflows of the engine read-by: [scripts/sync-repos.py] issue: "#191" - says: The workflows that make up the kind's battery. + says: The workflows that make up the kind's [battery](../kinds/README.md#the-battery). - key: own-checks org: any repo: never diff --git a/tests/workflows/test_battery_doc.py b/tests/workflows/test_battery_doc.py index ba47e4da..c36df1aa 100644 --- a/tests/workflows/test_battery_doc.py +++ b/tests/workflows/test_battery_doc.py @@ -51,3 +51,24 @@ def test_it_says_a_repository_runs_it_through_its_callers(): assert "through its callers" in text assert "workflow-templates/" in text assert "sync-repos.py callers" in text + + +LINK = re.compile(r"\[battery\]\(\.\./kinds/README\.md#the-battery\)") + + +def test_the_heading_gives_the_anchor_the_links_use(): + assert "\n## The battery\n" in KINDS.read_text(encoding="utf-8") + + +def test_the_first_battery_of_each_page_links_to_the_definition(): + for page in ("configuration.md", "adopting.md", "maintaining.md"): + text = (ROOT / "docs" / page).read_text(encoding="utf-8") + first = re.search(r"battery", text, re.IGNORECASE) + assert first, page + link = LINK.search(text) + assert link and link.start() == first.start() - 1, page + + +def test_the_configuration_link_comes_from_the_schema(): + schema = (ROOT / "policy" / "settings-schema.yml").read_text(encoding="utf-8") + assert LINK.search(schema) From a4f1d88b9cb31a8683fef3b56e8ed8e51f123611 Mon Sep 17 00:00:00 2001 From: "dilux-bot[bot]" <333187390+dilux-bot[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 14:34:24 +0000 Subject: [PATCH 4/4] chore: ready for review