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
2 changes: 1 addition & 1 deletion .claude/skills/java/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: java
description: Umfassende Java-Entwicklungsrichtlinien für Backend-Projekte. Enthält Konventionen, Integrationen (PostgreSQL, Javalin, Jackson, Log4j2), Patterns (Clean Architecture, Repository) und Build-Konfiguration (Maven). Triggers auf Java-Code, REST APIs, Datenbank, jOOQ, Flyway, DTOs, Records, Logging, Properties, Identifiers, Security.
# GENERIERT aus personal/skills-ref/java/ — nicht hier editieren; Aenderungen gehoeren nach ~/.claude/skills-ref/java/.
source: personal-provider-ref
ref-hash: sha256:8a4c909743dc64a6c65624290ae533a0c33027f19ad593362f2e0606491304d9
ref-hash: sha256:c9b1fe65dbfd6413221ddb5a5bea9c6c7dea36cd7b40623b0c2c4fc1efad50d0
---

# Java Development Guide
Expand Down
58 changes: 45 additions & 13 deletions .claude/skills/java/rules/build-maven.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,32 +93,64 @@ mvn wrapper:wrapper -Dmaven=3.9.12

---

### Assembly Plugin (Fat JAR)
### Assembly (Anwendungs-JAR plus `lib/`)

Eine Anwendung wird **nie** als Fat JAR gebaut — weder mit `maven-shade-plugin` noch mit dem
`jar-with-dependencies`-Deskriptor des Assembly-Plugins. Beide entpacken die Abhaengigkeiten in ein
einziges Archiv, und dabei ueberschreiben sich Dateien, die mehrere Jars an derselben Stelle
mitbringen: `META-INF/services`, `reference.conf`, Signaturdateien. Der Shade kann das mit
Transformern reparieren, `jar-with-dependencies` kann es gar nicht — und eine Reparatur, die still
scheitert, faellt erst beim Start auf, nicht im gruenen Build.

Stattdessen bleibt das Anwendungs-JAR fuer sich, die Abhaengigkeiten liegen als eigene Jars daneben,
und der Klassenpfad steht im Manifest.

```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.8.0</version>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven.dependency.plugin.version}</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals><goal>copy-dependencies</goal></goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven.jar.plugin.version}</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>net.example.Application</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals><goal>single</goal></goals>
</execution>
</executions>
</plugin>
```

`java -jar target/app.jar` funktioniert damit unveraendert: `Class-Path` im Manifest wird relativ zum
Jar aufgeloest. Zusaetzlich bleibt lesbar, welche Version welcher Abhaengigkeit ausgeliefert wird —
genau das verliert ein Fat JAR, und mit ihm die Grundlage fuer CVE-Scans und das Nachziehen einer
einzelnen Abhaengigkeit.

Ein verteilbares Archiv (`tar.gz`, `zip`) baut `maven-assembly-plugin` mit einem eigenen Deskriptor
darauf auf — erst dann, wenn ein Deployment eines braucht.

Bei einem `module-info.java` kommt hinzu, dass ein Fat JAR den Moduldeskriptor verwirft: `shade`
fuehrt ihn nicht zusammen, und die Anwendung laeuft danach aus dem unbenannten Modul. Die Assembly
erhaelt ihn.

---

### Befehle
Expand All @@ -144,4 +176,4 @@ mvn wrapper:wrapper -Dmaven=3.9.12
- [ ] pom.xml konfiguriert
- [ ] JDK Version gesetzt
- [ ] JUnit hinzugefuegt
- [ ] Assembly Plugin (optional)
- [ ] Abhaengigkeiten als eigene Jars plus Manifest-Klassenpfad (nie ein Fat JAR)
42 changes: 34 additions & 8 deletions bin/traceability/traceability.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# GENERIERT aus personal/tools-ref/traceability/ — nicht hier editieren; Aenderungen gehoeren nach ~/.claude/tools-ref/traceability/.
# source: personal-provider-ref
# ref-hash: sha256:e76cfbc5dcf2fb98d5403fbe8552b57c92abcdd461245c305280e08ca30c8843
# ref-hash: sha256:a2541ffdcf3e5f58f8644626143ebe99d85fe1dc5b3d7900c597667b84f3b757
"""
traceability.py — prueft die Verkettung zwischen Anforderungen und Tests.

Expand Down Expand Up @@ -159,20 +159,32 @@ def read_spec(path: Path, is_delta: bool):


def collect_requirements(spec_root: Path):
"""Ist-Spec zuerst, Delta-Specs ueberschreiben — so gewinnt der laufende Change."""
"""Ist-Spec zuerst, Delta-Specs ueberschreiben — so gewinnt der laufende Change.

Definieren mehrere offene Delta-Specs denselben Kurznamen, gewinnt die
hoechste Revision, nicht die zuletzt in Sortierreihenfolge gelesene Datei.
"""
definitions, duplicates = {}, []
removed, tombstones = {}, {}
delta_defined = set()

def record(defs, source_is_delta):
for a in defs:
previous = definitions.get(a.base)
if previous is not None and not source_is_delta:
duplicates.append(Finding(
"doppelter Kurzname",
f"req~{a.base} steht auch in {previous.file}:{previous.line}",
a.file, a.line))
continue
if previous is not None:
if not source_is_delta:
duplicates.append(Finding(
"doppelter Kurzname",
f"req~{a.base} steht auch in {previous.file}:{previous.line}",
a.file, a.line))
continue
# Revisionen werden der Reihe nach vergeben, die hoehere ist
# immer das spaetere Wort — Dateireihenfolge entscheidet nicht.
if a.base in delta_defined and a.revision < previous.revision:
continue
definitions[a.base] = a
if source_is_delta:
delta_defined.add(a.base)

specs_dir = spec_root / "specs"
if specs_dir.is_dir():
Expand Down Expand Up @@ -565,6 +577,20 @@ def self_check():
"a_test.go": "// [impl->req~zugang.ganz-neu~1]\n",
}, []),

# Zwei offene Delta-Specs auf demselben Kurznamen: "b" sortiert vor "g",
# die niedrigere Revision wird also NACH der hoeheren gelesen. Gewaenne
# die zuletzt gelesene Datei (der fruehere Fehler), stuende die Anforderung
# auf ~2 und der Verweis auf ~3 gaelte faelschlich als vorgegriffen.
"delta ueberholt delta: hoehere revision gewinnt trotz dateireihenfolge": ({
"docs/changes/2026-09-21-bot-name-patterns/specs/pulls/spec.md":
"## MODIFIED Requirements\n\n### Requirement: Bot-Filter\n"
"`req~pulls.bot-filter~3`\n",
"docs/changes/2026-09-21-gitea-bot-logins/specs/pulls/spec.md":
"## MODIFIED Requirements\n\n### Requirement: Bot-Filter\n"
"`req~pulls.bot-filter~2`\n",
"a_test.go": "// [impl->req~pulls.bot-filter~3]\n",
}, []),

"doppelter kurzname": ({
"docs/specs/zugang/spec.md":
"### Requirement: Eins\n`req~zugang.doppelt~1`\n"
Expand Down
Loading