diff --git a/.claude/skills/java/SKILL.md b/.claude/skills/java/SKILL.md
index 9785366..8775293 100644
--- a/.claude/skills/java/SKILL.md
+++ b/.claude/skills/java/SKILL.md
@@ -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
diff --git a/.claude/skills/java/rules/build-maven.md b/.claude/skills/java/rules/build-maven.md
index 1e91d7f..3f6db3e 100644
--- a/.claude/skills/java/rules/build-maven.md
+++ b/.claude/skills/java/rules/build-maven.md
@@ -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
org.apache.maven.plugins
- maven-assembly-plugin
- 3.8.0
+ maven-dependency-plugin
+ ${maven.dependency.plugin.version}
+
+
+ copy-dependencies
+ package
+ copy-dependencies
+
+ ${project.build.directory}/lib
+ runtime
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven.jar.plugin.version}
-
- jar-with-dependencies
-
net.example.Application
+ true
+ lib/
-
-
- package
- single
-
-
```
+`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
@@ -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)
diff --git a/bin/traceability/traceability.py b/bin/traceability/traceability.py
index 334a80e..02a2ef0 100644
--- a/bin/traceability/traceability.py
+++ b/bin/traceability/traceability.py
@@ -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.
@@ -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():
@@ -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"