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
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ end_of_line = lf
[catalog/snippets/husky/pre-commit]
end_of_line = lf

# This repository's own hook, paired with the `.gitattributes` pin.
# The git pin alone leaves the editor free to write a CRLF shebang, which would break it.
[.husky/pre-commit]
end_of_line = lf

# Linux scripts
[*.sh]
end_of_line = lf
Expand Down
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
*.sh text eol=lf
# The husky pre-commit snippet is an extensionless shebang script (like a copied .husky/pre-commit).
catalog/snippets/husky/pre-commit text eol=lf
# This repository's own hook, which is the extensionless case the comment above names.
.husky/pre-commit text eol=lf

# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows.
# Pin LF only for a `.py` executed directly via its shebang, by path.
Expand Down
52 changes: 52 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/sh
# Local pre-commit gate for this repository: the doc checks CI runs, each at the scope that fits it.
# Enable it per clone with `git config core.hooksPath .husky`.
# A clone carries no hooks path, so this file does nothing until that is set.
# It deliberately does not source `_/husky.sh`.
# That file is gitignored and arrives with an npm install this repository does not have.
# Sourcing it would therefore break the hook in a fresh clone.
# The path is kept for the fleet convention the line-ending pins are written against.
#
# The language-formatting half the fleet convention names is absent here, and measured rather than assumed.
# This repository declares ruff in `pyproject.toml`, no workflow runs it, and the tree does not pass it.
# `ruff format --check` reports 13 of 57 files would be reformatted and `ruff check` reports 106 errors.
# A gate failing on the corpus it guards blocks every commit from the moment it lands.
# Converging the Python comes first, and the step is added here after that rather than before it.
#
# `repo_gate.py --check sha-pin` is absent for a different reason.
# It resolves same-owner pins against the GitHub API, and a hook needing a network fails offline.
# The doc linters that need Docker stay in CI and in the VS Code Lint tasks.
set -e

# Git already runs a hook from the top level, measured by committing from `scripts/` and printing `pwd`.
# This is belt and braces for an invocation that does not come from git.
# The relative paths below would otherwise resolve against whatever directory the caller was in.
cd "$(git rev-parse --show-toplevel)"

# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
# On native Windows the python.org install registers `py` and not `python3`.
# That name resolves to a Microsoft Store alias stub, and Git Bash inherits the Windows PATH.
# The stub is on PATH and fails when run, so a presence test selects it and the hook then breaks.
# Running the probe is the whole point: it is what tells a working interpreter from a name.
if python3 --version >/dev/null 2>&1; then
run_py() { python3 "$@"; }
elif py -3 --version >/dev/null 2>&1; then
run_py() { py -3 "$@"; }
else
echo "pre-commit: neither 'python3 --version' nor 'py -3 --version' ran, so the doc gates did not run." >&2
echo "pre-commit: see docs/host-setup.md 'What a Host Must Provide'." >&2
exit 1
fi

# The prose gate is scoped to what changed against HEAD, which is the policy for prose.
# A rule is applied as a file is next edited rather than swept across the tree.
# Whole-tree costs about 2.2 seconds where the diff-scoped run costs about 0.13.
# The scope is the working tree rather than the index.
# A partially staged file is therefore judged on all of its edits, not only the staged ones.
# CI re-runs the same rules over the whole tree, which is what makes that affordable here.
run_py scripts/prose_lint.py . --diff HEAD

# The eol check is repo-wide rather than diff-scoped, and it is here because it is already fast.
# It reads `.gitattributes` against `.editorconfig` for the whole repository and takes no file list.
# At about 0.04 seconds there is nothing to scope, so scoping it would only make it wrong.
run_py scripts/repo_gate.py --check eol
2 changes: 1 addition & 1 deletion GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ CI runs the full lint set, but run the linters locally before pushing to catch i
**Each surface runs the lint with the tool that fits it, all from the same config files** (`.markdownlint-cli2.jsonc`, `cspell.json`, `.editorconfig`):

