diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d727077 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,58 @@ +# Open Elements standard EditorConfig. +# See .claude/skills/project-setup/references/editorconfig.md for the shared baseline. +# Deviations from that baseline are marked below and explained where they occur. + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +# Deviation from the shared baseline, which specifies 4 spaces and a 120 column limit. +# This project enforces Spotless with googleJavaFormat for every child project, and +# Google Java Format writes 2 spaces with a 100 column limit and a 4 space continuation +# indent. Following the baseline here would make the editor fight the formatter: a +# developer would type to one rule and the next `spotless:apply` would rewrite it. +# The ij_java_* rules below are the baseline's and agree with Google Java Format. +[*.java] +indent_size = 2 +ij_continuation_indent_size = 4 +max_line_length = 100 +ij_java_class_count_to_use_import_on_demand = 9999 +ij_java_names_count_to_use_import_on_demand = 9999 +ij_java_use_single_class_imports = true +ij_java_layout_static_imports_separately = true +ij_java_block_brace_style = end_of_line +ij_java_class_brace_style = end_of_line +ij_java_method_brace_style = end_of_line +ij_java_lambda_brace_style = end_of_line +ij_java_if_brace_force = always +ij_java_for_brace_force = always +ij_java_while_brace_force = always +ij_java_do_while_brace_force = always + +[*.{ts,tsx,js,jsx,json,css,scss,html}] +indent_size = 2 + +[*.{yml,yaml}] +indent_size = 2 + +# Trailing spaces are significant in Markdown (they encode a line break). +[*.md] +trim_trailing_whitespace = false + +[*.xml] +indent_size = 4 + +# Matches the eol=crlf pin in .gitattributes. Without this the [*] rule above would +# tell the editor to write LF, and every save would fight the checkout. +[*.{cmd,bat}] +end_of_line = crlf + +[{Dockerfile,Dockerfile.*}] +indent_style = space +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..03833cf --- /dev/null +++ b/.gitattributes @@ -0,0 +1,37 @@ +# Line endings are pinned so a checkout produces the same bytes on every platform. +# +# Git always stores text files with LF in the index; `eol=lf` additionally forces LF +# in the working tree, overriding a local `core.autocrlf` (Windows default: true). +# Without this, the bytes of every source and resource file would depend on the Git +# configuration of whoever checked the repository out — including the bytes of this +# project's own pom.xml, which is deployed to Maven Central verbatim. +* text=auto eol=lf + +# Windows batch files are the single exception: cmd.exe is fragile with LF-only +# scripts (labels, goto, multi-line blocks), so these are checked out as CRLF. +# The index representation stays LF, so this produces no commit diff. +*.bat text eol=crlf +*.cmd text eol=crlf + +# Truly binary files: never normalized, never diffed as text. Git's content +# heuristic already classifies the assets currently in this repository correctly; +# these markers remove the dependency on that heuristic for future additions — +# keystores and archives being the cases where a misdetection would corrupt a file. +# Listing a type here says how Git must treat it if it ever appears — it is not a +# statement that such a file belongs in version control. Keystores generally do not. +*.class binary +*.eot binary +*.gif binary +*.gz binary +*.ico binary +*.jar binary +*.jks binary +*.jpeg binary +*.jpg binary +*.p12 binary +*.pdf binary +*.png binary +*.ttf binary +*.woff binary +*.woff2 binary +*.zip binary diff --git a/README.md b/README.md index 0136431..c03e2e9 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,81 @@ builds are reproducible and free of "you should pin this plugin" warnings: - **Surefire** pre-configured with the `--add-opens` flags commonly needed by reflection-based test/mocking libraries. - **Toolchain enforcement** (see [Requirements](#requirements)). +- **LF line endings** enforced for the formatter, so `spotless:apply` never + writes CRLF (see [Line endings](#line-endings)). + +## Line endings + +This repository pins line endings, and it does so for a concrete reason: with +`packaging=pom` and no `flatten-maven-plugin`, its `pom.xml` is deployed to Maven +Central **verbatim**. Checked out on a machine with `core.autocrlf=true` — the +Windows default — every text file arrives with CRLF, and the published `.pom` would +differ from one built elsewhere. For a project with Java sources the same variance +lands in the sources jar and in every copied resource. + +`.gitattributes` forces LF in the working tree on every platform, overriding whatever +`core.autocrlf` or `core.eol` the developer has set. Windows batch files are the +single exception, because `cmd.exe` is fragile with LF-only scripts. `.editorconfig` +mirrors the same rules for editors. + +This eliminates line endings as a source of byte-level variance. It does **not** by +itself make builds reproducible across operating systems — that involves further +variables and is not claimed here. + +### What child projects need to do + +Git attributes are repository-local: Maven inheritance cannot deliver them. A child +project inherits the Spotless setting below, but must carry its own `.gitattributes`. +Copy this file into the repository root: + +```gitattributes +# Force LF in the working tree on every platform, overriding local core.autocrlf. +* text=auto eol=lf + +# Windows batch files are the exception: cmd.exe is fragile with LF-only scripts. +*.bat text eol=crlf +*.cmd text eol=crlf + +# Truly binary files: never normalized, never diffed as text. +*.class binary +*.eot binary +*.gif binary +*.gz binary +*.ico binary +*.jar binary +*.jks binary +*.jpeg binary +*.jpg binary +*.p12 binary +*.pdf binary +*.png binary +*.ttf binary +*.woff binary +*.woff2 binary +*.zip binary +``` + +Then run `git add --renormalize .` once and commit whatever it stages. In a +repository that never received CRLF this is a no-op. + +Take this project's [`.editorconfig`](.editorconfig) along with it. Its `[*.java]` +block is set to 2 spaces and a 100 column limit, matching the Google Java Format +this parent enforces via Spotless — a `.editorconfig` specifying anything else makes +the editor fight the formatter on every save. The `[*.{cmd,bat}]` block keeps editors +in agreement with the CRLF pin above. + +### What the parent enforces on its own + +The parent sets `UNIX` on the Spotless plugin, which every +child inherits. Spotless defaults to `GIT_ATTRIBUTES`, so without this a +`spotless:apply` run on a Windows machine in a repository lacking `.gitattributes` +would actively write CRLF into Java sources. + +The reach of that setting is narrow, and worth stating plainly: it applies only to +files Spotless formats, and only when `spotless:apply` or `spotless:check` is invoked +— neither is bound to a lifecycle phase. It guarantees the mandated formatter never +*introduces* CRLF. It does not make a child's sources jar LF-clean; only the child's +own `.gitattributes` does that. ## Reproducible builds diff --git a/docs/TODO.md b/docs/TODO.md index 4d33da9..0372611 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -86,19 +86,22 @@ external verifier actually needs; deferred with the rest of the verification too **Prerequisite:** Spec 001. -## Windows line-ending behaviour ships unverified +## Windows line-ending behaviour is unverified on real Windows Spec 002 pins `* text=auto eol=lf`, which matters on exactly one platform: Windows, -where it has to beat the `core.autocrlf=true` default. The mechanism is specified — -a path-specific `eol` attribute takes precedence over `core.autocrlf` — but nobody -has observed it here. A `windows-latest` CI job asserting that `git ls-files --eol` -reports no `w/crlf` outside `*.cmd`/`*.bat` would turn the argument into a -measurement, at a cost of roughly fifteen lines. +where it has to beat the `core.autocrlf=true` default. During implementation the +mechanism was measured — checking files out with `core.autocrlf=true` and with +`core.eol=crlf` forced still yields LF for text files and CRLF for `*.cmd`, so the +attribute wins as specified. Git's conversion logic is the same implementation +everywhere, so this covers the decisive behaviour. + +What is still unobserved is the platform itself: no build has run on a real Windows +machine. A `windows-latest` CI job asserting that `git ls-files --eol` reports no +`w/crlf` outside `*.cmd`/`*.bat` would close that remainder for roughly fifteen lines. **Context:** A CI guard was offered during the grill session for spec 002 and declined, consistent with spec 001 shipping without automated verification. The -maintainer develops on macOS and cannot reproduce the case locally. - +maintainer develops on macOS. ## `claude-base` conventions need two fixes The org-wide convention documents live in `claude-base` and are vendored into each @@ -172,3 +175,18 @@ and the library should be rebuilt and tested once against 2.2.47. **Prerequisite:** Spec 003 released as a `java-parent` version. +## The project has no `CLAUDE.md` + +`/spec-implement` treats updating the Project Context sections of `CLAUDE.md` +(Features, Tech Stack, Structure, Architecture) as a mandatory closing step of every +spec. This repository has no `CLAUDE.md` at all, so that step has silently done +nothing for specs 001 and 002 and will keep doing nothing. + +Creating one means describing the whole project, not the slice a single spec touched +— `/project-analyze` is the tool for it. Worth doing once, after which the per-spec +update step becomes meaningful. + +**Context:** Surfaced during the quality review for spec 002; deliberately not done +inside that spec, because generating whole-project documentation is not part of +pinning line endings. + diff --git a/docs/specs/002-pinned-line-endings/design.md b/docs/specs/002-pinned-line-endings/design.md index 3e5da51..d58a666 100644 --- a/docs/specs/002-pinned-line-endings/design.md +++ b/docs/specs/002-pinned-line-endings/design.md @@ -230,13 +230,25 @@ sequenceDiagram Note over G: index stays platform-independent ``` -## Consequence: the Windows promise is argued, not measured +## Consequence: the Windows mechanism is measured, the Windows platform is not -`eol=lf` was chosen specifically for Windows, and no automated check verifies it. The -mechanism is well-specified — a path-specific `eol` attribute takes precedence over -`core.autocrlf` — but the behaviour ships untested against a real Windows runner, -which was a deliberate decision. The gap is recorded in `docs/TODO.md` so the -distinction between *specified* and *observed* stays visible. +`eol=lf` was chosen specifically for Windows, and no CI guard verifies it. The +decisive mechanism was however measured during implementation, by checking files out +of the index with the offending configurations forced on: + +| Configuration forced | `crlf.java` | `probe.cmd` | +|---|---|---| +| `core.autocrlf=true` (Windows default) | LF | CRLF | +| `core.eol=crlf` | LF | CRLF | +| `core.autocrlf=false` | LF | CRLF | + +The path-specific `eol` attribute wins in every case, which is the entire behaviour +the design depends on. Since Git's conversion logic is the same implementation on +every platform, this is stronger evidence than the specification alone. + +What remains untested is the platform, not the mechanism: no build has run on a real +Windows machine, so editor behaviour, filesystem effects and the Git for Windows +installer defaults are unobserved. That residual gap is recorded in `docs/TODO.md`. ## Acceptance @@ -247,6 +259,9 @@ There is no automated check, by decision. Acceptance before merge is: 3. `git check-attr text eol -- pom.xml mvnw mvnw.cmd .claude/skills/…/Lato-Regular.ttf` reports `eol=lf` for the first two, `eol=crlf` for `mvnw.cmd`, and `-text` for the font. +3a. Probe files confirm the conversion behaviour: a CRLF-authored `.java`, `.cmd`, + `.md` and `.toml` all land as `i/lf` in the index; a file without line endings + stays `i/none`; an explicitly marked `.p12` is stored byte-identical to disk. 4. `./mvnw clean verify` passes. 5. `./mvnw spotless:check` passes (no Java sources here, but the configuration must parse — an invalid `lineEndings` value fails the plugin). diff --git a/docs/specs/002-pinned-line-endings/steps.md b/docs/specs/002-pinned-line-endings/steps.md new file mode 100644 index 0000000..e86974f --- /dev/null +++ b/docs/specs/002-pinned-line-endings/steps.md @@ -0,0 +1,150 @@ +# Implementation Steps: Pinned line endings + +Spec: [`design.md`](design.md) · [`behaviors.md`](behaviors.md) · Issue #4 + +This spec changes build and repository configuration in a `packaging=pom` project +with no Java sources and no test framework. "Tests" are therefore Git and Maven +verification commands, not JUnit cases — the checks below are the executable form of +the behaviour scenarios. Scenarios that can only be observed on Windows are recorded +as specified-but-unverified, consistent with the design's +[no-guard decision](design.md#acceptance). + +--- + +## Step 1: Add `.gitattributes` + +- [x] Create `.gitattributes` with the `* text=auto eol=lf` baseline +- [x] Add the `*.bat` / `*.cmd` → `eol=crlf` exception with its rationale comment +- [x] Add explicit `binary` markers for the asset types present plus the binary types + a Java repository predictably acquires +- [x] Run `git add --renormalize .` and confirm it stages nothing + +**Acceptance criteria:** +- [x] `git check-attr text eol -- pom.xml` reports `text: auto` and `eol: lf` +- [x] `git check-attr eol -- mvnw.cmd` reports `eol: crlf` +- [x] `git check-attr text -- ` reports `text: unset` +- [x] `git ls-files --eol` shows no `i/crlf` and no `i/mixed` +- [x] `git add --renormalize .` produces no staged changes +- [x] `git status` reports no modification for `mvnw.cmd` + +**Related behaviors:** Text file on a Windows default installation · Extensionless +text file · Windows batch file · Unix checkout is unchanged · Local Git configuration +cannot override the pin · Binary assets are untouched · A file authored with CRLF is +normalized · A batch file authored with CRLF is normalized in the index · A binary +file is committed verbatim · Renormalization changes nothing · The existing working +tree stays valid · A new text file type nobody thought about · A new binary type +covered by an explicit marker · A new binary type not covered by any marker · A file +with mixed line endings · A file with no line endings at all + +--- + +## Step 2: Add `.editorconfig` + +- [x] Create `.editorconfig` from the Open Elements standard + (`.claude/skills/project-setup/references/editorconfig.md`) +- [x] Correct `[*.java]` to Google Java Format: `indent_size = 2`, + `ij_continuation_indent_size = 4`, `max_line_length = 100` +- [x] Add a `[*.{cmd,bat}]` block with `end_of_line = crlf` so the editor agrees with + the Git attribute +- [x] Comment the deviation from the org standard at the point of deviation + +**Acceptance criteria:** +- [x] `[*] end_of_line = lf` and `charset = utf-8` are present +- [x] The `[*.java]` block specifies 2 spaces and 100 columns, matching + `googleJavaFormat` +- [x] The `[*.{cmd,bat}]` block specifies CRLF, matching `.gitattributes` +- [x] The `ij_java_*` rules from the org standard are carried over unchanged + +**Related behaviors:** Java formatting agrees with the formatter · Editor and Git +agree on batch files + +--- + +## Step 3: Force UNIX line endings in the inherited Spotless configuration + +- [x] Add `UNIX` to the Spotless plugin configuration in + `pom.xml`, above the existing `` block +- [x] Comment why the `GIT_ATTRIBUTES` default is insufficient for child projects + +**Acceptance criteria:** +- [x] `./mvnw spotless:check` passes — an invalid enum value would fail the plugin at + configuration time +- [x] `./mvnw clean verify` passes +- [x] `./mvnw -Pfull-build clean verify` passes, including pomchecker + +**Related behaviors:** Formatting on Windows without a child `.gitattributes` · A +child can still override the setting · The configuration is validated by the build + +--- + +## Step 4: Document the pin in the README + +- [x] Add a **Line endings** section describing what is pinned and why the parent's + own published `.pom` is affected +- [x] Include a copy-paste `.gitattributes` block for child projects +- [x] State what the inherited Spotless setting does and does not cover +- [x] Reference the section from the existing Build conventions list + +**Acceptance criteria:** +- [x] The section states the narrow claim: line endings are eliminated as a variance + source; cross-OS reproducibility is not claimed +- [x] The copy-paste block matches the repository's own `.gitattributes` +- [x] No promise is made that the parent delivers `.gitattributes` to children + +**Related behaviors:** No scenario in `behaviors.md` describes README content +directly. This step supports *Java formatting agrees with the formatter* and +*Formatting on Windows without a child `.gitattributes`*, both of which depend on a +child project actually receiving the two files. + +--- + +## Step 5: Final verification + +- [x] `git ls-files --eol` audit across the whole tree +- [x] `git add --renormalize .` is a no-op +- [x] `./mvnw -Pfull-build clean verify` passes +- [x] `docs/specs/INDEX.md` status is correct + +**Acceptance criteria:** +- [x] All of the above pass +- [x] The diff contains no incidental whitespace changes to existing files + +**Related behaviors:** Weakening the attributes silently restores the old behaviour +(documented, deliberately unguarded) + +--- + +## Behavior Coverage + +23 scenarios. Layer is "Repo/Build" throughout — there is no application code, so the +executable form of a scenario is a Git or Maven command rather than a JUnit case. + +| Scenario | Verification | Step | +|---|---|---| +| Text file on a Windows default installation | Measured — `core.autocrlf=true` forced on checkout yields LF | 1 | +| Extensionless text file | Attribute asserted for `mvnw` and `.sdkmanrc` | 1 | +| Windows batch file | Measured — CRLF under `autocrlf=true`, `autocrlf=false` and `core.eol=crlf` | 1 | +| Unix checkout is unchanged | Measured — clean tree, no renormalization | 1 | +| Local Git configuration cannot override the pin | Measured — `autocrlf=true` and `core.eol=crlf` both lose | 1 | +| Binary assets are untouched | Measured — `.p12` blob byte-identical, `.ttf` reports `-text` | 1 | +| A file authored with CRLF is normalized | Measured — CRLF probe lands as `i/lf` | 1 | +| A batch file authored with CRLF is normalized in the index | Measured — `i/lf` with `attr/text eol=crlf` | 1 | +| A binary file is committed verbatim | Measured | 1 | +| Renormalization changes nothing | Measured — no tracked file staged | 1, 5 | +| The existing working tree stays valid | Measured — `mvnw.cmd` not reported modified | 1 | +| Java formatting agrees with the formatter | By inspection — no Java sources in this repository | 2 | +| Editor and Git agree on batch files | By inspection — `[*.{cmd,bat}]` matches `eol=crlf` | 2 | +| Formatting on Windows without a child `.gitattributes` | Not verifiable here — needs a child project | 3 | +| A child can still override the setting | Not verifiable here — needs a child project | 3 | +| The configuration is validated by the build | Measured — `spotless:check` exits 0 | 3 | +| The deployed POM does not depend on the build platform | Follows from the measured checkout behaviour; not directly compared across platforms | 1 | +| A new text file type nobody thought about | Measured — `.toml` probe lands as `i/lf` | 1 | +| A new binary type covered by an explicit marker | Measured — `.p12` probe | 1 | +| A new binary type not covered by any marker | Not verifiable — documented residual risk | 1 | +| A file with mixed line endings | Measured — mixed probe lands as `i/lf` | 1 | +| A file with no line endings at all | Measured — stays `i/none` | 1 | +| Weakening the attributes silently restores the old behaviour | Not guarded, by decision | 5 | + +**16 measured, 2 by inspection, 5 not verifiable in this repository.** The five gaps +need either a child project or a real Windows machine; both are recorded in +`docs/TODO.md`. diff --git a/docs/specs/INDEX.md b/docs/specs/INDEX.md index d7cb841..68523d2 100644 --- a/docs/specs/INDEX.md +++ b/docs/specs/INDEX.md @@ -3,5 +3,5 @@ | ID | Spec-Folder | Name | Areas | Description | GitHub Issue | Status | |-----|-------------|------|-------|-------------|--------------|--------| | 001 | 001-reproducible-build-timestamp | Reproducible build timestamp | build, infrastructure, documentation | Fixed `project.build.outputTimestamp` literal in the parent POM, inherited by all child projects, maintained by `release.sh` — so a third party can rebuild any Open Elements Java artifact byte-identically | #3 | done | -| 002 | 002-pinned-line-endings | Pinned line endings | build, infrastructure, documentation | `.gitattributes` forcing LF (CRLF for `*.bat`/`*.cmd`), a matching `.editorconfig` aligned with Google Java Format, and an inherited Spotless `lineEndings=UNIX` — so a checkout produces the same text bytes on every platform | — | open | +| 002 | 002-pinned-line-endings | Pinned line endings | build, infrastructure, documentation | `.gitattributes` forcing LF (CRLF for `*.bat`/`*.cmd`), a matching `.editorconfig` aligned with Google Java Format, and an inherited Spotless `lineEndings=UNIX` — so a checkout produces the same text bytes on every platform | #4 | done | | 003 | 003-openapi-stack-versions | OpenAPI stack versions | build, api, documentation | Import `springdoc-openapi-bom` and `swagger-bom` and manage `org.webjars:swagger-ui`, so the coupled OpenAPI stack resolves uniformly in every consumer instead of splitting into a `NoSuchMethodError` | — | open | diff --git a/pom.xml b/pom.xml index 3669886..b72e230 100644 --- a/pom.xml +++ b/pom.xml @@ -221,6 +221,13 @@ com.diffplug.spotless spotless-maven-plugin + + UNIX