Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<lineEndings>UNIX</lineEndings>` 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

Expand Down
34 changes: 26 additions & 8 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

27 changes: 21 additions & 6 deletions docs/specs/002-pinned-line-endings/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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).
Expand Down
Loading