Commit 0a86bca

Browse files
authored
Promote the Tagline Close, the Ledger Refresh, and the Pre-Commit Doc Gates (#643)
Three commits, all backlog bookkeeping and local tooling. No rule changes meaning, and nothing here alters what CI gates. **#640, the tagline question.** #577 asked that the tagline be a distinct thing from the intro so a README may carry a second paragraph without the cap or the mirrors reaching it. That shipped in `c2ce145` (#632), so the issue is closed against it. Grepping the number before closing found `TODO.md` had also given #577 to a different entry, the README-to-About hop, which the issue's body never covers. That hop now carries #639 of its own. **#641, the divergence ledger.** `reports/divergences.md` is a live pass over each repo's ground-truth branch, so a committed copy is only as current as its last run. Regenerating moved three rows: `AGENTS.md` "Fleet Bootstrap" is now divergent at Blog and HomeAutomation-Config, and `GOVERNANCE.md` "Verification Discipline" and "Workflow YAML Conventions" widened from one repo to four. The reason `TODO.md` gave for regenerating did not survive the run and is corrected. Two spent backlog claims went with it. **#642, the pre-commit hook.** The chore was to reconsider whether the hook should run the doc gates now that they are diff-scoped. Measuring first found this repository had no hook at all, while `GOVERNANCE.md` and `scripts/README.md` both described what one runs. There is now a committed `.husky/pre-commit` running the prose gate diff-scoped and the line-ending check repo-wide, with the `.gitattributes` and `.editorconfig` pins that `GOVERNANCE.md` "Line Endings" makes mandatory for an extensionless hook. The formatting half the fleet convention names is deliberately absent, and that is the finding worth carrying: this repository declares `[tool.ruff]` in `pyproject.toml` and `spec/project-types.json` declares `python.ruff.config`, yet no workflow runs ruff and the tree does not pass it, at 13 of 57 files unformatted and 106 lint errors. A ruff step would have blocked every commit from the moment it landed. The `GOVERNANCE.md` bullet now states the condition rather than a tool list, that a repo adds each half once its tree passes that half, and the measurement is recorded as a `TODO.md` chore. ## Carried content in this promotion `GOVERNANCE.md` "Running the Linters Locally" changed, and that section is carried `verbatim`, so every downstream copy is byte-mismatched until the next fleet visit. It rides the re-vendor entry already tracked under `TODO.md` "Fleet Sweeps" rather than owing a sweep of its own. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents b4b8f2e + 25e01e3 commit 0a86bca

22 files changed

Lines changed: 244 additions & 68 deletions

‎.editorconfig‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ end_of_line = lf
6060
[catalog/snippets/husky/pre-commit]
6161
end_of_line = lf
6262

63+
# This repository's own hook, paired with the `.gitattributes` pin.
64+
# The git pin alone leaves the editor free to write a CRLF shebang, which would break it.
65+
[.husky/pre-commit]
66+
end_of_line = lf
67+
6368
# Linux scripts
6469
[*.sh]
6570
end_of_line = lf

‎.gitattributes‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*.shtexteol=lf
1414
# The husky pre-commit snippet is an extensionless shebang script (like a copied .husky/pre-commit).
1515
catalog/snippets/husky/pre-committexteol=lf
16+
# This repository's own hook, which is the extensionless case the comment above names.
17+
.husky/pre-committexteol=lf
1618

1719
# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows.
1820
# Pin LF only for a `.py` executed directly via its shebang, by path.

‎.husky/pre-commit‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/sh
2+
# Local pre-commit gate for this repository: the doc checks CI runs, each at the scope that fits it.
3+
# Enable it per clone with `git config core.hooksPath .husky`.
4+
# A clone carries no hooks path, so this file does nothing until that is set.
5+
# It deliberately does not source `_/husky.sh`.
6+
# That file is gitignored and arrives with an npm install this repository does not have.
7+
# Sourcing it would therefore break the hook in a fresh clone.
8+
# The path is kept for the fleet convention the line-ending pins are written against.
9+
#
10+
# The language-formatting half the fleet convention names is absent here, and measured rather than assumed.
11+
# This repository declares ruff in `pyproject.toml`, no workflow runs it, and the tree does not pass it.
12+
# `ruff format --check` reports 13 of 57 files would be reformatted and `ruff check` reports 106 errors.
13+
# A gate failing on the corpus it guards blocks every commit from the moment it lands.
14+
# Converging the Python comes first, and the step is added here after that rather than before it.
15+
#
16+
# `repo_gate.py --check sha-pin` is absent for a different reason.
17+
# It resolves same-owner pins against the GitHub API, and a hook needing a network fails offline.
18+
# The doc linters that need Docker stay in CI and in the VS Code Lint tasks.
19+
set -e
20+
21+
# Git already runs a hook from the top level, measured by committing from `scripts/` and printing `pwd`.
22+
# This is belt and braces for an invocation that does not come from git.
23+
# The relative paths below would otherwise resolve against whatever directory the caller was in.
24+
cd"$(git rev-parse --show-toplevel)"
25+
26+
# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
27+
# On native Windows the python.org install registers `py` and not `python3`.
28+
# That name resolves to a Microsoft Store alias stub, and Git Bash inherits the Windows PATH.
29+
# The stub is on PATH and fails when run, so a presence test selects it and the hook then breaks.
30+
# Running the probe is the whole point: it is what tells a working interpreter from a name.
31+
if python3 --version >/dev/null 2>&1;then
32+
run_py() { python3 "$@"; }
33+
elif py -3 --version >/dev/null 2>&1;then
34+
run_py() { py -3 "$@"; }
35+
else
36+
echo"pre-commit: neither 'python3 --version' nor 'py -3 --version' ran, so the doc gates did not run.">&2
37+
echo"pre-commit: see docs/host-setup.md 'What a Host Must Provide'.">&2
38+
exit 1
39+
fi
40+
41+
# The prose gate is scoped to what changed against HEAD, which is the policy for prose.
42+
# A rule is applied as a file is next edited rather than swept across the tree.
43+
# Whole-tree costs about 2.2 seconds where the diff-scoped run costs about 0.13.
44+
# The scope is the working tree rather than the index.
45+
# A partially staged file is therefore judged on all of its edits, not only the staged ones.
46+
# CI re-runs the same rules over the whole tree, which is what makes that affordable here.
47+
run_py scripts/prose_lint.py . --diff HEAD
48+
49+
# The eol check is repo-wide rather than diff-scoped, and it is here because it is already fast.
50+
# It reads `.gitattributes` against `.editorconfig` for the whole repository and takes no file list.
51+
# At about 0.04 seconds there is nothing to scope, so scoping it would only make it wrong.
52+
run_py scripts/repo_gate.py --check eol

‎GOVERNANCE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ CI runs the full lint set, but run the linters locally before pushing to catch i
368368
**Each surface runs the lint with the tool that fits it, all from the same config files** (`.markdownlint-cli2.jsonc`, `cspell.json`, `.editorconfig`):
369369

370370
-**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.
371-
-**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.
371+
- **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.
372372
-**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.
373373

374374
The Docker invocations below are the same ones the VS Code tasks use, for ad-hoc or headless (agent) runs.

‎TODO.md‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ One pull request moving the canonical short description into declared data, so e
5656

5757
-**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.
5858
-**Blocked by** - The entry above, since the field is what `repo-config/configure.sh` would set the panel from.
59-
-**Issue** - [#577][issue-577], whose tagline half shipped on 2026-08-08.
59+
-**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.
6060
-**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.
6161
-**Open** - Nothing beyond sequencing.
6262
-**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.
@@ -124,7 +124,7 @@ One pull request measuring the remaining carried surface against the carry-versu
124124

125125
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.
126126

127-
**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.
127+
**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.
128128

129129
-**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.
130130
-**Blocked by** - Nothing, and no repo is edited by the hub. Each lands on its own next visit.
@@ -375,6 +375,7 @@ Small work with no research to preserve, selectable one bullet at a time.
375375
-**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.
376376
-**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.
377377
-**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.
378+
- **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.
378379
-**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].
379380
-**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.
380381
-**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.
@@ -390,7 +391,7 @@ Work that lands on a downstream visit rather than as a hub pull request, so it i
390391

391392
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.
392393

393-
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.
394+
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.
394395

395396
-**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.
396397
-**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.
@@ -460,18 +461,12 @@ Regenerate [reports/divergences.md][divergences-report] before using it as the w
460461
Actions on issues that are the maintainer's to take, each carrying its evidence so it is one action rather than a re-derivation.
461462

462463
-**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.
463-
-**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.
464464

465465
## Verified Complete, Awaiting Close
466466

467467
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.
468468

469-
-**[#578][issue-578], three rules that state the common case and leave the recurring one unstated.** Complete on all three items.
470-
-**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.
471-
-**Checked** - `develop` at `a706ddb` on 2026-08-08, where all three gaps were re-read before the widening was written.
472-
-**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.
473-
-**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.
474-
-**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.
469+
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.
475470

476471
<!-- Issues -->
477472

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

502498
<!-- Pull requests -->
503499

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 0a86bca

Browse files
authored
Promote the Tagline Close, the Ledger Refresh, and the Pre-Commit Doc Gates (#643)
Three commits, all backlog bookkeeping and local tooling. No rule changes meaning, and nothing here alters what CI gates. **#640, the tagline question.** #577 asked that the tagline be a distinct thing from the intro so a README may carry a second paragraph without the cap or the mirrors reaching it. That shipped in `c2ce145` (#632), so the issue is closed against it. Grepping the number before closing found `TODO.md` had also given #577 to a different entry, the README-to-About hop, which the issue's body never covers. That hop now carries #639 of its own. **#641, the divergence ledger.** `reports/divergences.md` is a live pass over each repo's ground-truth branch, so a committed copy is only as current as its last run. Regenerating moved three rows: `AGENTS.md` "Fleet Bootstrap" is now divergent at Blog and HomeAutomation-Config, and `GOVERNANCE.md` "Verification Discipline" and "Workflow YAML Conventions" widened from one repo to four. The reason `TODO.md` gave for regenerating did not survive the run and is corrected. Two spent backlog claims went with it. **#642, the pre-commit hook.** The chore was to reconsider whether the hook should run the doc gates now that they are diff-scoped. Measuring first found this repository had no hook at all, while `GOVERNANCE.md` and `scripts/README.md` both described what one runs. There is now a committed `.husky/pre-commit` running the prose gate diff-scoped and the line-ending check repo-wide, with the `.gitattributes` and `.editorconfig` pins that `GOVERNANCE.md` "Line Endings" makes mandatory for an extensionless hook. The formatting half the fleet convention names is deliberately absent, and that is the finding worth carrying: this repository declares `[tool.ruff]` in `pyproject.toml` and `spec/project-types.json` declares `python.ruff.config`, yet no workflow runs ruff and the tree does not pass it, at 13 of 57 files unformatted and 106 lint errors. A ruff step would have blocked every commit from the moment it landed. The `GOVERNANCE.md` bullet now states the condition rather than a tool list, that a repo adds each half once its tree passes that half, and the measurement is recorded as a `TODO.md` chore. ## Carried content in this promotion `GOVERNANCE.md` "Running the Linters Locally" changed, and that section is carried `verbatim`, so every downstream copy is byte-mismatched until the next fleet visit. It rides the re-vendor entry already tracked under `TODO.md` "Fleet Sweeps" rather than owing a sweep of its own. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents b4b8f2e + 25e01e3 commit 0a86bca

22 files changed

Lines changed: 244 additions & 68 deletions

‎.editorconfig‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ end_of_line = lf
6060
[catalog/snippets/husky/pre-commit]
6161
end_of_line = lf
6262

63+
# This repository's own hook, paired with the `.gitattributes` pin.
64+
# The git pin alone leaves the editor free to write a CRLF shebang, which would break it.
65+
[.husky/pre-commit]
66+
end_of_line = lf
67+
6368
# Linux scripts
6469
[*.sh]
6570
end_of_line = lf

‎.gitattributes‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*.shtexteol=lf
1414
# The husky pre-commit snippet is an extensionless shebang script (like a copied .husky/pre-commit).
1515
catalog/snippets/husky/pre-committexteol=lf
16+
# This repository's own hook, which is the extensionless case the comment above names.
17+
.husky/pre-committexteol=lf
1618

1719
# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows.
1820
# Pin LF only for a `.py` executed directly via its shebang, by path.

‎.husky/pre-commit‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/sh
2+
# Local pre-commit gate for this repository: the doc checks CI runs, each at the scope that fits it.
3+
# Enable it per clone with `git config core.hooksPath .husky`.
4+
# A clone carries no hooks path, so this file does nothing until that is set.
5+
# It deliberately does not source `_/husky.sh`.
6+
# That file is gitignored and arrives with an npm install this repository does not have.
7+
# Sourcing it would therefore break the hook in a fresh clone.
8+
# The path is kept for the fleet convention the line-ending pins are written against.
9+
#
10+
# The language-formatting half the fleet convention names is absent here, and measured rather than assumed.
11+
# This repository declares ruff in `pyproject.toml`, no workflow runs it, and the tree does not pass it.
12+
# `ruff format --check` reports 13 of 57 files would be reformatted and `ruff check` reports 106 errors.
13+
# A gate failing on the corpus it guards blocks every commit from the moment it lands.
14+
# Converging the Python comes first, and the step is added here after that rather than before it.
15+
#
16+
# `repo_gate.py --check sha-pin` is absent for a different reason.
17+
# It resolves same-owner pins against the GitHub API, and a hook needing a network fails offline.
18+
# The doc linters that need Docker stay in CI and in the VS Code Lint tasks.
19+
set -e
20+
21+
# Git already runs a hook from the top level, measured by committing from `scripts/` and printing `pwd`.
22+
# This is belt and braces for an invocation that does not come from git.
23+
# The relative paths below would otherwise resolve against whatever directory the caller was in.
24+
cd"$(git rev-parse --show-toplevel)"
25+
26+
# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
27+
# On native Windows the python.org install registers `py` and not `python3`.
28+
# That name resolves to a Microsoft Store alias stub, and Git Bash inherits the Windows PATH.
29+
# The stub is on PATH and fails when run, so a presence test selects it and the hook then breaks.
30+
# Running the probe is the whole point: it is what tells a working interpreter from a name.
31+
if python3 --version >/dev/null 2>&1;then
32+
run_py() { python3 "$@"; }
33+
elif py -3 --version >/dev/null 2>&1;then
34+
run_py() { py -3 "$@"; }
35+
else
36+
echo"pre-commit: neither 'python3 --version' nor 'py -3 --version' ran, so the doc gates did not run.">&2
37+
echo"pre-commit: see docs/host-setup.md 'What a Host Must Provide'.">&2
38+
exit 1
39+
fi
40+
41+
# The prose gate is scoped to what changed against HEAD, which is the policy for prose.
42+
# A rule is applied as a file is next edited rather than swept across the tree.
43+
# Whole-tree costs about 2.2 seconds where the diff-scoped run costs about 0.13.
44+
# The scope is the working tree rather than the index.
45+
# A partially staged file is therefore judged on all of its edits, not only the staged ones.
46+
# CI re-runs the same rules over the whole tree, which is what makes that affordable here.
47+
run_py scripts/prose_lint.py . --diff HEAD
48+
49+
# The eol check is repo-wide rather than diff-scoped, and it is here because it is already fast.
50+
# It reads `.gitattributes` against `.editorconfig` for the whole repository and takes no file list.
51+
# At about 0.04 seconds there is nothing to scope, so scoping it would only make it wrong.
52+
run_py scripts/repo_gate.py --check eol

‎GOVERNANCE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ CI runs the full lint set, but run the linters locally before pushing to catch i
368368
**Each surface runs the lint with the tool that fits it, all from the same config files** (`.markdownlint-cli2.jsonc`, `cspell.json`, `.editorconfig`):
369369

370370
-**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.
371-
-**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.
371+
- **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.
372372
-**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.
373373

374374
The Docker invocations below are the same ones the VS Code tasks use, for ad-hoc or headless (agent) runs.

‎TODO.md‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ One pull request moving the canonical short description into declared data, so e
5656

5757
-**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.
5858
-**Blocked by** - The entry above, since the field is what `repo-config/configure.sh` would set the panel from.
59-
-**Issue** - [#577][issue-577], whose tagline half shipped on 2026-08-08.
59+
-**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.
6060
-**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.
6161
-**Open** - Nothing beyond sequencing.
6262
-**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.
@@ -124,7 +124,7 @@ One pull request measuring the remaining carried surface against the carry-versu
124124

125125
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.
126126

127-
**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.
127+
**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.
128128

129129
-**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.
130130
-**Blocked by** - Nothing, and no repo is edited by the hub. Each lands on its own next visit.
@@ -375,6 +375,7 @@ Small work with no research to preserve, selectable one bullet at a time.
375375
-**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.
376376
-**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.
377377
-**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.
378+
- **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.
378379
-**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].
379380
-**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.
380381
-**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.
@@ -390,7 +391,7 @@ Work that lands on a downstream visit rather than as a hub pull request, so it i
390391

391392
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.
392393

393-
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.
394+
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.
394395

395396
-**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.
396397
-**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.
@@ -460,18 +461,12 @@ Regenerate [reports/divergences.md][divergences-report] before using it as the w
460461
Actions on issues that are the maintainer's to take, each carrying its evidence so it is one action rather than a re-derivation.
461462

462463
-**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.
463-
-**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.
464464

465465
## Verified Complete, Awaiting Close
466466

467467
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.
468468

469-
-**[#578][issue-578], three rules that state the common case and leave the recurring one unstated.** Complete on all three items.
470-
-**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.
471-
-**Checked** - `develop` at `a706ddb` on 2026-08-08, where all three gaps were re-read before the widening was written.
472-
-**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.
473-
-**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.
474-
-**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.
469+
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.
475470

476471
<!-- Issues -->
477472

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

502498
<!-- Pull requests -->
503499

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 0a86bca

Browse files
authored
Promote the Tagline Close, the Ledger Refresh, and the Pre-Commit Doc Gates (#643)
Three commits, all backlog bookkeeping and local tooling. No rule changes meaning, and nothing here alters what CI gates. **#640, the tagline question.** #577 asked that the tagline be a distinct thing from the intro so a README may carry a second paragraph without the cap or the mirrors reaching it. That shipped in `c2ce145` (#632), so the issue is closed against it. Grepping the number before closing found `TODO.md` had also given #577 to a different entry, the README-to-About hop, which the issue's body never covers. That hop now carries #639 of its own. **#641, the divergence ledger.** `reports/divergences.md` is a live pass over each repo's ground-truth branch, so a committed copy is only as current as its last run. Regenerating moved three rows: `AGENTS.md` "Fleet Bootstrap" is now divergent at Blog and HomeAutomation-Config, and `GOVERNANCE.md` "Verification Discipline" and "Workflow YAML Conventions" widened from one repo to four. The reason `TODO.md` gave for regenerating did not survive the run and is corrected. Two spent backlog claims went with it. **#642, the pre-commit hook.** The chore was to reconsider whether the hook should run the doc gates now that they are diff-scoped. Measuring first found this repository had no hook at all, while `GOVERNANCE.md` and `scripts/README.md` both described what one runs. There is now a committed `.husky/pre-commit` running the prose gate diff-scoped and the line-ending check repo-wide, with the `.gitattributes` and `.editorconfig` pins that `GOVERNANCE.md` "Line Endings" makes mandatory for an extensionless hook. The formatting half the fleet convention names is deliberately absent, and that is the finding worth carrying: this repository declares `[tool.ruff]` in `pyproject.toml` and `spec/project-types.json` declares `python.ruff.config`, yet no workflow runs ruff and the tree does not pass it, at 13 of 57 files unformatted and 106 lint errors. A ruff step would have blocked every commit from the moment it landed. The `GOVERNANCE.md` bullet now states the condition rather than a tool list, that a repo adds each half once its tree passes that half, and the measurement is recorded as a `TODO.md` chore. ## Carried content in this promotion `GOVERNANCE.md` "Running the Linters Locally" changed, and that section is carried `verbatim`, so every downstream copy is byte-mismatched until the next fleet visit. It rides the re-vendor entry already tracked under `TODO.md` "Fleet Sweeps" rather than owing a sweep of its own. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents b4b8f2e + 25e01e3 commit 0a86bca

22 files changed

Lines changed: 244 additions & 68 deletions

‎.editorconfig‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ end_of_line = lf
6060
[catalog/snippets/husky/pre-commit]
6161
end_of_line = lf
6262

63+
# This repository's own hook, paired with the `.gitattributes` pin.
64+
# The git pin alone leaves the editor free to write a CRLF shebang, which would break it.
65+
[.husky/pre-commit]
66+
end_of_line = lf
67+
6368
# Linux scripts
6469
[*.sh]
6570
end_of_line = lf

‎.gitattributes‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*.shtexteol=lf
1414
# The husky pre-commit snippet is an extensionless shebang script (like a copied .husky/pre-commit).
1515
catalog/snippets/husky/pre-committexteol=lf
16+
# This repository's own hook, which is the extensionless case the comment above names.
17+
.husky/pre-committexteol=lf
1618

1719
# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows.
1820
# Pin LF only for a `.py` executed directly via its shebang, by path.

‎.husky/pre-commit‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/sh
2+
# Local pre-commit gate for this repository: the doc checks CI runs, each at the scope that fits it.
3+
# Enable it per clone with `git config core.hooksPath .husky`.
4+
# A clone carries no hooks path, so this file does nothing until that is set.
5+
# It deliberately does not source `_/husky.sh`.
6+
# That file is gitignored and arrives with an npm install this repository does not have.
7+
# Sourcing it would therefore break the hook in a fresh clone.
8+
# The path is kept for the fleet convention the line-ending pins are written against.
9+
#
10+
# The language-formatting half the fleet convention names is absent here, and measured rather than assumed.
11+
# This repository declares ruff in `pyproject.toml`, no workflow runs it, and the tree does not pass it.
12+
# `ruff format --check` reports 13 of 57 files would be reformatted and `ruff check` reports 106 errors.
13+
# A gate failing on the corpus it guards blocks every commit from the moment it lands.
14+
# Converging the Python comes first, and the step is added here after that rather than before it.
15+
#
16+
# `repo_gate.py --check sha-pin` is absent for a different reason.
17+
# It resolves same-owner pins against the GitHub API, and a hook needing a network fails offline.
18+
# The doc linters that need Docker stay in CI and in the VS Code Lint tasks.
19+
set -e
20+
21+
# Git already runs a hook from the top level, measured by committing from `scripts/` and printing `pwd`.
22+
# This is belt and braces for an invocation that does not come from git.
23+
# The relative paths below would otherwise resolve against whatever directory the caller was in.
24+
cd"$(git rev-parse --show-toplevel)"
25+
26+
# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
27+
# On native Windows the python.org install registers `py` and not `python3`.
28+
# That name resolves to a Microsoft Store alias stub, and Git Bash inherits the Windows PATH.
29+
# The stub is on PATH and fails when run, so a presence test selects it and the hook then breaks.
30+
# Running the probe is the whole point: it is what tells a working interpreter from a name.
31+
if python3 --version >/dev/null 2>&1;then
32+
run_py() { python3 "$@"; }
33+
elif py -3 --version >/dev/null 2>&1;then
34+
run_py() { py -3 "$@"; }
35+
else
36+
echo"pre-commit: neither 'python3 --version' nor 'py -3 --version' ran, so the doc gates did not run.">&2
37+
echo"pre-commit: see docs/host-setup.md 'What a Host Must Provide'.">&2
38+
exit 1
39+
fi
40+
41+
# The prose gate is scoped to what changed against HEAD, which is the policy for prose.
42+
# A rule is applied as a file is next edited rather than swept across the tree.
43+
# Whole-tree costs about 2.2 seconds where the diff-scoped run costs about 0.13.
44+
# The scope is the working tree rather than the index.
45+
# A partially staged file is therefore judged on all of its edits, not only the staged ones.
46+
# CI re-runs the same rules over the whole tree, which is what makes that affordable here.
47+
run_py scripts/prose_lint.py . --diff HEAD
48+
49+
# The eol check is repo-wide rather than diff-scoped, and it is here because it is already fast.
50+
# It reads `.gitattributes` against `.editorconfig` for the whole repository and takes no file list.
51+
# At about 0.04 seconds there is nothing to scope, so scoping it would only make it wrong.
52+
run_py scripts/repo_gate.py --check eol

‎GOVERNANCE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ CI runs the full lint set, but run the linters locally before pushing to catch i
368368
**Each surface runs the lint with the tool that fits it, all from the same config files** (`.markdownlint-cli2.jsonc`, `cspell.json`, `.editorconfig`):
369369

370370
-**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.
371-
-**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.
371+
- **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.
372372
-**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.
373373

374374
The Docker invocations below are the same ones the VS Code tasks use, for ad-hoc or headless (agent) runs.

‎TODO.md‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ One pull request moving the canonical short description into declared data, so e
5656

5757
-**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.
5858
-**Blocked by** - The entry above, since the field is what `repo-config/configure.sh` would set the panel from.
59-
-**Issue** - [#577][issue-577], whose tagline half shipped on 2026-08-08.
59+
-**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.
6060
-**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.
6161
-**Open** - Nothing beyond sequencing.
6262
-**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.
@@ -124,7 +124,7 @@ One pull request measuring the remaining carried surface against the carry-versu
124124

125125
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.
126126

127-
**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.
127+
**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.
128128

129129
-**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.
130130
-**Blocked by** - Nothing, and no repo is edited by the hub. Each lands on its own next visit.
@@ -375,6 +375,7 @@ Small work with no research to preserve, selectable one bullet at a time.
375375
-**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.
376376
-**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.
377377
-**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.
378+
- **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.
378379
-**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].
379380
-**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.
380381
-**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.
@@ -390,7 +391,7 @@ Work that lands on a downstream visit rather than as a hub pull request, so it i
390391

391392
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.
392393

393-
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.
394+
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.
394395

395396
-**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.
396397
-**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.
@@ -460,18 +461,12 @@ Regenerate [reports/divergences.md][divergences-report] before using it as the w
460461
Actions on issues that are the maintainer's to take, each carrying its evidence so it is one action rather than a re-derivation.
461462

462463
-**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.
463-
-**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.
464464

465465
## Verified Complete, Awaiting Close
466466

467467
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.
468468

469-
-**[#578][issue-578], three rules that state the common case and leave the recurring one unstated.** Complete on all three items.
470-
-**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.
471-
-**Checked** - `develop` at `a706ddb` on 2026-08-08, where all three gaps were re-read before the widening was written.
472-
-**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.
473-
-**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.
474-
-**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.
469+
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.
475470

476471
<!-- Issues -->
477472

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

502498
<!-- Pull requests -->
503499

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 0a86bca

Browse files
authored
Promote the Tagline Close, the Ledger Refresh, and the Pre-Commit Doc Gates (#643)
Three commits, all backlog bookkeeping and local tooling. No rule changes meaning, and nothing here alters what CI gates. **#640, the tagline question.** #577 asked that the tagline be a distinct thing from the intro so a README may carry a second paragraph without the cap or the mirrors reaching it. That shipped in `c2ce145` (#632), so the issue is closed against it. Grepping the number before closing found `TODO.md` had also given #577 to a different entry, the README-to-About hop, which the issue's body never covers. That hop now carries #639 of its own. **#641, the divergence ledger.** `reports/divergences.md` is a live pass over each repo's ground-truth branch, so a committed copy is only as current as its last run. Regenerating moved three rows: `AGENTS.md` "Fleet Bootstrap" is now divergent at Blog and HomeAutomation-Config, and `GOVERNANCE.md` "Verification Discipline" and "Workflow YAML Conventions" widened from one repo to four. The reason `TODO.md` gave for regenerating did not survive the run and is corrected. Two spent backlog claims went with it. **#642, the pre-commit hook.** The chore was to reconsider whether the hook should run the doc gates now that they are diff-scoped. Measuring first found this repository had no hook at all, while `GOVERNANCE.md` and `scripts/README.md` both described what one runs. There is now a committed `.husky/pre-commit` running the prose gate diff-scoped and the line-ending check repo-wide, with the `.gitattributes` and `.editorconfig` pins that `GOVERNANCE.md` "Line Endings" makes mandatory for an extensionless hook. The formatting half the fleet convention names is deliberately absent, and that is the finding worth carrying: this repository declares `[tool.ruff]` in `pyproject.toml` and `spec/project-types.json` declares `python.ruff.config`, yet no workflow runs ruff and the tree does not pass it, at 13 of 57 files unformatted and 106 lint errors. A ruff step would have blocked every commit from the moment it landed. The `GOVERNANCE.md` bullet now states the condition rather than a tool list, that a repo adds each half once its tree passes that half, and the measurement is recorded as a `TODO.md` chore. ## Carried content in this promotion `GOVERNANCE.md` "Running the Linters Locally" changed, and that section is carried `verbatim`, so every downstream copy is byte-mismatched until the next fleet visit. It rides the re-vendor entry already tracked under `TODO.md` "Fleet Sweeps" rather than owing a sweep of its own. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents b4b8f2e + 25e01e3 commit 0a86bca

22 files changed

Lines changed: 244 additions & 68 deletions

‎.editorconfig‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ end_of_line = lf
6060
[catalog/snippets/husky/pre-commit]
6161
end_of_line = lf
6262

63+
# This repository's own hook, paired with the `.gitattributes` pin.
64+
# The git pin alone leaves the editor free to write a CRLF shebang, which would break it.
65+
[.husky/pre-commit]
66+
end_of_line = lf
67+
6368
# Linux scripts
6469
[*.sh]
6570
end_of_line = lf

‎.gitattributes‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*.shtexteol=lf
1414
# The husky pre-commit snippet is an extensionless shebang script (like a copied .husky/pre-commit).
1515
catalog/snippets/husky/pre-committexteol=lf
16+
# This repository's own hook, which is the extensionless case the comment above names.
17+
.husky/pre-committexteol=lf
1618

1719
# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows.
1820
# Pin LF only for a `.py` executed directly via its shebang, by path.

‎.husky/pre-commit‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/sh
2+
# Local pre-commit gate for this repository: the doc checks CI runs, each at the scope that fits it.
3+
# Enable it per clone with `git config core.hooksPath .husky`.
4+
# A clone carries no hooks path, so this file does nothing until that is set.
5+
# It deliberately does not source `_/husky.sh`.
6+
# That file is gitignored and arrives with an npm install this repository does not have.
7+
# Sourcing it would therefore break the hook in a fresh clone.
8+
# The path is kept for the fleet convention the line-ending pins are written against.
9+
#
10+
# The language-formatting half the fleet convention names is absent here, and measured rather than assumed.
11+
# This repository declares ruff in `pyproject.toml`, no workflow runs it, and the tree does not pass it.
12+
# `ruff format --check` reports 13 of 57 files would be reformatted and `ruff check` reports 106 errors.
13+
# A gate failing on the corpus it guards blocks every commit from the moment it lands.
14+
# Converging the Python comes first, and the step is added here after that rather than before it.
15+
#
16+
# `repo_gate.py --check sha-pin` is absent for a different reason.
17+
# It resolves same-owner pins against the GitHub API, and a hook needing a network fails offline.
18+
# The doc linters that need Docker stay in CI and in the VS Code Lint tasks.
19+
set -e
20+
21+
# Git already runs a hook from the top level, measured by committing from `scripts/` and printing `pwd`.
22+
# This is belt and braces for an invocation that does not come from git.
23+
# The relative paths below would otherwise resolve against whatever directory the caller was in.
24+
cd"$(git rev-parse --show-toplevel)"
25+
26+
# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
27+
# On native Windows the python.org install registers `py` and not `python3`.
28+
# That name resolves to a Microsoft Store alias stub, and Git Bash inherits the Windows PATH.
29+
# The stub is on PATH and fails when run, so a presence test selects it and the hook then breaks.
30+
# Running the probe is the whole point: it is what tells a working interpreter from a name.
31+
if python3 --version >/dev/null 2>&1;then
32+
run_py() { python3 "$@"; }
33+
elif py -3 --version >/dev/null 2>&1;then
34+
run_py() { py -3 "$@"; }
35+
else
36+
echo"pre-commit: neither 'python3 --version' nor 'py -3 --version' ran, so the doc gates did not run.">&2
37+
echo"pre-commit: see docs/host-setup.md 'What a Host Must Provide'.">&2
38+
exit 1
39+
fi
40+
41+
# The prose gate is scoped to what changed against HEAD, which is the policy for prose.
42+
# A rule is applied as a file is next edited rather than swept across the tree.
43+
# Whole-tree costs about 2.2 seconds where the diff-scoped run costs about 0.13.
44+
# The scope is the working tree rather than the index.
45+
# A partially staged file is therefore judged on all of its edits, not only the staged ones.
46+
# CI re-runs the same rules over the whole tree, which is what makes that affordable here.
47+
run_py scripts/prose_lint.py . --diff HEAD
48+
49+
# The eol check is repo-wide rather than diff-scoped, and it is here because it is already fast.
50+
# It reads `.gitattributes` against `.editorconfig` for the whole repository and takes no file list.
51+
# At about 0.04 seconds there is nothing to scope, so scoping it would only make it wrong.
52+
run_py scripts/repo_gate.py --check eol

‎GOVERNANCE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ CI runs the full lint set, but run the linters locally before pushing to catch i
368368
**Each surface runs the lint with the tool that fits it, all from the same config files** (`.markdownlint-cli2.jsonc`, `cspell.json`, `.editorconfig`):
369369

370370
-**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.
371-
-**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.
371+
- **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.
372372
-**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.
373373

374374
The Docker invocations below are the same ones the VS Code tasks use, for ad-hoc or headless (agent) runs.

‎TODO.md‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ One pull request moving the canonical short description into declared data, so e
5656

5757
-**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.
5858
-**Blocked by** - The entry above, since the field is what `repo-config/configure.sh` would set the panel from.
59-
-**Issue** - [#577][issue-577], whose tagline half shipped on 2026-08-08.
59+
-**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.
6060
-**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.
6161
-**Open** - Nothing beyond sequencing.
6262
-**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.
@@ -124,7 +124,7 @@ One pull request measuring the remaining carried surface against the carry-versu
124124

125125
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.
126126

127-
**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.
127+
**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.
128128

129129
-**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.
130130
-**Blocked by** - Nothing, and no repo is edited by the hub. Each lands on its own next visit.
@@ -375,6 +375,7 @@ Small work with no research to preserve, selectable one bullet at a time.
375375
-**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.
376376
-**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.
377377
-**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.
378+
- **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.
378379
-**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].
379380
-**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.
380381
-**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.
@@ -390,7 +391,7 @@ Work that lands on a downstream visit rather than as a hub pull request, so it i
390391

391392
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.
392393

393-
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.
394+
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.
394395

395396
-**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.
396397
-**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.
@@ -460,18 +461,12 @@ Regenerate [reports/divergences.md][divergences-report] before using it as the w
460461
Actions on issues that are the maintainer's to take, each carrying its evidence so it is one action rather than a re-derivation.
461462

462463
-**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.
463-
-**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.
464464

465465
## Verified Complete, Awaiting Close
466466

467467
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.
468468

469-
-**[#578][issue-578], three rules that state the common case and leave the recurring one unstated.** Complete on all three items.
470-
-**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.
471-
-**Checked** - `develop` at `a706ddb` on 2026-08-08, where all three gaps were re-read before the widening was written.
472-
-**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.
473-
-**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.
474-
-**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.
469+
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.
475470

476471
<!-- Issues -->
477472

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

502498
<!-- Pull requests -->
503499

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 0a86bca

Browse files
authored
Promote the Tagline Close, the Ledger Refresh, and the Pre-Commit Doc Gates (#643)
Three commits, all backlog bookkeeping and local tooling. No rule changes meaning, and nothing here alters what CI gates. **#640, the tagline question.** #577 asked that the tagline be a distinct thing from the intro so a README may carry a second paragraph without the cap or the mirrors reaching it. That shipped in `c2ce145` (#632), so the issue is closed against it. Grepping the number before closing found `TODO.md` had also given #577 to a different entry, the README-to-About hop, which the issue's body never covers. That hop now carries #639 of its own. **#641, the divergence ledger.** `reports/divergences.md` is a live pass over each repo's ground-truth branch, so a committed copy is only as current as its last run. Regenerating moved three rows: `AGENTS.md` "Fleet Bootstrap" is now divergent at Blog and HomeAutomation-Config, and `GOVERNANCE.md` "Verification Discipline" and "Workflow YAML Conventions" widened from one repo to four. The reason `TODO.md` gave for regenerating did not survive the run and is corrected. Two spent backlog claims went with it. **#642, the pre-commit hook.** The chore was to reconsider whether the hook should run the doc gates now that they are diff-scoped. Measuring first found this repository had no hook at all, while `GOVERNANCE.md` and `scripts/README.md` both described what one runs. There is now a committed `.husky/pre-commit` running the prose gate diff-scoped and the line-ending check repo-wide, with the `.gitattributes` and `.editorconfig` pins that `GOVERNANCE.md` "Line Endings" makes mandatory for an extensionless hook. The formatting half the fleet convention names is deliberately absent, and that is the finding worth carrying: this repository declares `[tool.ruff]` in `pyproject.toml` and `spec/project-types.json` declares `python.ruff.config`, yet no workflow runs ruff and the tree does not pass it, at 13 of 57 files unformatted and 106 lint errors. A ruff step would have blocked every commit from the moment it landed. The `GOVERNANCE.md` bullet now states the condition rather than a tool list, that a repo adds each half once its tree passes that half, and the measurement is recorded as a `TODO.md` chore. ## Carried content in this promotion `GOVERNANCE.md` "Running the Linters Locally" changed, and that section is carried `verbatim`, so every downstream copy is byte-mismatched until the next fleet visit. It rides the re-vendor entry already tracked under `TODO.md` "Fleet Sweeps" rather than owing a sweep of its own. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents b4b8f2e + 25e01e3 commit 0a86bca

22 files changed

Lines changed: 244 additions & 68 deletions

‎.editorconfig‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ end_of_line = lf
6060
[catalog/snippets/husky/pre-commit]
6161
end_of_line = lf
6262

63+
# This repository's own hook, paired with the `.gitattributes` pin.
64+
# The git pin alone leaves the editor free to write a CRLF shebang, which would break it.
65+
[.husky/pre-commit]
66+
end_of_line = lf
67+
6368
# Linux scripts
6469
[*.sh]
6570
end_of_line = lf

‎.gitattributes‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*.shtexteol=lf
1414
# The husky pre-commit snippet is an extensionless shebang script (like a copied .husky/pre-commit).
1515
catalog/snippets/husky/pre-committexteol=lf
16+
# This repository's own hook, which is the extensionless case the comment above names.
17+
.husky/pre-committexteol=lf
1618

1719
# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows.
1820
# Pin LF only for a `.py` executed directly via its shebang, by path.

‎.husky/pre-commit‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/sh
2+
# Local pre-commit gate for this repository: the doc checks CI runs, each at the scope that fits it.
3+
# Enable it per clone with `git config core.hooksPath .husky`.
4+
# A clone carries no hooks path, so this file does nothing until that is set.
5+
# It deliberately does not source `_/husky.sh`.
6+
# That file is gitignored and arrives with an npm install this repository does not have.
7+
# Sourcing it would therefore break the hook in a fresh clone.
8+
# The path is kept for the fleet convention the line-ending pins are written against.
9+
#
10+
# The language-formatting half the fleet convention names is absent here, and measured rather than assumed.
11+
# This repository declares ruff in `pyproject.toml`, no workflow runs it, and the tree does not pass it.
12+
# `ruff format --check` reports 13 of 57 files would be reformatted and `ruff check` reports 106 errors.
13+
# A gate failing on the corpus it guards blocks every commit from the moment it lands.
14+
# Converging the Python comes first, and the step is added here after that rather than before it.
15+
#
16+
# `repo_gate.py --check sha-pin` is absent for a different reason.
17+
# It resolves same-owner pins against the GitHub API, and a hook needing a network fails offline.
18+
# The doc linters that need Docker stay in CI and in the VS Code Lint tasks.
19+
set -e
20+
21+
# Git already runs a hook from the top level, measured by committing from `scripts/` and printing `pwd`.
22+
# This is belt and braces for an invocation that does not come from git.
23+
# The relative paths below would otherwise resolve against whatever directory the caller was in.
24+
cd"$(git rev-parse --show-toplevel)"
25+
26+
# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
27+
# On native Windows the python.org install registers `py` and not `python3`.
28+
# That name resolves to a Microsoft Store alias stub, and Git Bash inherits the Windows PATH.
29+
# The stub is on PATH and fails when run, so a presence test selects it and the hook then breaks.
30+
# Running the probe is the whole point: it is what tells a working interpreter from a name.
31+
if python3 --version >/dev/null 2>&1;then
32+
run_py() { python3 "$@"; }
33+
elif py -3 --version >/dev/null 2>&1;then
34+
run_py() { py -3 "$@"; }
35+
else
36+
echo"pre-commit: neither 'python3 --version' nor 'py -3 --version' ran, so the doc gates did not run.">&2
37+
echo"pre-commit: see docs/host-setup.md 'What a Host Must Provide'.">&2
38+
exit 1
39+
fi
40+
41+
# The prose gate is scoped to what changed against HEAD, which is the policy for prose.
42+
# A rule is applied as a file is next edited rather than swept across the tree.
43+
# Whole-tree costs about 2.2 seconds where the diff-scoped run costs about 0.13.
44+
# The scope is the working tree rather than the index.
45+
# A partially staged file is therefore judged on all of its edits, not only the staged ones.
46+
# CI re-runs the same rules over the whole tree, which is what makes that affordable here.
47+
run_py scripts/prose_lint.py . --diff HEAD
48+
49+
# The eol check is repo-wide rather than diff-scoped, and it is here because it is already fast.
50+
# It reads `.gitattributes` against `.editorconfig` for the whole repository and takes no file list.
51+
# At about 0.04 seconds there is nothing to scope, so scoping it would only make it wrong.
52+
run_py scripts/repo_gate.py --check eol

‎GOVERNANCE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ CI runs the full lint set, but run the linters locally before pushing to catch i
368368
**Each surface runs the lint with the tool that fits it, all from the same config files** (`.markdownlint-cli2.jsonc`, `cspell.json`, `.editorconfig`):
369369

370370
-**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.
371-
-**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.
371+
- **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.
372372
-**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.
373373

374374
The Docker invocations below are the same ones the VS Code tasks use, for ad-hoc or headless (agent) runs.

‎TODO.md‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ One pull request moving the canonical short description into declared data, so e
5656

5757
-**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.
5858
-**Blocked by** - The entry above, since the field is what `repo-config/configure.sh` would set the panel from.
59-
-**Issue** - [#577][issue-577], whose tagline half shipped on 2026-08-08.
59+
-**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.
6060
-**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.
6161
-**Open** - Nothing beyond sequencing.
6262
-**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.
@@ -124,7 +124,7 @@ One pull request measuring the remaining carried surface against the carry-versu
124124

125125
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.
126126

127-
**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.
127+
**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.
128128

129129
-**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.
130130
-**Blocked by** - Nothing, and no repo is edited by the hub. Each lands on its own next visit.
@@ -375,6 +375,7 @@ Small work with no research to preserve, selectable one bullet at a time.
375375
-**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.
376376
-**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.
377377
-**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.
378+
- **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.
378379
-**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].
379380
-**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.
380381
-**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.
@@ -390,7 +391,7 @@ Work that lands on a downstream visit rather than as a hub pull request, so it i
390391

391392
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.
392393

393-
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.
394+
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.
394395

395396
-**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.
396397
-**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.
@@ -460,18 +461,12 @@ Regenerate [reports/divergences.md][divergences-report] before using it as the w
460461
Actions on issues that are the maintainer's to take, each carrying its evidence so it is one action rather than a re-derivation.
461462

462463
-**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.
463-
-**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.
464464

465465
## Verified Complete, Awaiting Close
466466

467467
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.
468468

469-
-**[#578][issue-578], three rules that state the common case and leave the recurring one unstated.** Complete on all three items.
470-
-**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.
471-
-**Checked** - `develop` at `a706ddb` on 2026-08-08, where all three gaps were re-read before the widening was written.
472-
-**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.
473-
-**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.
474-
-**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.
469+
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.
475470

476471
<!-- Issues -->
477472

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

502498
<!-- Pull requests -->
503499

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 0a86bca

Browse files
authored
Promote the Tagline Close, the Ledger Refresh, and the Pre-Commit Doc Gates (#643)
Three commits, all backlog bookkeeping and local tooling. No rule changes meaning, and nothing here alters what CI gates. **#640, the tagline question.** #577 asked that the tagline be a distinct thing from the intro so a README may carry a second paragraph without the cap or the mirrors reaching it. That shipped in `c2ce145` (#632), so the issue is closed against it. Grepping the number before closing found `TODO.md` had also given #577 to a different entry, the README-to-About hop, which the issue's body never covers. That hop now carries #639 of its own. **#641, the divergence ledger.** `reports/divergences.md` is a live pass over each repo's ground-truth branch, so a committed copy is only as current as its last run. Regenerating moved three rows: `AGENTS.md` "Fleet Bootstrap" is now divergent at Blog and HomeAutomation-Config, and `GOVERNANCE.md` "Verification Discipline" and "Workflow YAML Conventions" widened from one repo to four. The reason `TODO.md` gave for regenerating did not survive the run and is corrected. Two spent backlog claims went with it. **#642, the pre-commit hook.** The chore was to reconsider whether the hook should run the doc gates now that they are diff-scoped. Measuring first found this repository had no hook at all, while `GOVERNANCE.md` and `scripts/README.md` both described what one runs. There is now a committed `.husky/pre-commit` running the prose gate diff-scoped and the line-ending check repo-wide, with the `.gitattributes` and `.editorconfig` pins that `GOVERNANCE.md` "Line Endings" makes mandatory for an extensionless hook. The formatting half the fleet convention names is deliberately absent, and that is the finding worth carrying: this repository declares `[tool.ruff]` in `pyproject.toml` and `spec/project-types.json` declares `python.ruff.config`, yet no workflow runs ruff and the tree does not pass it, at 13 of 57 files unformatted and 106 lint errors. A ruff step would have blocked every commit from the moment it landed. The `GOVERNANCE.md` bullet now states the condition rather than a tool list, that a repo adds each half once its tree passes that half, and the measurement is recorded as a `TODO.md` chore. ## Carried content in this promotion `GOVERNANCE.md` "Running the Linters Locally" changed, and that section is carried `verbatim`, so every downstream copy is byte-mismatched until the next fleet visit. It rides the re-vendor entry already tracked under `TODO.md` "Fleet Sweeps" rather than owing a sweep of its own. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents b4b8f2e + 25e01e3 commit 0a86bca

22 files changed

Lines changed: 244 additions & 68 deletions

‎.editorconfig‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ end_of_line = lf
6060
[catalog/snippets/husky/pre-commit]
6161
end_of_line = lf
6262

63+
# This repository's own hook, paired with the `.gitattributes` pin.
64+
# The git pin alone leaves the editor free to write a CRLF shebang, which would break it.
65+
[.husky/pre-commit]
66+
end_of_line = lf
67+
6368
# Linux scripts
6469
[*.sh]
6570
end_of_line = lf

‎.gitattributes‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*.shtexteol=lf
1414
# The husky pre-commit snippet is an extensionless shebang script (like a copied .husky/pre-commit).
1515
catalog/snippets/husky/pre-committexteol=lf
16+
# This repository's own hook, which is the extensionless case the comment above names.
17+
.husky/pre-committexteol=lf
1618

1719
# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows.
1820
# Pin LF only for a `.py` executed directly via its shebang, by path.

‎.husky/pre-commit‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/sh
2+
# Local pre-commit gate for this repository: the doc checks CI runs, each at the scope that fits it.
3+
# Enable it per clone with `git config core.hooksPath .husky`.
4+
# A clone carries no hooks path, so this file does nothing until that is set.
5+
# It deliberately does not source `_/husky.sh`.
6+
# That file is gitignored and arrives with an npm install this repository does not have.
7+
# Sourcing it would therefore break the hook in a fresh clone.
8+
# The path is kept for the fleet convention the line-ending pins are written against.
9+
#
10+
# The language-formatting half the fleet convention names is absent here, and measured rather than assumed.
11+
# This repository declares ruff in `pyproject.toml`, no workflow runs it, and the tree does not pass it.
12+
# `ruff format --check` reports 13 of 57 files would be reformatted and `ruff check` reports 106 errors.
13+
# A gate failing on the corpus it guards blocks every commit from the moment it lands.
14+
# Converging the Python comes first, and the step is added here after that rather than before it.
15+
#
16+
# `repo_gate.py --check sha-pin` is absent for a different reason.
17+
# It resolves same-owner pins against the GitHub API, and a hook needing a network fails offline.
18+
# The doc linters that need Docker stay in CI and in the VS Code Lint tasks.
19+
set -e
20+
21+
# Git already runs a hook from the top level, measured by committing from `scripts/` and printing `pwd`.
22+
# This is belt and braces for an invocation that does not come from git.
23+
# The relative paths below would otherwise resolve against whatever directory the caller was in.
24+
cd"$(git rev-parse --show-toplevel)"
25+
26+
# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
27+
# On native Windows the python.org install registers `py` and not `python3`.
28+
# That name resolves to a Microsoft Store alias stub, and Git Bash inherits the Windows PATH.
29+
# The stub is on PATH and fails when run, so a presence test selects it and the hook then breaks.
30+
# Running the probe is the whole point: it is what tells a working interpreter from a name.
31+
if python3 --version >/dev/null 2>&1;then
32+
run_py() { python3 "$@"; }
33+
elif py -3 --version >/dev/null 2>&1;then
34+
run_py() { py -3 "$@"; }
35+
else
36+
echo"pre-commit: neither 'python3 --version' nor 'py -3 --version' ran, so the doc gates did not run.">&2
37+
echo"pre-commit: see docs/host-setup.md 'What a Host Must Provide'.">&2
38+
exit 1
39+
fi
40+
41+
# The prose gate is scoped to what changed against HEAD, which is the policy for prose.
42+
# A rule is applied as a file is next edited rather than swept across the tree.
43+
# Whole-tree costs about 2.2 seconds where the diff-scoped run costs about 0.13.
44+
# The scope is the working tree rather than the index.
45+
# A partially staged file is therefore judged on all of its edits, not only the staged ones.
46+
# CI re-runs the same rules over the whole tree, which is what makes that affordable here.
47+
run_py scripts/prose_lint.py . --diff HEAD
48+
49+
# The eol check is repo-wide rather than diff-scoped, and it is here because it is already fast.
50+
# It reads `.gitattributes` against `.editorconfig` for the whole repository and takes no file list.
51+
# At about 0.04 seconds there is nothing to scope, so scoping it would only make it wrong.
52+
run_py scripts/repo_gate.py --check eol

‎GOVERNANCE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ CI runs the full lint set, but run the linters locally before pushing to catch i
368368
**Each surface runs the lint with the tool that fits it, all from the same config files** (`.markdownlint-cli2.jsonc`, `cspell.json`, `.editorconfig`):
369369

370370
-**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.
371-
-**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.
371+
- **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.
372372
-**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.
373373

374374
The Docker invocations below are the same ones the VS Code tasks use, for ad-hoc or headless (agent) runs.

‎TODO.md‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ One pull request moving the canonical short description into declared data, so e
5656

5757
-**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.
5858
-**Blocked by** - The entry above, since the field is what `repo-config/configure.sh` would set the panel from.
59-
-**Issue** - [#577][issue-577], whose tagline half shipped on 2026-08-08.
59+
-**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.
6060
-**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.
6161
-**Open** - Nothing beyond sequencing.
6262
-**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.
@@ -124,7 +124,7 @@ One pull request measuring the remaining carried surface against the carry-versu
124124

125125
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.
126126

127-
**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.
127+
**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.
128128

129129
-**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.
130130
-**Blocked by** - Nothing, and no repo is edited by the hub. Each lands on its own next visit.
@@ -375,6 +375,7 @@ Small work with no research to preserve, selectable one bullet at a time.
375375
-**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.
376376
-**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.
377377
-**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.
378+
- **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.
378379
-**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].
379380
-**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.
380381
-**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.
@@ -390,7 +391,7 @@ Work that lands on a downstream visit rather than as a hub pull request, so it i
390391

391392
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.
392393

393-
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.
394+
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.
394395

395396
-**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.
396397
-**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.
@@ -460,18 +461,12 @@ Regenerate [reports/divergences.md][divergences-report] before using it as the w
460461
Actions on issues that are the maintainer's to take, each carrying its evidence so it is one action rather than a re-derivation.
461462

462463
-**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.
463-
-**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.
464464

465465
## Verified Complete, Awaiting Close
466466

467467
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.
468468

469-
-**[#578][issue-578], three rules that state the common case and leave the recurring one unstated.** Complete on all three items.
470-
-**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.
471-
-**Checked** - `develop` at `a706ddb` on 2026-08-08, where all three gaps were re-read before the widening was written.
472-
-**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.
473-
-**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.
474-
-**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.
469+
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.
475470

476471
<!-- Issues -->
477472

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

502498
<!-- Pull requests -->
503499

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 0a86bca

Browse files
authored
Promote the Tagline Close, the Ledger Refresh, and the Pre-Commit Doc Gates (#643)
Three commits, all backlog bookkeeping and local tooling. No rule changes meaning, and nothing here alters what CI gates. **#640, the tagline question.** #577 asked that the tagline be a distinct thing from the intro so a README may carry a second paragraph without the cap or the mirrors reaching it. That shipped in `c2ce145` (#632), so the issue is closed against it. Grepping the number before closing found `TODO.md` had also given #577 to a different entry, the README-to-About hop, which the issue's body never covers. That hop now carries #639 of its own. **#641, the divergence ledger.** `reports/divergences.md` is a live pass over each repo's ground-truth branch, so a committed copy is only as current as its last run. Regenerating moved three rows: `AGENTS.md` "Fleet Bootstrap" is now divergent at Blog and HomeAutomation-Config, and `GOVERNANCE.md` "Verification Discipline" and "Workflow YAML Conventions" widened from one repo to four. The reason `TODO.md` gave for regenerating did not survive the run and is corrected. Two spent backlog claims went with it. **#642, the pre-commit hook.** The chore was to reconsider whether the hook should run the doc gates now that they are diff-scoped. Measuring first found this repository had no hook at all, while `GOVERNANCE.md` and `scripts/README.md` both described what one runs. There is now a committed `.husky/pre-commit` running the prose gate diff-scoped and the line-ending check repo-wide, with the `.gitattributes` and `.editorconfig` pins that `GOVERNANCE.md` "Line Endings" makes mandatory for an extensionless hook. The formatting half the fleet convention names is deliberately absent, and that is the finding worth carrying: this repository declares `[tool.ruff]` in `pyproject.toml` and `spec/project-types.json` declares `python.ruff.config`, yet no workflow runs ruff and the tree does not pass it, at 13 of 57 files unformatted and 106 lint errors. A ruff step would have blocked every commit from the moment it landed. The `GOVERNANCE.md` bullet now states the condition rather than a tool list, that a repo adds each half once its tree passes that half, and the measurement is recorded as a `TODO.md` chore. ## Carried content in this promotion `GOVERNANCE.md` "Running the Linters Locally" changed, and that section is carried `verbatim`, so every downstream copy is byte-mismatched until the next fleet visit. It rides the re-vendor entry already tracked under `TODO.md` "Fleet Sweeps" rather than owing a sweep of its own. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents b4b8f2e + 25e01e3 commit 0a86bca

22 files changed

Lines changed: 244 additions & 68 deletions

‎.editorconfig‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ end_of_line = lf
6060
[catalog/snippets/husky/pre-commit]
6161
end_of_line = lf
6262

63+
# This repository's own hook, paired with the `.gitattributes` pin.
64+
# The git pin alone leaves the editor free to write a CRLF shebang, which would break it.
65+
[.husky/pre-commit]
66+
end_of_line = lf
67+
6368
# Linux scripts
6469
[*.sh]
6570
end_of_line = lf

‎.gitattributes‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*.shtexteol=lf
1414
# The husky pre-commit snippet is an extensionless shebang script (like a copied .husky/pre-commit).
1515
catalog/snippets/husky/pre-committexteol=lf
16+
# This repository's own hook, which is the extensionless case the comment above names.
17+
.husky/pre-committexteol=lf
1618

1719
# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows.
1820
# Pin LF only for a `.py` executed directly via its shebang, by path.

‎.husky/pre-commit‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/sh
2+
# Local pre-commit gate for this repository: the doc checks CI runs, each at the scope that fits it.
3+
# Enable it per clone with `git config core.hooksPath .husky`.
4+
# A clone carries no hooks path, so this file does nothing until that is set.
5+
# It deliberately does not source `_/husky.sh`.
6+
# That file is gitignored and arrives with an npm install this repository does not have.
7+
# Sourcing it would therefore break the hook in a fresh clone.
8+
# The path is kept for the fleet convention the line-ending pins are written against.
9+
#
10+
# The language-formatting half the fleet convention names is absent here, and measured rather than assumed.
11+
# This repository declares ruff in `pyproject.toml`, no workflow runs it, and the tree does not pass it.
12+
# `ruff format --check` reports 13 of 57 files would be reformatted and `ruff check` reports 106 errors.
13+
# A gate failing on the corpus it guards blocks every commit from the moment it lands.
14+
# Converging the Python comes first, and the step is added here after that rather than before it.
15+
#
16+
# `repo_gate.py --check sha-pin` is absent for a different reason.
17+
# It resolves same-owner pins against the GitHub API, and a hook needing a network fails offline.
18+
# The doc linters that need Docker stay in CI and in the VS Code Lint tasks.
19+
set -e
20+
21+
# Git already runs a hook from the top level, measured by committing from `scripts/` and printing `pwd`.
22+
# This is belt and braces for an invocation that does not come from git.
23+
# The relative paths below would otherwise resolve against whatever directory the caller was in.
24+
cd"$(git rev-parse --show-toplevel)"
25+
26+
# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
27+
# On native Windows the python.org install registers `py` and not `python3`.
28+
# That name resolves to a Microsoft Store alias stub, and Git Bash inherits the Windows PATH.
29+
# The stub is on PATH and fails when run, so a presence test selects it and the hook then breaks.
30+
# Running the probe is the whole point: it is what tells a working interpreter from a name.
31+
if python3 --version >/dev/null 2>&1;then
32+
run_py() { python3 "$@"; }
33+
elif py -3 --version >/dev/null 2>&1;then
34+
run_py() { py -3 "$@"; }
35+
else
36+
echo"pre-commit: neither 'python3 --version' nor 'py -3 --version' ran, so the doc gates did not run.">&2
37+
echo"pre-commit: see docs/host-setup.md 'What a Host Must Provide'.">&2
38+
exit 1
39+
fi
40+
41+
# The prose gate is scoped to what changed against HEAD, which is the policy for prose.
42+
# A rule is applied as a file is next edited rather than swept across the tree.
43+
# Whole-tree costs about 2.2 seconds where the diff-scoped run costs about 0.13.
44+
# The scope is the working tree rather than the index.
45+
# A partially staged file is therefore judged on all of its edits, not only the staged ones.
46+
# CI re-runs the same rules over the whole tree, which is what makes that affordable here.
47+
run_py scripts/prose_lint.py . --diff HEAD
48+
49+
# The eol check is repo-wide rather than diff-scoped, and it is here because it is already fast.
50+
# It reads `.gitattributes` against `.editorconfig` for the whole repository and takes no file list.
51+
# At about 0.04 seconds there is nothing to scope, so scoping it would only make it wrong.
52+
run_py scripts/repo_gate.py --check eol

‎GOVERNANCE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ CI runs the full lint set, but run the linters locally before pushing to catch i
368368
**Each surface runs the lint with the tool that fits it, all from the same config files** (`.markdownlint-cli2.jsonc`, `cspell.json`, `.editorconfig`):
369369

370370
-**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.
371-
-**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.
371+
- **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.
372372
-**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.
373373

374374
The Docker invocations below are the same ones the VS Code tasks use, for ad-hoc or headless (agent) runs.

‎TODO.md‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ One pull request moving the canonical short description into declared data, so e
5656

5757
-**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.
5858
-**Blocked by** - The entry above, since the field is what `repo-config/configure.sh` would set the panel from.
59-
-**Issue** - [#577][issue-577], whose tagline half shipped on 2026-08-08.
59+
-**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.
6060
-**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.
6161
-**Open** - Nothing beyond sequencing.
6262
-**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.
@@ -124,7 +124,7 @@ One pull request measuring the remaining carried surface against the carry-versu
124124

125125
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.
126126

127-
**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.
127+
**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.
128128

129129
-**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.
130130
-**Blocked by** - Nothing, and no repo is edited by the hub. Each lands on its own next visit.
@@ -375,6 +375,7 @@ Small work with no research to preserve, selectable one bullet at a time.
375375
-**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.
376376
-**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.
377377
-**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.
378+
- **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.
378379
-**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].
379380
-**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.
380381
-**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.
@@ -390,7 +391,7 @@ Work that lands on a downstream visit rather than as a hub pull request, so it i
390391

391392
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.
392393

393-
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.
394+
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.
394395

395396
-**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.
396397
-**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.
@@ -460,18 +461,12 @@ Regenerate [reports/divergences.md][divergences-report] before using it as the w
460461
Actions on issues that are the maintainer's to take, each carrying its evidence so it is one action rather than a re-derivation.
461462

462463
-**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.
463-
-**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.
464464

465465
## Verified Complete, Awaiting Close
466466

467467
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.
468468

469-
-**[#578][issue-578], three rules that state the common case and leave the recurring one unstated.** Complete on all three items.
470-
-**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.
471-
-**Checked** - `develop` at `a706ddb` on 2026-08-08, where all three gaps were re-read before the widening was written.
472-
-**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.
473-
-**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.
474-
-**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.
469+
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.
475470

476471
<!-- Issues -->
477472

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

502498
<!-- Pull requests -->
503499

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 0a86bca

Browse files
authored
Promote the Tagline Close, the Ledger Refresh, and the Pre-Commit Doc Gates (#643)
Three commits, all backlog bookkeeping and local tooling. No rule changes meaning, and nothing here alters what CI gates. **#640, the tagline question.** #577 asked that the tagline be a distinct thing from the intro so a README may carry a second paragraph without the cap or the mirrors reaching it. That shipped in `c2ce145` (#632), so the issue is closed against it. Grepping the number before closing found `TODO.md` had also given #577 to a different entry, the README-to-About hop, which the issue's body never covers. That hop now carries #639 of its own. **#641, the divergence ledger.** `reports/divergences.md` is a live pass over each repo's ground-truth branch, so a committed copy is only as current as its last run. Regenerating moved three rows: `AGENTS.md` "Fleet Bootstrap" is now divergent at Blog and HomeAutomation-Config, and `GOVERNANCE.md` "Verification Discipline" and "Workflow YAML Conventions" widened from one repo to four. The reason `TODO.md` gave for regenerating did not survive the run and is corrected. Two spent backlog claims went with it. **#642, the pre-commit hook.** The chore was to reconsider whether the hook should run the doc gates now that they are diff-scoped. Measuring first found this repository had no hook at all, while `GOVERNANCE.md` and `scripts/README.md` both described what one runs. There is now a committed `.husky/pre-commit` running the prose gate diff-scoped and the line-ending check repo-wide, with the `.gitattributes` and `.editorconfig` pins that `GOVERNANCE.md` "Line Endings" makes mandatory for an extensionless hook. The formatting half the fleet convention names is deliberately absent, and that is the finding worth carrying: this repository declares `[tool.ruff]` in `pyproject.toml` and `spec/project-types.json` declares `python.ruff.config`, yet no workflow runs ruff and the tree does not pass it, at 13 of 57 files unformatted and 106 lint errors. A ruff step would have blocked every commit from the moment it landed. The `GOVERNANCE.md` bullet now states the condition rather than a tool list, that a repo adds each half once its tree passes that half, and the measurement is recorded as a `TODO.md` chore. ## Carried content in this promotion `GOVERNANCE.md` "Running the Linters Locally" changed, and that section is carried `verbatim`, so every downstream copy is byte-mismatched until the next fleet visit. It rides the re-vendor entry already tracked under `TODO.md` "Fleet Sweeps" rather than owing a sweep of its own. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents b4b8f2e + 25e01e3 commit 0a86bca

22 files changed

Lines changed: 244 additions & 68 deletions

‎.editorconfig‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ end_of_line = lf
6060
[catalog/snippets/husky/pre-commit]
6161
end_of_line = lf
6262

63+
# This repository's own hook, paired with the `.gitattributes` pin.
64+
# The git pin alone leaves the editor free to write a CRLF shebang, which would break it.
65+
[.husky/pre-commit]
66+
end_of_line = lf
67+
6368
# Linux scripts
6469
[*.sh]
6570
end_of_line = lf

‎.gitattributes‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*.shtexteol=lf
1414
# The husky pre-commit snippet is an extensionless shebang script (like a copied .husky/pre-commit).
1515
catalog/snippets/husky/pre-committexteol=lf
16+
# This repository's own hook, which is the extensionless case the comment above names.
17+
.husky/pre-committexteol=lf
1618

1719
# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows.
1820
# Pin LF only for a `.py` executed directly via its shebang, by path.

‎.husky/pre-commit‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/bin/sh
2+
# Local pre-commit gate for this repository: the doc checks CI runs, each at the scope that fits it.
3+
# Enable it per clone with `git config core.hooksPath .husky`.
4+
# A clone carries no hooks path, so this file does nothing until that is set.
5+
# It deliberately does not source `_/husky.sh`.
6+
# That file is gitignored and arrives with an npm install this repository does not have.
7+
# Sourcing it would therefore break the hook in a fresh clone.
8+
# The path is kept for the fleet convention the line-ending pins are written against.
9+
#
10+
# The language-formatting half the fleet convention names is absent here, and measured rather than assumed.
11+
# This repository declares ruff in `pyproject.toml`, no workflow runs it, and the tree does not pass it.
12+
# `ruff format --check` reports 13 of 57 files would be reformatted and `ruff check` reports 106 errors.
13+
# A gate failing on the corpus it guards blocks every commit from the moment it lands.
14+
# Converging the Python comes first, and the step is added here after that rather than before it.
15+
#
16+
# `repo_gate.py --check sha-pin` is absent for a different reason.
17+
# It resolves same-owner pins against the GitHub API, and a hook needing a network fails offline.
18+
# The doc linters that need Docker stay in CI and in the VS Code Lint tasks.
19+
set -e
20+
21+
# Git already runs a hook from the top level, measured by committing from `scripts/` and printing `pwd`.
22+
# This is belt and braces for an invocation that does not come from git.
23+
# The relative paths below would otherwise resolve against whatever directory the caller was in.
24+
cd"$(git rev-parse --show-toplevel)"
25+
26+
# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
27+
# On native Windows the python.org install registers `py` and not `python3`.
28+
# That name resolves to a Microsoft Store alias stub, and Git Bash inherits the Windows PATH.
29+
# The stub is on PATH and fails when run, so a presence test selects it and the hook then breaks.
30+
# Running the probe is the whole point: it is what tells a working interpreter from a name.
31+
if python3 --version >/dev/null 2>&1;then
32+
run_py() { python3 "$@"; }
33+
elif py -3 --version >/dev/null 2>&1;then
34+
run_py() { py -3 "$@"; }
35+
else
36+
echo"pre-commit: neither 'python3 --version' nor 'py -3 --version' ran, so the doc gates did not run.">&2
37+
echo"pre-commit: see docs/host-setup.md 'What a Host Must Provide'.">&2
38+
exit 1
39+
fi
40+
41+
# The prose gate is scoped to what changed against HEAD, which is the policy for prose.
42+
# A rule is applied as a file is next edited rather than swept across the tree.
43+
# Whole-tree costs about 2.2 seconds where the diff-scoped run costs about 0.13.
44+
# The scope is the working tree rather than the index.
45+
# A partially staged file is therefore judged on all of its edits, not only the staged ones.
46+
# CI re-runs the same rules over the whole tree, which is what makes that affordable here.
47+
run_py scripts/prose_lint.py . --diff HEAD
48+
49+
# The eol check is repo-wide rather than diff-scoped, and it is here because it is already fast.
50+
# It reads `.gitattributes` against `.editorconfig` for the whole repository and takes no file list.
51+
# At about 0.04 seconds there is nothing to scope, so scoping it would only make it wrong.
52+
run_py scripts/repo_gate.py --check eol

‎GOVERNANCE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ CI runs the full lint set, but run the linters locally before pushing to catch i
368368
**Each surface runs the lint with the tool that fits it, all from the same config files** (`.markdownlint-cli2.jsonc`, `cspell.json`, `.editorconfig`):
369369

370370
-**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.
371-
-**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.
371+
- **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.
372372
-**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.
373373

374374
The Docker invocations below are the same ones the VS Code tasks use, for ad-hoc or headless (agent) runs.

‎TODO.md‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ One pull request moving the canonical short description into declared data, so e
5656

5757
-**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.
5858
-**Blocked by** - The entry above, since the field is what `repo-config/configure.sh` would set the panel from.
59-
-**Issue** - [#577][issue-577], whose tagline half shipped on 2026-08-08.
59+
-**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.
6060
-**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.
6161
-**Open** - Nothing beyond sequencing.
6262
-**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.
@@ -124,7 +124,7 @@ One pull request measuring the remaining carried surface against the carry-versu
124124

125125
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.
126126

127-
**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.
127+
**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.
128128

129129
-**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.
130130
-**Blocked by** - Nothing, and no repo is edited by the hub. Each lands on its own next visit.
@@ -375,6 +375,7 @@ Small work with no research to preserve, selectable one bullet at a time.
375375
-**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.
376376
-**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.
377377
-**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.
378+
- **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.
378379
-**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].
379380
-**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.
380381
-**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.
@@ -390,7 +391,7 @@ Work that lands on a downstream visit rather than as a hub pull request, so it i
390391

391392
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.
392393

393-
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.
394+
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.
394395

395396
-**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.
396397
-**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.
@@ -460,18 +461,12 @@ Regenerate [reports/divergences.md][divergences-report] before using it as the w
460461
Actions on issues that are the maintainer's to take, each carrying its evidence so it is one action rather than a re-derivation.
461462

462463
-**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.
463-
-**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.
464464

465465
## Verified Complete, Awaiting Close
466466

467467
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.
468468

469-
-**[#578][issue-578], three rules that state the common case and leave the recurring one unstated.** Complete on all three items.
470-
-**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.
471-
-**Checked** - `develop` at `a706ddb` on 2026-08-08, where all three gaps were re-read before the widening was written.
472-
-**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.
473-
-**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.
474-
-**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.
469+
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.
475470

476471
<!-- Issues -->
477472

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

502498
<!-- Pull requests -->
503499

0 commit comments

Comments
 (0)