Operational repos: line endings follow the consuming app's platform - #288
Merged
Conversation
A config repo is a view into an application's configuration directory (often the tree mounted into that app's container), so its files must use the ending the app itself reads and writes - not the fleet CRLF default. Encode this per repo instead of forcing one rule on all config repos. - AGENTS.md "Line Endings": operational repos set the global [*] end_of_line default to the app's native platform (LF for Linux-native/container config - ESPHome, Home Assistant, devcontainer-only/HACS; CRLF for a Windows-native editor - Vantage/Design Center); execution-sensitive LF pins still apply; do not re-normalize such a repo to the fleet CRLF default. - registry: add a lineEndings field (lf | crlf); set lf on ESPHome-Config, HomeAssistant-Config, HomeAutomation and crlf on Vantage-Config; schema + validate.py enforce the enum. - spec/project-types.json recurring.eol: the global default is CRLF for release repos or the registry lineEndings value for operational repos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces per-repository line-ending policy for operational (config) repos, so their global .editorconfig default can match the consuming application's native platform instead of forcing the fleet CRLF default.
Changes:
- Add a
lineEndings(lf|crlf) field to the fleet registry schema and set it for selected operational repos. - Extend
spec/validate.pyto validatelineEndingsvalues in registry entries. - Update governance/docs assertions (AGENTS.md + project-types recurring checks) to describe the operational-repo line-ending rule.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/validate.py | Adds validation for the new lineEndings registry field. |
| spec/project-types.json | Updates the recurring EOL check text to incorporate operational-repo defaults from the registry. |
| registry/repos.schema.json | Adds the lineEndings field definition to the registry schema. |
| registry/repos.json | Populates lineEndings for specific operational repos (LF for Linux-native configs, CRLF for Windows-native editor configs). |
| AGENTS.md | Documents the operational-repo exception: global EOL default follows the consuming app platform. |
Enforce the intended invariant both ways: registry schema adds a conditional (if workflowModel == operational then lineEndings required), and validate.py fails an operational repo that omits lineEndings. Release repos still omit it and use the fleet CRLF default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines
+127
to
+133
| eol = repo.get("lineEndings") | ||
| if eol is not None and eol not in ("lf", "crlf"): | ||
| errors.append(f"{name}: lineEndings '{eol}' invalid (expected lf or crlf)") | ||
| # An operational repo's endings follow the consuming app's platform, so they must be declared; a release | ||
| # repo omits the field and uses the fleet CRLF default. | ||
| if model == "operational" and eol is None: | ||
| errors.append(f"{name}: operational repo must declare lineEndings (lf or crlf)") |
Comment on lines
+34
to
+40
| "allOf": [ | ||
| { | ||
| "comment": "An operational repo must declare its line endings (release repos use the fleet CRLF default).", | ||
| "if": { "properties": { "workflowModel": { "const": "operational" } }, "required": ["workflowModel"] }, | ||
| "then": { "required": ["lineEndings"] } | ||
| } | ||
| ], |
|
|
||
| - **[`.editorconfig`](./.editorconfig) sets the line ending:** `[*] end_of_line = crlf` is the **default** - every file type is CRLF unless pinned otherwise - with **LF** pinned for the execution-sensitive exceptions - `*.sh`, Dockerfiles, and any individual `.py` executed directly via its shebang (pinned **by path**, e.g. `spec/validate.py`; vanilla `.py` stays CRLF, since Python's universal newlines accept it and it is commonly edited on Windows). Only the LF exceptions are declared; the redundant per-type CRLF rules are intentionally omitted. `.gitattributes` mirrors it: `* -text` (git stores the exact bytes you commit and will **not** normalize) plus the matching LF pins. | ||
| - **Choosing an ending for a new file type:** CRLF is the **default** - cross-platform editors on Windows produce it, and it is harmless on Linux for everything except shell. Use LF only when the type **requires** it or CRLF **breaks how it is consumed**: executable scripts/shebangs (`*.sh`, s6, husky), Dockerfiles (CRLF breaks `RUN` heredocs/continuations), and tool-owned formats with a native LF ending (KiCad). **Non-workflow YAML stays CRLF** - GitHub Actions' parser tolerates it (a repo that also runs yamllint sets `new-lines: disable` to defer to `.editorconfig`). **Workflow YAML (`.github/workflows/*.{yml,yaml}`) is pinned LF** in `.editorconfig` - Dependabot and Actions rewrite it with LF, so declaring LF keeps it consistent instead of mixed on every bump. This (and the catalog snippet workflows in `catalog/snippets/workflows/*`, pinned LF the same way) is an LF class **not** backed by a `.gitattributes` pin: git keeps `* -text` (no normalization), and CI's `editorconfig-checker` (EOL-only) catches a mismatch instead. Distinguish where a file is *consumed* from where it is *edited*: consumption on Linux alone does not force LF. A config or pattern file consumed by a Linux tool stays CRLF when the tool tolerates a trailing CR: `.dockerignore` and `.gitignore` are CRLF (their parsers strip the CR), and only a *Dockerfile* - interpreted, where a CR breaks `RUN` heredocs and line continuations - is LF. | ||
| - **Operational (config) repos: the global default follows the consuming application's native platform, not the fleet CRLF default.** A config repo (registry `workflowModel: operational`) is a *view into an application's configuration directory* - often the exact tree mounted into that app's container - so its files must use the ending the app itself reads and writes, and forcing the fleet CRLF default would fight the app. Set the `[*] end_of_line` default to the app's native ending and record it in the registry [`lineEndings`](./registry/repos.json) field (`lf` | `crlf`): **LF** for a Linux-native app whose config lives in a Linux container - ESPHome, Home Assistant, a devcontainer-only or HACS config - and **CRLF** for a Windows-native editor - e.g. Vantage-Config, edited by Design Center on Windows. The execution-sensitive LF pins (`*.sh`, Dockerfiles, workflow YAML) still apply on top, and `.gitattributes` still mirrors the chosen default. This override is for operational repos only; `release` repos keep the `[*] end_of_line = crlf` fleet default above. Do **not** re-normalize such a repo to the fleet default - that is exactly the over-normalization these per-repo endings prevent. |
ptr727
added a commit
that referenced
this pull request
Jul 13, 2026
…epos (#292) Promote the current `develop` snapshot to `main`. Adds the **operational** workflow model and its supporting rules/tooling (PRs #287-#291): - **#287** operational `workflowModel` (direct-to-`develop` live-config repos; shared `main` ruleset; dispatch-only releases; model-aware `configure.sh`). - **#288** per-repo `lineEndings` field (operational repos follow the consuming app's platform; validated). - **#289** mixed-consumer EOL guidance (prefer splitting by platform); dropped the standalone Vantage-Config catalog entry (later re-added, recreated single-platform). - **#290** renamed `HomeAutomation` -> `HomeAutomation-Config`; EOL guidance leads with the split-preferred approach. - **#291** CODESTYLE.md defers line-ending governance to AGENTS.md (was hardcoding CRLF, contradicting `lf` operational repos). Applied end-to-end to ESPHome-Config, HomeAutomation-Config, HomeAssistant-Config (lf) and the recreated Vantage-Config (crlf). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A config repo is a view into an application's configuration directory (often the exact tree mounted into that app's container), so its files must use the line ending the app itself reads and writes - not the fleet CRLF default. Encode this per repo instead of forcing one rule on all config repos.
[*] end_of_linedefault to the consuming app's native platform - LF for Linux-native/container config (ESPHome, Home Assistant, devcontainer-only/HACS), CRLF for a Windows-native editor (Vantage-Config / Design Center). Execution-sensitive LF pins still apply; do not re-normalize such a repo to the fleet default.lineEndingsfield (lf|crlf); setlfon ESPHome-Config, HomeAssistant-Config, HomeAutomation andcrlfon Vantage-Config; schema +validate.pyenforce the enum.recurring.eol: the global default is CRLF for release repos or the registrylineEndingsvalue for operational repos.Context
Surfaced during the operational-repo rollout: ESPHome-Config uses a global
end_of_line = lf(it is edited as the ESPHome container's/configview). Matching the app's platform is correct; converging it to fleet-CRLF would be over-normalization.Verification
python3 spec/validate.pypasses (rejects an invalidlineEndings); markdownlint + cspell clean on AGENTS.md.🤖 Generated with Claude Code