Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ end_of_line = lf
[catalog/snippets/husky/pre-commit]
end_of_line = lf

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

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

# Vanilla `.py` follows the CRLF default, since Python's universal newlines accept CRLF and it is commonly edited on Windows.
# Pin LF only for a `.py` executed directly via its shebang, by path.
Expand Down
22 changes: 22 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/sh
# Local pre-commit gate for this repository: the doc checks CI runs, scoped to the diff.
# Enable it per clone with `git config core.hooksPath .husky`.
# A clone carries no hooks path, so this file does nothing until that is set.
# It deliberately does not source `_/husky.sh`.
# That file is gitignored and arrives with an npm install this repository does not have.
# Sourcing it would therefore break the hook in a fresh clone.
# The path is kept for the fleet convention the line-ending pins are written against.
# What is absent is a decision rather than an oversight.
# `repo_gate.py --check sha-pin` resolves same-owner pins against the GitHub API.
# It stays in CI, where a network is assumed, since a hook needing one fails offline.
# The doc linters that need Docker stay in CI and in the VS Code Lint tasks.
set -e

# Scoped to what changed against HEAD, which is the policy for prose.
# A rule is applied as a file is next edited rather than swept across the tree.
# Whole-tree costs about 2.2 seconds where this costs about 0.2.
# The scope is the working tree rather than the index.
# A partially staged file is therefore judged on all of its edits, not only the staged ones.
# CI re-runs the same rules over the whole tree, which is what makes that affordable here.
python3 scripts/prose_lint.py . --diff HEAD
python3 scripts/repo_gate.py --check eol
2 changes: 1 addition & 1 deletion GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ CI runs the full lint set, but run the linters locally before pushing to catch i
**Each surface runs the lint with the tool that fits it, all from the same config files** (`.markdownlint-cli2.jsonc`, `cspell.json`, `.editorconfig`):

- **CI (authoritative)** runs **markdownlint-cli2**, **cspell**, and **actionlint** as pinned action wrappers (Dependabot bumps them), plus **editorconfig-checker** via Docker `:latest` (its action only installs the CLI, so the Docker one-liner is what actually runs the check). markdownlint covers all `**/*.md`, and **cspell is scoped to `README.md` + `HISTORY.md`** (see [CODESTYLE.md](./CODESTYLE.md) "Markdown and Spelling" for why), matching the cspell one-liner below.
- **The `.husky/pre-commit` hook** runs **language formatting only**: CSharpier + `dotnet format` (or ruff) via native tooling, no Docker and no doc linters, so it stays fast.
- **The `.husky/pre-commit` hook** runs **language formatting** (CSharpier + `dotnet format`, or ruff, via native tooling) and the **diff-scoped doc gates**, never Docker and never a network call, so it stays fast. The doc half 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.2 seconds and is what makes it affordable in a hook at all. `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.
Comment thread
ptr727 marked this conversation as resolved.
Outdated
- **The VS Code Lint tasks** run the full doc-lint set via Docker `:latest` on demand, the local surface for Markdown, spelling, workflow, and EditorConfig checks.

The Docker invocations below are the same ones the VS Code tasks use, for ad-hoc or headless (agent) runs.
Expand Down
2 changes: 1 addition & 1 deletion scripts/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Repo Scripts

The fleet's checks and review tooling, run by hand, with the deterministic ones also gating CI. Each one exists because the CI linters pass on the failure it catches: `markdownlint`, `cspell`, `actionlint`, and `editorconfig-checker` all report clean on prose that breaks a documented [`GOVERNANCE.md`][governance] rule. Doc linters stay out of the pre-commit hook, which runs language formatting only so it stays fast.
The fleet's checks and review tooling, run by hand, with the deterministic ones also gating CI. Each one exists because the CI linters pass on the failure it catches: `markdownlint`, `cspell`, `actionlint`, and `editorconfig-checker` all report clean on prose that breaks a documented [`GOVERNANCE.md`][governance] rule. The pre-commit hook runs the deterministic doc gates diff-scoped, `prose_lint.py --diff HEAD` and `repo_gate.py --check eol`, which together cost about 0.2 seconds where the whole-tree sweep costs about 2.2. The earlier decision to keep doc linters out was made when a sweep was the only mode, and what reversed it is the diff scope rather than a change of preference. The gates needing Docker, and `sha-pin` which resolves a pin against the GitHub API, stay in CI. The hook reads the working tree rather than the index, so a partially staged file is judged on all of its edits, which CI's whole-tree run is the backstop for.
Comment thread
Copilot marked this conversation as resolved.
Outdated

**Hosted here and reached, never carried.** These are not declared in [`spec/files.json`][files], so the audit does not expect a downstream repo to ship them, the same footing as `spec/audit.py`. That is the fleet model rather than an omission: a script holding no per-repo content is one copy for the fleet, run from a hub checkout against the repository named on the command line, per [GOVERNANCE.md "Hub-Hosted Tooling"][governance-hub-hosted-tooling]. A repository that cannot reach the hub reports the check as not run rather than reconstructing it, since a rebuilt gate encodes its author's reading of the rule and agrees with no other repository. CI reaches the same rules through the [`prose-gate`][prose-gate-action] composite action, which a caller pins to a commit SHA. It reads the copy bundled at that pin only where the run targets `main`, and takes the rules from hub `develop` on every other target, a feature-branch push included, so a released repo's gate is reproducible while every branch below it exercises a rule change before that change reaches `main`. A caller wanting one specific hub ref passes `rules-ref` and overrides both.

Expand Down