Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1690da3fab | |||
| cb6e0103e7 |
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/lib/common.sh"
|
||||||
|
|
||||||
|
REPO_ROOT="$(resolve_repo_root)"
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
# check-docs-drift.sh
|
||||||
|
# Zweck: sehr schlanke Drift-Checks gegen die neue Doku-Struktur.
|
||||||
|
# - Kein Guidelines-System mehr.
|
||||||
|
# - Single Source of Truth: `docs/`
|
||||||
|
|
||||||
|
err=0
|
||||||
|
|
||||||
|
has() { grep -q "$2" "$1" || { echo "[DRIFT] '$2' fehlt in $1"; err=1; }; }
|
||||||
|
miss() { grep -q "$2" "$1" && { echo "[DRIFT] Veralteter Begriff '$2' in $1"; err=1; }; }
|
||||||
|
|
||||||
|
# Harte Altlast-Pfade dürfen nicht mehr vorkommen
|
||||||
|
if git grep -n "docs/00_Domain/" -- docs >/dev/null 2>&1; then
|
||||||
|
echo "[DRIFT] Veralteter Pfad 'docs/00_Domain/' in docs/* gefunden"
|
||||||
|
err=1
|
||||||
|
fi
|
||||||
|
if git grep -n "docs/adr/" -- docs >/dev/null 2>&1; then
|
||||||
|
echo "[DRIFT] Veralteter Pfad 'docs/adr/' in docs/* gefunden"
|
||||||
|
err=1
|
||||||
|
fi
|
||||||
|
if git grep -n "docs/c4/" -- docs >/dev/null 2>&1; then
|
||||||
|
echo "[DRIFT] Veralteter Pfad 'docs/c4/' in docs/* gefunden"
|
||||||
|
err=1
|
||||||
|
fi
|
||||||
|
if git grep -n "docs/how-to/" -- docs >/dev/null 2>&1; then
|
||||||
|
echo "[DRIFT] Veralteter Pfad 'docs/how-to/' in docs/* gefunden"
|
||||||
|
err=1
|
||||||
|
fi
|
||||||
|
if git grep -n "docs/reference/" -- docs >/dev/null 2>&1; then
|
||||||
|
echo "[DRIFT] Veralteter Pfad 'docs/reference/' in docs/* gefunden"
|
||||||
|
err=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Quelle der Wahrheit: Gateway-Technologie (sollte in Architektur/ADRs/C4 konsistent sein)
|
||||||
|
has docs/01_Architecture/ARCHITECTURE.md "Spring Cloud Gateway"
|
||||||
|
has docs/01_Architecture/adr/0007-api-gateway-pattern-de.md "Spring Cloud Gateway"
|
||||||
|
miss docs/01_Architecture/adr/0007-api-gateway-pattern-de.md "Ktor"
|
||||||
|
has docs/01_Architecture/c4/02-container-de.puml "Spring Cloud Gateway"
|
||||||
|
miss docs/01_Architecture/c4/02-container-de.puml "Ktor"
|
||||||
|
|
||||||
|
exit $err
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Common helpers for AI guardrail scripts
|
||||||
|
|
||||||
|
# Robustly resolve the repository root directory.
|
||||||
|
# Strategy: prefer Git; fallback to marker search upwards; last resort: current dir.
|
||||||
|
resolve_repo_root() {
|
||||||
|
local start
|
||||||
|
start="${1:-$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)}"
|
||||||
|
if command -v git >/dev/null 2>&1; then
|
||||||
|
if git -C "$start" rev-parse --show-toplevel >/dev/null 2>&1; then
|
||||||
|
git -C "$start" rev-parse --show-toplevel
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
local dir
|
||||||
|
dir="$(cd "$start" && pwd)"
|
||||||
|
while [ "$dir" != "/" ]; do
|
||||||
|
if [ -f "$dir/gradlew" ] || [ -f "$dir/settings.gradle.kts" ] || [ -d "$dir/.git" ]; then
|
||||||
|
echo "$dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
dir="$(dirname "$dir")"
|
||||||
|
done
|
||||||
|
pwd
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/lib/common.sh"
|
||||||
|
|
||||||
|
REPO_ROOT="$(resolve_repo_root)"
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
mkdir -p build/diagrams
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in docs/architecture/c4/*.puml; do
|
||||||
|
docker run --rm -v "$PWD":/data plantuml/plantuml -tsvg "/data/$f" -o "/data/build/diagrams"
|
||||||
|
echo "Rendered build/diagrams/$(basename "${f%.puml}").svg"
|
||||||
|
done
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/lib/common.sh"
|
||||||
|
|
||||||
|
REPO_ROOT="$(resolve_repo_root)"
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
QUICK_MODE=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--quick)
|
||||||
|
QUICK_MODE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
cat << 'EOF'
|
||||||
|
Docs Link-Validierung
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
./.ai/scripts/validate-links.sh [--quick]
|
||||||
|
|
||||||
|
BESCHREIBUNG:
|
||||||
|
Prüft Markdown-Links in `docs/**/*.md` auf gebrochene relative Pfade.
|
||||||
|
Ignoriert externe Links (http/https/mailto) sowie reine Anchors (#...).
|
||||||
|
|
||||||
|
OPTIONEN:
|
||||||
|
--quick Führt nur eine Teilmenge der Prüfungen durch (aktuell nicht implementiert).
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unbekannter Parameter: $1" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
python3 - <<'PY'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
|
root = Path.cwd()
|
||||||
|
docs_dir = root / "docs"
|
||||||
|
|
||||||
|
if not docs_dir.is_dir():
|
||||||
|
print(f"[ERROR] docs-Verzeichnis nicht gefunden: {docs_dir}", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
# Veraltete Pfad-Prüfungen wurden entfernt; Fokus auf Link-Integrität.
|
||||||
|
FORBIDDEN_SUBSTRINGS = []
|
||||||
|
|
||||||
|
md_files = sorted(docs_dir.rglob("*.md"))
|
||||||
|
|
||||||
|
link_pattern = re.compile(r"\]\(([^)]+)\)")
|
||||||
|
|
||||||
|
errors = 0
|
||||||
|
|
||||||
|
def is_external(target: str) -> bool:
|
||||||
|
t = target.lower()
|
||||||
|
return t.startswith("http://") or t.startswith("https://") or t.startswith("mailto:")
|
||||||
|
|
||||||
|
def strip_fragment_and_query(target: str) -> str:
|
||||||
|
target = target.split("#", 1)[0]
|
||||||
|
target = target.split("?", 1)[0]
|
||||||
|
return target
|
||||||
|
|
||||||
|
for f in md_files:
|
||||||
|
text = f.read_text(encoding="utf-8", errors="replace")
|
||||||
|
|
||||||
|
for forbidden in FORBIDDEN_SUBSTRINGS:
|
||||||
|
if forbidden in text:
|
||||||
|
print(f"[ERROR] Veralteter Pfad '{forbidden}' in {f}")
|
||||||
|
errors += 1
|
||||||
|
|
||||||
|
for match in link_pattern.finditer(text):
|
||||||
|
target = match.group(1).strip()
|
||||||
|
|
||||||
|
if not target:
|
||||||
|
continue
|
||||||
|
if is_external(target):
|
||||||
|
continue
|
||||||
|
if target.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if target.startswith("<") and target.endswith(">"):
|
||||||
|
target = target[1:-1]
|
||||||
|
|
||||||
|
target = unquote(strip_fragment_and_query(target))
|
||||||
|
|
||||||
|
if target.startswith("/"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ":" in target.split("/", 1)[0]:
|
||||||
|
# z.B. "vscode:..."
|
||||||
|
continue
|
||||||
|
|
||||||
|
resolved = (f.parent / target).resolve()
|
||||||
|
|
||||||
|
try:
|
||||||
|
resolved.relative_to(root.resolve())
|
||||||
|
except ValueError:
|
||||||
|
print(f"[ERROR] Link zeigt außerhalb des Repos: {f} -> {target}")
|
||||||
|
errors += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if resolved.is_dir():
|
||||||
|
if not (resolved / "README.md").is_file():
|
||||||
|
print(f"[ERROR] Verlinktes Verzeichnis ohne README.md: {f} -> {target}")
|
||||||
|
errors += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not resolved.exists():
|
||||||
|
print(f"[ERROR] Broken link: {f} -> {target}")
|
||||||
|
errors += 1
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
print(f"[ERROR] Link-Validierung fehlgeschlagen: {errors} Fehler")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"[OK] Link-Validierung erfolgreich: {len(md_files)} Markdown-Dateien geprüft")
|
||||||
|
PY
|
||||||
+1
-1
@@ -193,7 +193,7 @@ secrets/
|
|||||||
# ===================================================================
|
# ===================================================================
|
||||||
TODO*.md
|
TODO*.md
|
||||||
NOTES*.md
|
NOTES*.md
|
||||||
**/.junie/
|
.junie/
|
||||||
|
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
# Keep essential files (override exclusions)
|
# Keep essential files (override exclusions)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
desktop-tests:
|
desktop-tests:
|
||||||
# Komplett deaktivierbar über Repo-Variable: Settings → Variables → DESKTOP_CI_ENABLED=true
|
# Komplett deaktivierbar über Repo-Variable: Settings → Variables → DESKTOP_CI_ENABLED=true
|
||||||
# Zusätzlich: Für Plan‑B‑Builds überspringen, wenn Commit-Message [planb] enthält
|
# Zusätzlich: Für Plan‑B‑Builds überspringen, wenn Commit-Message [planb] enthält
|
||||||
if: ${{ vars.DESKTOP_CI_ENABLED == 'true' && !contains(github.event.head_commit.message, '[planb]') }}
|
if: ${{ vars.DESKTOP_CI_ENABLED == 'true' && !contains(gitea.event.head_commit.message, '[planb]') }}
|
||||||
name: Compose Desktop — Tests (headless) & Build
|
name: Compose Desktop — Tests (headless) & Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: Build and Publish Docker Images
|
name: Build and Publish Docker Images
|
||||||
run-name: Build & Publish by @${{ github.actor }}
|
run-name: Build & Publish by @${{ gitea.actor }}
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -117,8 +117,8 @@ jobs:
|
|||||||
images: ${{ env.REGISTRY_INTERNAL }}/${{ env.IMAGE_PREFIX }}/${{ matrix.image }}
|
images: ${{ env.REGISTRY_INTERNAL }}/${{ env.IMAGE_PREFIX }}/${{ matrix.image }}
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=tag
|
type=ref,event=tag
|
||||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
type=raw,value=latest,enable=${{ gitea.ref == 'refs/heads/main' }}
|
||||||
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
|
type=sha,format=long,enable=${{ gitea.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -132,5 +132,5 @@ jobs:
|
|||||||
provenance: false
|
provenance: false
|
||||||
sbom: false
|
sbom: false
|
||||||
build-args: |
|
build-args: |
|
||||||
BUILD_DATE=${{ github.event.head_commit.timestamp || 'unknown' }}
|
BUILD_DATE=${{ gitea.event.head_commit.timestamp || 'unknown' }}
|
||||||
VERSION=${{ github.sha }}
|
VERSION=${{ gitea.sha }}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
chmod +x install-conveyor.sh
|
chmod +x install-conveyor.sh
|
||||||
./install-conveyor.sh
|
./install-conveyor.sh
|
||||||
fi
|
fi
|
||||||
echo "$HOME/.conveyor/bin" >> $GITHUB_PATH
|
echo "$HOME/.conveyor/bin" >> $GITEA_PATH
|
||||||
|
|
||||||
- name: Windows .msi mit Conveyor bauen
|
- name: Windows .msi mit Conveyor bauen
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
no-hardcoded-versions:
|
no-hardcoded-versions:
|
||||||
# Für Plan-B-Builds überspringen: Commit-Message enthält [planb]
|
# Für Plan-B-Builds überspringen: Commit-Message enthält [planb]
|
||||||
if: ${{ !contains(github.event.head_commit.message, '[planb]') }}
|
if: ${{ !contains(gitea.event.head_commit.message, '[planb]') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
tag-release:
|
tag-release:
|
||||||
name: 🏷️ Git-Tag setzen
|
name: 🏷️ Git-Tag setzen
|
||||||
# Für Plan-B-Builds überspringen: Commit-Message enthält [planb]
|
# Für Plan-B-Builds überspringen: Commit-Message enthält [planb]
|
||||||
if: ${{ !contains(github.event.head_commit.message, '[planb]') }}
|
if: ${{ !contains(gitea.event.head_commit.message, '[planb]') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.read-version.outputs.version }}
|
version: ${{ steps.read-version.outputs.version }}
|
||||||
@@ -64,7 +64,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Git-Tag erstellen & pushen
|
- name: Git-Tag erstellen & pushen
|
||||||
if: steps.check-tag.outputs.already_tagged == 'false' && github.event.inputs.dry_run != 'true'
|
if: steps.check-tag.outputs.already_tagged == 'false' && gitea.event.inputs.dry_run != 'true'
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.read-version.outputs.tag }}"
|
TAG="${{ steps.read-version.outputs.tag }}"
|
||||||
VERSION="${{ steps.read-version.outputs.version }}"
|
VERSION="${{ steps.read-version.outputs.version }}"
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
package-linux:
|
package-linux:
|
||||||
name: 📦 Linux .deb Packaging
|
name: 📦 Linux .deb Packaging
|
||||||
# Nur ausführen, wenn Desktop-CI explizit aktiviert ist UND kein Plan‑B Commit
|
# Nur ausführen, wenn Desktop-CI explizit aktiviert ist UND kein Plan‑B Commit
|
||||||
if: ${{ vars.DESKTOP_CI_ENABLED == 'true' && !contains(github.event.head_commit.message, '[planb]') }}
|
if: ${{ vars.DESKTOP_CI_ENABLED == 'true' && !contains(gitea.event.head_commit.message, '[planb]') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: tag-release
|
needs: tag-release
|
||||||
|
|
||||||
@@ -88,11 +88,11 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup JDK 21 (Temurin)
|
- name: Setup JDK 25 (Temurin)
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: '21'
|
java-version: '25'
|
||||||
|
|
||||||
- name: Gradle cache
|
- name: Gradle cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -128,7 +128,7 @@ jobs:
|
|||||||
package-windows:
|
package-windows:
|
||||||
name: 📦 Windows .msi Packaging
|
name: 📦 Windows .msi Packaging
|
||||||
# Nur ausführen, wenn Desktop-CI explizit aktiviert ist UND kein Plan‑B Commit
|
# Nur ausführen, wenn Desktop-CI explizit aktiviert ist UND kein Plan‑B Commit
|
||||||
if: ${{ vars.DESKTOP_CI_ENABLED == 'true' && !contains(github.event.head_commit.message, '[planb]') }}
|
if: ${{ vars.DESKTOP_CI_ENABLED == 'true' && !contains(gitea.event.head_commit.message, '[planb]') }}
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: tag-release
|
needs: tag-release
|
||||||
|
|
||||||
@@ -136,11 +136,11 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup JDK 21 (Temurin)
|
- name: Setup JDK 25 (Temurin)
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: '21'
|
java-version: '25'
|
||||||
|
|
||||||
- name: Gradle cache
|
- name: Gradle cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -179,11 +179,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Summary ausgeben
|
- name: Summary ausgeben
|
||||||
run: |
|
run: |
|
||||||
echo "## 🚀 Release ${{ needs.tag-release.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
echo "## 🚀 Release ${{ needs.tag-release.outputs.version }}" >> $GITEA_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITEA_STEP_SUMMARY
|
||||||
echo "| Artefakt | Status |" >> $GITHUB_STEP_SUMMARY
|
echo "| Artefakt | Status |" >> $GITEA_STEP_SUMMARY
|
||||||
echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
|
echo "|----------|--------|" >> $GITEA_STEP_SUMMARY
|
||||||
echo "| Linux .deb | ${{ needs.package-linux.result }} |" >> $GITHUB_STEP_SUMMARY
|
echo "| Linux .deb | ${{ needs.package-linux.result }} |" >> $GITEA_STEP_SUMMARY
|
||||||
echo "| Windows .msi | ${{ needs.package-windows.result }} |" >> $GITHUB_STEP_SUMMARY
|
echo "| Windows .msi | ${{ needs.package-windows.result }} |" >> $GITEA_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITEA_STEP_SUMMARY
|
||||||
echo "**Git-Tag:** \`${{ needs.tag-release.outputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
|
echo "**Git-Tag:** \`${{ needs.tag-release.outputs.tag }}\`" >> $GITEA_STEP_SUMMARY
|
||||||
|
|||||||
@@ -1,43 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# check-docs-drift.sh
|
# Shim: Weiterleitung auf zentrale Guardrail in .ai/
|
||||||
# Zweck: sehr schlanke Drift-Checks gegen die neue Doku-Struktur.
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
|
||||||
# - Kein Guidelines-System mehr.
|
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR")"
|
||||||
# - Single Source of Truth: `docs/`
|
exec "$ROOT_DIR/.ai/scripts/check-docs-drift.sh" "$@"
|
||||||
|
|
||||||
err=0
|
|
||||||
|
|
||||||
has() { grep -q "$2" "$1" || { echo "[DRIFT] '$2' fehlt in $1"; err=1; }; }
|
|
||||||
miss() { grep -q "$2" "$1" && { echo "[DRIFT] Veralteter Begriff '$2' in $1"; err=1; }; }
|
|
||||||
|
|
||||||
# Harte Altlast-Pfade dürfen nicht mehr vorkommen
|
|
||||||
if git grep -n "docs/00_Domain/" -- docs >/dev/null 2>&1; then
|
|
||||||
echo "[DRIFT] Veralteter Pfad 'docs/00_Domain/' in docs/* gefunden"
|
|
||||||
err=1
|
|
||||||
fi
|
|
||||||
if git grep -n "docs/adr/" -- docs >/dev/null 2>&1; then
|
|
||||||
echo "[DRIFT] Veralteter Pfad 'docs/adr/' in docs/* gefunden"
|
|
||||||
err=1
|
|
||||||
fi
|
|
||||||
if git grep -n "docs/c4/" -- docs >/dev/null 2>&1; then
|
|
||||||
echo "[DRIFT] Veralteter Pfad 'docs/c4/' in docs/* gefunden"
|
|
||||||
err=1
|
|
||||||
fi
|
|
||||||
if git grep -n "docs/how-to/" -- docs >/dev/null 2>&1; then
|
|
||||||
echo "[DRIFT] Veralteter Pfad 'docs/how-to/' in docs/* gefunden"
|
|
||||||
err=1
|
|
||||||
fi
|
|
||||||
if git grep -n "docs/reference/" -- docs >/dev/null 2>&1; then
|
|
||||||
echo "[DRIFT] Veralteter Pfad 'docs/reference/' in docs/* gefunden"
|
|
||||||
err=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Quelle der Wahrheit: Gateway-Technologie (sollte in Architektur/ADRs/C4 konsistent sein)
|
|
||||||
has docs/01_Architecture/ARCHITECTURE.md "Spring Cloud Gateway"
|
|
||||||
has docs/01_Architecture/adr/0007-api-gateway-pattern-de.md "Spring Cloud Gateway"
|
|
||||||
miss docs/01_Architecture/adr/0007-api-gateway-pattern-de.md "Ktor"
|
|
||||||
has docs/01_Architecture/c4/02-container-de.puml "Spring Cloud Gateway"
|
|
||||||
miss docs/01_Architecture/c4/02-container-de.puml "Ktor"
|
|
||||||
|
|
||||||
exit $err
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
mkdir -p build/diagrams
|
# Shim: Weiterleitung auf zentrale Guardrail in .ai/
|
||||||
shopt -s nullglob
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
|
||||||
for f in docs/architecture/c4/*.puml; do
|
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR")"
|
||||||
docker run --rm -v "$PWD":/data plantuml/plantuml -tsvg "/data/$f" -o "/data/build/diagrams"
|
exec "$ROOT_DIR/.ai/scripts/render-plantuml.sh" "$@"
|
||||||
echo "Rendered build/diagrams/$(basename "${f%.puml}").svg"
|
|
||||||
done
|
|
||||||
|
|||||||
@@ -1,136 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# validate-links.sh - Link-Validierung für Projektdokumentation (`docs/**`).
|
# Shim: Weiterleitung auf zentrale Guardrail in .ai/
|
||||||
# Zweck: Guardrail für die "Docs-as-Code"-Strategie.
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
|
||||||
|
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR")"
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
exec "$ROOT_DIR/.ai/scripts/validate-links.sh" "$@"
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
||||||
cd "$PROJECT_ROOT"
|
|
||||||
|
|
||||||
QUICK_MODE=false
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case $1 in
|
|
||||||
--quick)
|
|
||||||
QUICK_MODE=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--help|-h)
|
|
||||||
cat << 'EOF'
|
|
||||||
Docs Link-Validierung
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
./.junie/scripts/validate-links.sh [--quick]
|
|
||||||
|
|
||||||
BESCHREIBUNG:
|
|
||||||
Prüft Markdown-Links in `docs/**/*.md` auf gebrochene relative Pfade.
|
|
||||||
Ignoriert externe Links (http/https/mailto) sowie reine Anchors (#...).
|
|
||||||
|
|
||||||
OPTIONEN:
|
|
||||||
--quick Führt nur eine Teilmenge der Prüfungen durch (aktuell nicht implementiert).
|
|
||||||
EOF
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "[ERROR] Unbekannter Parameter: $1" >&2
|
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
python3 - <<'PY'
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from urllib.parse import unquote
|
|
||||||
|
|
||||||
root = Path.cwd()
|
|
||||||
docs_dir = root / "docs"
|
|
||||||
|
|
||||||
if not docs_dir.is_dir():
|
|
||||||
print(f"[ERROR] docs-Verzeichnis nicht gefunden: {docs_dir}", file=sys.stderr)
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
# Veraltete Pfad-Prüfungen wurden entfernt, da sie zu wartungsintensiv waren.
|
|
||||||
# Das Skript konzentriert sich nun auf die Validierung der Link-Integrität.
|
|
||||||
FORBIDDEN_SUBSTRINGS = []
|
|
||||||
|
|
||||||
md_files = sorted(docs_dir.rglob("*.md"))
|
|
||||||
|
|
||||||
link_pattern = re.compile(r"\]\(([^)]+)\)")
|
|
||||||
|
|
||||||
errors = 0
|
|
||||||
|
|
||||||
def is_external(target: str) -> bool:
|
|
||||||
t = target.lower()
|
|
||||||
return t.startswith("http://") or t.startswith("https://") or t.startswith("mailto:")
|
|
||||||
|
|
||||||
def strip_fragment_and_query(target: str) -> str:
|
|
||||||
# remove fragment and query parts
|
|
||||||
target = target.split("#", 1)[0]
|
|
||||||
target = target.split("?", 1)[0]
|
|
||||||
return target
|
|
||||||
|
|
||||||
for f in md_files:
|
|
||||||
text = f.read_text(encoding="utf-8", errors="replace")
|
|
||||||
|
|
||||||
for forbidden in FORBIDDEN_SUBSTRINGS:
|
|
||||||
if forbidden in text:
|
|
||||||
print(f"[ERROR] Veralteter Pfad '{forbidden}' in {f}")
|
|
||||||
errors += 1
|
|
||||||
|
|
||||||
for match in link_pattern.finditer(text):
|
|
||||||
target = match.group(1).strip()
|
|
||||||
|
|
||||||
if not target:
|
|
||||||
continue
|
|
||||||
if is_external(target):
|
|
||||||
continue
|
|
||||||
if target.startswith("#"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# drop angle brackets <...> used in markdown for urls with spaces
|
|
||||||
if target.startswith("<") and target.endswith(">"):
|
|
||||||
target = target[1:-1]
|
|
||||||
|
|
||||||
target = unquote(strip_fragment_and_query(target))
|
|
||||||
|
|
||||||
# ignore absolute paths in the repo (we treat them as doc-style links; validate only if relative)
|
|
||||||
if target.startswith("/"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# ignore non-file targets (e.g. empty or protocol-less anchors)
|
|
||||||
if ":" in target.split("/", 1)[0]:
|
|
||||||
# things like "vscode:..." etc.
|
|
||||||
continue
|
|
||||||
|
|
||||||
# treat as file path relative to markdown file
|
|
||||||
resolved = (f.parent / target).resolve()
|
|
||||||
|
|
||||||
# keep validation within repo
|
|
||||||
try:
|
|
||||||
resolved.relative_to(root.resolve())
|
|
||||||
except ValueError:
|
|
||||||
print(f"[ERROR] Link zeigt außerhalb des Repos: {f} -> {target}")
|
|
||||||
errors += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# allow directories if they contain README.md
|
|
||||||
if resolved.is_dir():
|
|
||||||
if not (resolved / "README.md").is_file():
|
|
||||||
print(f"[ERROR] Verlinktes Verzeichnis ohne README.md: {f} -> {target}")
|
|
||||||
errors += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not resolved.exists():
|
|
||||||
print(f"[ERROR] Broken link: {f} -> {target}")
|
|
||||||
errors += 1
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print(f"[ERROR] Link-Validierung fehlgeschlagen: {errors} Fehler")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print(f"[OK] Link-Validierung erfolgreich: {len(md_files)} Markdown-Dateien geprüft")
|
|
||||||
PY
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# .aiignore - Verhindert Token-Waste für Nolik
|
||||||
|
|
||||||
|
# Abhängigkeiten & Binaries
|
||||||
|
build/
|
||||||
|
.gradle/
|
||||||
|
*.jar
|
||||||
|
*.deb
|
||||||
|
*.msi
|
||||||
|
|
||||||
|
# Sensible Daten (auch lokal!)
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
config/docker/certs/
|
||||||
|
*.pem
|
||||||
|
*.jks
|
||||||
|
postgres-data/
|
||||||
|
valkey-data/
|
||||||
|
|
||||||
|
# Doku-Builds (Nolik soll die Source-Files in docs/ lesen, nicht die HTML-Exporte)
|
||||||
|
build/dokka/
|
||||||
|
docs/Neumarkt2026/*.pdf
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Shim: Weiterleitung auf zentrale Guardrail in .ai/
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
|
||||||
|
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR")"
|
||||||
|
exec "$ROOT_DIR/.ai/scripts/check-docs-drift.sh" "$@"
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Shim: Weiterleitung auf zentrale Guardrail in .ai/
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd)"
|
||||||
|
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$SCRIPT_DIR")"
|
||||||
|
exec "$ROOT_DIR/.ai/scripts/validate-links.sh" "$@"
|
||||||
Reference in New Issue
Block a user