- **CI (authoritative)** runs **markdownlint-cli2**, **cspell**, and **actionlint** as pinned action wrappers (Dependabot bumps them), plus **editorconfig-checker** via Docker `:latest` (its action only installs the CLI, so the Docker one-liner is what actually runs the check). markdownlint covers all `**/*.md`, and **cspell is scoped to `README.md` + `HISTORY.md`** (see [CODESTYLE.md](./CODESTYLE.md) "Markdown and Spelling" for why), matching the cspell one-liner below.
- **The `.husky/pre-commit` hook** runs **language formatting only**: CSharpier + `dotnet format` (or ruff) via native tooling, no Docker and no doc linters, so it stays fast.
- **The `.husky/pre-commit` hook** runs **language formatting** and the **diff-scoped doc gates**, never Docker and never a network call, so it stays fast. The formatting half is whatever the repo's own language needs, CSharpier and `dotnet format` for .NET or ruff for Python, via native tooling. A repo adds each half once its tree passes that half, since a gate that fails on the corpus it guards blocks every commit from the moment it lands, so a hook running one half is a repo mid-convergence rather than a repo out of conformance. The doc half runs each gate at the scope that fits it. The prose gate is scoped to what the commit changes rather than swept over the tree, which is the difference between about 2.2 seconds and about 0.13 and is what makes it affordable in a hook at all. A whole-repo check belongs there too when it is already fast and takes no file list, which the line-ending consistency check is, so scope is a property of the gate rather than a rule the hook applies to all of them. `repo_gate.py --check sha-pin` stays out, since it resolves a same-owner pin against the GitHub API and a hook that needs a network fails offline. A repo enables the hook per clone with `git config core.hooksPath .husky`, and CI remains the authoritative run either way.
- **The VS Code Lint tasks** run the full doc-lint set via Docker `:latest` on demand, the local surface for Markdown, spelling, workflow, and EditorConfig checks.

The Docker invocations below are the same ones the VS Code tasks use, for ad-hoc or headless (agent) runs.
Expand Down
16 changes: 6 additions & 10 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ One pull request moving the canonical short description into declared data, so e

- **Close the README-to-About hop, which is the only one nothing writes.** The audit reports a drifted About panel, and no tool sets it.
- **Blocked by** - The entry above, since the field is what `repo-config/configure.sh` would set the panel from.
- **Issue** - [#577][issue-577], whose tagline half shipped on 2026-08-08.
- **Issue** - [#639][issue-639], filed on 2026-08-09 because this entry had been carrying [#577][issue-577], whose body covers only the README tagline and never mentions the About panel, and whose tagline half shipped on 2026-08-08.
- **Checked** - `develop` on 2026-08-08, where `repo-config/configure.sh` sets every other repository setting and carries no `description` handling, and [`catalog/snippets/workflows/publish-docker-readme-task.yml`][workflows] pushes `github.event.repository.description` to Docker Hub.
- **Open** - Nothing beyond sequencing.
- **Settled** - The chain is README, then the About panel by hand, then Docker Hub by CI, so the unautomated hop is the first one and it is the one that drifts. PhotoCleaner is the worked case, where the About panel still matched the README and only the Docker Hub short description had diverged.
Expand Down Expand Up @@ -124,7 +124,7 @@ One pull request measuring the remaining carried surface against the carry-versu

The spec rework and its audit check shipped. What remains is the per-repo conformance the check now reports, and one section the fleet carries that the model does not name.

**State** `backlog`. **Touches** each repo's `README.md` on its next visit, plus [`spec/readme-structure.md`][readme-structure] and [`spec/readme-sections.json`][readme-sections] if `Build Artifacts` is adopted. **Cost** one edit per repo, driven by the finding rather than by a sweep.
**State** `decision`, on where `## Build Artifacts` belongs, which is the only thing here a hub pull request settles. The four conformance entries above it are not selectable as hub work at all: each lands on a repo's own next visit, in the sense "Fleet Sweeps" below gives that phrase, and they sit here rather than there because the finding counts are what the shipped check measures. **Touches** each repo's `README.md` on its next visit, plus [`spec/readme-structure.md`][readme-structure] and [`spec/readme-sections.json`][readme-sections] if `Build Artifacts` is adopted. **Cost** one edit per repo, driven by the finding rather than by a sweep.

- **Work off the conformance backlog the `readme-structure` dimension now reports.** Measured across all 22 cataloged repos on 2026-08-08, against the shipped checks: 73 findings, 71 on sections and 2 on shields, plus the 3 retired-badge findings the entry below carries.
- **Blocked by** - Nothing, and no repo is edited by the hub. Each lands on its own next visit.
Expand Down Expand Up @@ -375,6 +375,7 @@ Small work with no research to preserve, selectable one bullet at a time.
- **Reconsider whether the pre-commit hook runs the doc gates now that they are diff-scoped.** [`scripts/README.md`][scripts] records the current decision and its reason, that doc linters stay out of the hook so it stays fast, which was sound when the only mode was a whole-tree sweep, and a diff-scoped run finishes in about a second. The failure it would prevent is the most repeated one on record, comment sentences wrapped across lines caught after the commit rather than before it. Weigh it against the standing preference for a fast hook and against a hook that runs the gate from the wrong directory, which is its own false clean.
- **Audit the fleet's shell surface by size and branching, and decide per script whether Python with unit tests is cheaper.** The evidence is the review record rather than a language preference, since a non-trivial shell script earns findings round after round while every gate under [`scripts/`][scripts] carries a test file beside it and converges in one or two. The measure is lines, branch count, and the review rounds each has cost. `repo-config/configure.sh` and the agent-safety installer are the two worth measuring, and a bootstrap script that needs the Python it exists to install is not a rewrite worth having, which protects the installer more than the config script.
- **Make a table of contents standard for a long document rather than for the README alone.** [`spec/readme-structure.md`][readme-structure] fixes one at README position 4 and no other hub file carries one, which leaves the three longest documents without it, `CODESTYLE.md` at 516 lines, `GOVERNANCE.md` at 436 and `WORKFLOW.md` at 301, measured on `develop` at `3d1a0b1` on 2026-08-06. Settle the threshold in headings or lines so the audit can check it, and settle how it sits with the reference-link exception, since the four agent-instruction files keep inline links exactly because they are read one section at a time, which is the property that makes a contents list worth having in them. The mechanical constraint is that the list is filled by the Markdown All in One extension on save, so a file nobody opens in the editor grows a stale list, which is worse than absent because it is read as current.
- **Converge this repo's Python on the ruff configuration it already declares, then add the formatting half to the pre-commit hook.** `pyproject.toml` carries `[tool.ruff]` and [`spec/project-types.json`][project-types] declares `python.ruff.config`, yet no workflow runs ruff and the tree does not pass it, measured on `develop` at `6d020b1` on 2026-08-09 with ruff 0.16.2: `ruff format --check` reports 13 of 57 files would be reformatted, and `ruff check` reports 106 errors, of which 39 are auto-fixable. The largest groups are 24 `PLW1510` (a `subprocess.run` with no `check`), 17 `FURB167` (`re.M` for `re.MULTILINE`), 11 `EXE001` (a shebang on a non-executable file, which wants reading against the `eol-coverage` shebang set rather than fixed blindly), 9 `BLE001` and 9 `SIM117`. The hook deliberately ships without the ruff step for this reason, since a gate failing on the corpus it guards blocks every commit from the moment it lands, which is the measure-the-corpus-first rule applied to a gate rather than to an exemption. Decide whether CI gains a ruff job in the same pass, since a formatter enforced only by a hook is enforced only on the machines that enabled it.
- **Adopt the OCI annotation keys for Docker image metadata across the Docker repos**, replacing the ad-hoc and label-schema keys, per [#363][issue-363].
- **Sweep the central package-version property to `Directory.Packages.props` fleet-wide**, since PlexCleaner sets it in `Directory.Build.props`, off the [`CODESTYLE.md`][codestyle] canonical.
- **Canonicalize Python linter-config placement on `pyproject.toml`**, since one cataloged repo uses a standalone ruff config plus a pyright config. Track it as a drift finding and fix it downstream.
Expand All @@ -390,7 +391,7 @@ Work that lands on a downstream visit rather than as a hub pull request, so it i

Blog is the pilot. A sweep is proven there before any fleet-wide rollout, because it is the smallest tree, `hugo` plus `source-only` with no build to break, cataloged and audited on 2026-08-05, and one of only two repos carrying `AGENTS.md` "Fleet Bootstrap" today, so a carried-section change can be observed arriving there. The other carrier is HomeAutomation-Config, which is `operational` and therefore exercises the direct-to-`develop` path rather than the pull request one, which is the second visit worth making rather than the first.

Regenerate [reports/divergences.md][divergences-report] before using it as the work list, since the committed copy predates the retirement decision and renders `repo-config/configure.sh` under a re-vendor disposition that no longer applies to it. A stale ledger is the same hazard as a stale exemption, in that it hands out a work list measured against a tree that no longer exists.
Regenerate [reports/divergences.md][divergences-report] before using it as the work list, since it is a live pass over each repo's ground-truth branch and the committed copy is only as current as its last run. A stale ledger is the same hazard as a stale exemption, in that it hands out a work list measured against a tree that no longer exists. The reason this line used to give, that the committed copy still rendered `repo-config/configure.sh` under a re-vendor disposition, did not survive the check: that copy already carried the `retire` disposition, so the warning was true of the decision rather than of the file. What the 2026-08-09 regeneration actually moved was three rows, adding `AGENTS.md` "Fleet Bootstrap" as divergent at Blog and HomeAutomation-Config, and widening `GOVERNANCE.md` "Verification Discipline" and "Workflow YAML Conventions" from one repo to four.

- **Re-vendor the changed `verbatim` content, which is one sweep covering seven files.** Every repo holding a copy of a changed section is byte-mismatched against the hub until it takes the new one, which the audit reports as stale rather than modified.
- **Hub state** - Done, verified `develop` at `3d1a0b1` on 2026-08-06 for the sections below, with the prose batch adding five more [`GOVERNANCE.md`][governance] sections, verified `develop` at `d791930` on 2026-08-07.
Expand Down Expand Up @@ -460,18 +461,12 @@ Regenerate [reports/divergences.md][divergences-report] before using it as the w
Actions on issues that are the maintainer's to take, each carrying its evidence so it is one action rather than a re-derivation.

- **Re-scope [#305][issue-305] to the push half, and make it the tracking issue for the fleet re-vendor sweep.** Most of what it asked for is built, since the fidelity model, the [`spec/files.json`][files] manifest, [`spec/divergences.json`][divergences] with its generated [reports/divergences.md][divergences-report], and [`AUDIT.md`][audit-doc] section 10 together give the canonical-versus-adapted split and the audit path it proposed. What is genuinely still missing is the push half, since every one of those detects drift while the sweep that fixes it is manual. Re-scoped, it carries the "Fleet Sweeps" visit manifest and Blog as the pilot. Closing it against the built machinery is the alternative, and it loses the only tracking issue the sweep would have.
- **Comment on [#577][issue-577] that it is decided together with the declared description.** Declaring the field in [`registry/repos.json`][repos] makes every mirror read a field rather than parse a paragraph, so taking [#577][issue-577] first means writing an extraction rule the registry change then deletes.

## Verified Complete, Awaiting Close

Each was checked against the tree and has nothing left to do anywhere. Closing is the maintainer's call, and each wants the evidence quoted in the closing comment rather than a bare close.

- **[#578][issue-578], three rules that state the common case and leave the recurring one unstated.** Complete on all three items.
- **Fixed by** - The pull request carrying this entry, since the fix and the entry ship in one squash and the closing comment cites that SHA.
- **Checked** - `develop` at `a706ddb` on 2026-08-08, where all three gaps were re-read before the widening was written.
- **Closing evidence** - [`GOVERNANCE.md`][governance] "Branching Model" now states that an issue closes when its work is verifiably complete and that the keyword automates the case where completion and promotion coincide rather than adding a condition to it, naming work complete on `develop` with no promotion imminent as the second hand-close case beside a promotion that merged without the keyword, which answers item 1. "Communicating with the User" now says the message carrying the clickable link comes **before** the prompt rather than merely alongside it, since a prompt blocks on an answer and a later message arrives after that answer is given, which answers item 2. "Operational Repositories" now states when to decline the direct-commit grant, as a shape rather than a line count, and records that it stays guidance because a `pull_request` rule on the operational ruleset would gate the direct push and withdraw the allowance, which answers item 3.
- **Detail** - The item 1 rule sits in "Branching Model" rather than the "Git and Commit Rules" the retired cluster named, which is where the re-vendor has to look for it.
- **Detail** - Sweeping item 3 by term rather than by the instance the issue named found [`WORKFLOW.md`][workflow] section 3 restating the same allowance with the same silence, so it now points at the section that owns the test rather than repeating it, which is one rule in one place and a cross-reference for the second reader.
Nothing is awaiting close today. [#578][issue-578] was the last entry here and closed on 2026-08-08, and the part of it the fleet still owes is carried by the re-vendor entry under "Fleet Sweeps", which names the three sections it touches.

<!-- Issues -->

Expand All @@ -498,6 +493,7 @@ Each was checked against the tree and has nothing left to do anywhere. Closing i
[issue-607]: https://github.com/ptr727/ProjectTemplate/issues/607
[issue-623]: https://github.com/ptr727/ProjectTemplate/issues/623
[issue-633]: https://github.com/ptr727/ProjectTemplate/issues/633
[issue-639]: https://github.com/ptr727/ProjectTemplate/issues/639

<!-- Pull requests -->

Expand Down
Loading