tooling: make .junie/.gemini docs-first (remove legacy guidelines)
This commit is contained in:
Regular → Executable
+28
-28
@@ -1,43 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# 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; }; }
|
||||
|
||||
# Quelle der Wahrheit: Spring Cloud Gateway
|
||||
has docs/overview/system-overview.md "Spring Cloud Gateway"
|
||||
has docs/architecture/adr/0007-api-gateway-pattern-de.md "Spring Cloud Gateway"
|
||||
miss docs/architecture/adr/0007-api-gateway-pattern-de.md "Ktor"
|
||||
|
||||
# C4: Container muss Technology korrekt führen
|
||||
has docs/architecture/c4/02-container-de.puml "Spring Cloud Gateway"
|
||||
miss docs/architecture/c4/02-container-de.puml "Ktor"
|
||||
|
||||
# Verbiete versehentlich verbliebene englische ADR/C4 ohne -de
|
||||
if ls docs/architecture/adr/*.md 2>/dev/null | grep -E -v '-de\.md$' >/dev/null; then
|
||||
echo "[DRIFT] Englische ADR-Dateien ohne -de gefunden in docs/architecture/adr/"
|
||||
ls docs/architecture/adr/*.md | grep -E -v '-de\.md$' || true
|
||||
# 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 ls docs/architecture/c4/*.puml 2>/dev/null | grep -E -v '-de\.puml$' >/dev/null; then
|
||||
echo "[DRIFT] Englische C4-Dateien ohne -de gefunden in docs/architecture/c4/"
|
||||
ls docs/architecture/c4/*.puml | grep -E -v '-de\.puml$' || true
|
||||
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
|
||||
|
||||
# ADR-Stubs: max. 40 Zeilen und YouTrack-Link Pflicht, wenn als Stub gekennzeichnet
|
||||
for f in $(grep -RIl "^doc_type: adr-link" docs/architecture/adr 2>/dev/null || true); do
|
||||
lines=$(wc -l < "$f" | tr -d ' ')
|
||||
if [ "${lines}" -gt 40 ]; then
|
||||
echo "[DRIFT] ADR-Stub überschreitet 40 Zeilen: $f (${lines})"
|
||||
err=1
|
||||
fi
|
||||
if ! grep -Eiq "https?://[^ ]*youtrack" "$f"; then
|
||||
echo "[DRIFT] ADR-Stub ohne YouTrack-Link: $f"
|
||||
err=1
|
||||
fi
|
||||
done
|
||||
# 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,308 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# create-guideline.sh - Automatisierte Guideline-Erstellung für Meldestelle Guidelines
|
||||
# Version: 1.0.0
|
||||
# Autor: Junie AI-Assistant
|
||||
# Datum: 2025-09-15
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Bestimme das Projekt-Root-Verzeichnis
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Wechsle ins Projekt-Root für korrekte relative Pfade
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
GUIDELINES_DIR=".junie/guidelines"
|
||||
TEMPLATES_DIR="$GUIDELINES_DIR/_templates"
|
||||
META_DIR="$GUIDELINES_DIR/_meta"
|
||||
|
||||
# Farben für Output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging-Funktionen
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
# Hilfe-Funktion
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Meldestelle Guidelines Creator
|
||||
|
||||
USAGE:
|
||||
./create-guideline.sh [TYPE] [NAME] [SCOPE] [OPTIONS]
|
||||
|
||||
ARGUMENTE:
|
||||
TYPE Typ der Guideline (project-standard|process-guide|technology)
|
||||
NAME Name der neuen Guideline (ohne .md Extension)
|
||||
SCOPE Bereich/Scope der Guideline
|
||||
|
||||
OPTIONEN:
|
||||
--output-dir DIR Alternatives Zielverzeichnis (Standard: entsprechend TYPE)
|
||||
--no-meta-update Metadaten (versions.json) nicht automatisch aktualisieren
|
||||
--dry-run Zeige nur was erstellt würde, ohne Dateien zu schreiben
|
||||
--help Diese Hilfe anzeigen
|
||||
|
||||
BEISPIELE:
|
||||
./create-guideline.sh project-standard security-standards security-practices
|
||||
./create-guideline.sh process-guide deployment-process deployment-workflow
|
||||
./create-guideline.sh technology kubernetes-guide kubernetes-orchestration
|
||||
|
||||
VERFÜGBARE TEMPLATES:
|
||||
project-standard-template.md -> project-standards/
|
||||
process-guide-template.md -> process-guides/
|
||||
technology-guideline-template.md -> technology-guides/
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Globale Variablen
|
||||
TYPE=""
|
||||
NAME=""
|
||||
SCOPE=""
|
||||
OUTPUT_DIR=""
|
||||
NO_META_UPDATE=false
|
||||
DRY_RUN=false
|
||||
|
||||
# Command-line Parameter parsen
|
||||
parse_arguments() {
|
||||
if [[ $# -eq 0 ]] || [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
|
||||
show_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Erforderliche Argumente
|
||||
if [[ $# -lt 3 ]]; then
|
||||
log_error "Nicht genug Argumente. Benötigt: TYPE NAME SCOPE"
|
||||
echo "Nutze --help für Details"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TYPE="$1"
|
||||
NAME="$2"
|
||||
SCOPE="$3"
|
||||
shift 3
|
||||
|
||||
# Optionale Parameter
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--output-dir)
|
||||
OUTPUT_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-meta-update)
|
||||
NO_META_UPDATE=true
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_error "Unbekannter Parameter: $1"
|
||||
echo "Nutze --help für verfügbare Optionen"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Template-Pfad und Zielverzeichnis bestimmen
|
||||
determine_paths() {
|
||||
local template_file=""
|
||||
local default_target_dir=""
|
||||
|
||||
case "$TYPE" in
|
||||
"project-standard")
|
||||
template_file="$TEMPLATES_DIR/project-standard-template.md"
|
||||
default_target_dir="$GUIDELINES_DIR/project-standards"
|
||||
;;
|
||||
"process-guide")
|
||||
template_file="$TEMPLATES_DIR/process-guide-template.md"
|
||||
default_target_dir="$GUIDELINES_DIR/process-guides"
|
||||
;;
|
||||
"technology")
|
||||
template_file="$TEMPLATES_DIR/technology-guideline-template.md"
|
||||
default_target_dir="$GUIDELINES_DIR/technology-guides"
|
||||
;;
|
||||
*)
|
||||
log_error "Unbekannter Guideline-Typ: $TYPE"
|
||||
echo "Verfügbare Typen: project-standard, process-guide, technology"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ ! -f "$template_file" ]]; then
|
||||
log_error "Template nicht gefunden: $template_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Zielverzeichnis bestimmen
|
||||
if [[ -n "$OUTPUT_DIR" ]]; then
|
||||
TARGET_DIR="$OUTPUT_DIR"
|
||||
else
|
||||
TARGET_DIR="$default_target_dir"
|
||||
fi
|
||||
|
||||
TARGET_FILE="$TARGET_DIR/$NAME.md"
|
||||
TEMPLATE_PATH="$template_file"
|
||||
}
|
||||
|
||||
# Prüfe ob Ziel bereits existiert
|
||||
check_target_exists() {
|
||||
if [[ -f "$TARGET_FILE" ]]; then
|
||||
log_error "Guideline existiert bereits: $TARGET_FILE"
|
||||
log_info "Lösche die existierende Datei oder wähle einen anderen Namen"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stelle sicher, dass Zielverzeichnis existiert
|
||||
if [[ "$DRY_RUN" = false ]] && [[ ! -d "$TARGET_DIR" ]]; then
|
||||
mkdir -p "$TARGET_DIR"
|
||||
log_info "Zielverzeichnis erstellt: $TARGET_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
# Template-Platzhalter ersetzen
|
||||
process_template() {
|
||||
local current_date=$(date +%Y-%m-%d)
|
||||
local processed_content
|
||||
|
||||
if [[ "$DRY_RUN" = true ]]; then
|
||||
log_info "DRY-RUN: Würde Template verarbeiten..."
|
||||
log_info " Template: $TEMPLATE_PATH"
|
||||
log_info " NAME: $NAME"
|
||||
log_info " SCOPE: $SCOPE"
|
||||
log_info " DATE: $current_date"
|
||||
log_info " Ziel: $TARGET_FILE"
|
||||
return
|
||||
fi
|
||||
|
||||
# Template lesen und Platzhalter ersetzen
|
||||
processed_content=$(cat "$TEMPLATE_PATH")
|
||||
processed_content="${processed_content//\{\{NAME\}\}/$NAME}"
|
||||
processed_content="${processed_content//\{\{SCOPE\}\}/$SCOPE}"
|
||||
processed_content="${processed_content//\{\{DATE\}\}/$current_date}"
|
||||
|
||||
# Zieldatei erstellen
|
||||
echo "$processed_content" > "$TARGET_FILE"
|
||||
log_success "Neue Guideline erstellt: $TARGET_FILE"
|
||||
}
|
||||
|
||||
# Metadaten aktualisieren
|
||||
update_metadata() {
|
||||
if [[ "$NO_META_UPDATE" = true ]]; then
|
||||
log_info "Überspringe Metadaten-Update (--no-meta-update)"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" = true ]]; then
|
||||
log_info "DRY-RUN: Würde Metadaten aktualisieren..."
|
||||
return
|
||||
fi
|
||||
|
||||
local versions_file="$META_DIR/versions.json"
|
||||
if [[ ! -f "$versions_file" ]]; then
|
||||
log_warning "versions.json nicht gefunden: $versions_file"
|
||||
return
|
||||
fi
|
||||
|
||||
# Relative Pfad für versions.json
|
||||
local relative_path="${TARGET_FILE#$GUIDELINES_DIR/}"
|
||||
local current_date=$(date +%Y-%m-%d)
|
||||
|
||||
# Temporäre JSON-Update (einfache Implementierung)
|
||||
# In einer vollständigen Implementation würde man jq verwenden
|
||||
log_info "Metadaten-Update für $relative_path implementierung ausstehend"
|
||||
log_warning "Bitte aktualisieren Sie $versions_file manuell"
|
||||
}
|
||||
|
||||
# Cross-Referenzen aktualisieren
|
||||
update_cross_references() {
|
||||
if [[ "$DRY_RUN" = true ]]; then
|
||||
log_info "DRY-RUN: Würde Cross-Referenzen aktualisieren..."
|
||||
return
|
||||
fi
|
||||
|
||||
local cross_refs_file="$META_DIR/cross-refs.json"
|
||||
if [[ ! -f "$cross_refs_file" ]]; then
|
||||
log_warning "cross-refs.json nicht gefunden: $cross_refs_file"
|
||||
return
|
||||
fi
|
||||
|
||||
log_info "Cross-Referenz-Update implementierung ausstehend"
|
||||
log_warning "Bitte aktualisieren Sie $cross_refs_file manuell"
|
||||
}
|
||||
|
||||
# Validierung der neuen Guideline
|
||||
validate_new_guideline() {
|
||||
if [[ "$DRY_RUN" = true ]]; then
|
||||
log_info "DRY-RUN: Würde neue Guideline validieren..."
|
||||
return
|
||||
fi
|
||||
|
||||
log_info "Validiere neue Guideline..."
|
||||
|
||||
# Nutze das validate-links.sh Script falls verfügbar
|
||||
local validate_script=".junie/scripts/validate-links.sh"
|
||||
if [[ -x "$validate_script" ]]; then
|
||||
log_info "Führe Link-Validierung aus..."
|
||||
if "$validate_script" --quick; then
|
||||
log_success "Validierung erfolgreich!"
|
||||
else
|
||||
log_warning "Validierung ergab Warnungen - bitte prüfen Sie die Ausgabe"
|
||||
fi
|
||||
else
|
||||
log_warning "validate-links.sh nicht verfügbar - manuelle Validierung empfohlen"
|
||||
fi
|
||||
}
|
||||
|
||||
# Hauptfunktion
|
||||
main() {
|
||||
echo "🚀 Meldestelle Guidelines Creator"
|
||||
echo "=================================="
|
||||
|
||||
parse_arguments "$@"
|
||||
determine_paths
|
||||
check_target_exists
|
||||
process_template
|
||||
update_metadata
|
||||
update_cross_references
|
||||
validate_new_guideline
|
||||
|
||||
echo ""
|
||||
if [[ "$DRY_RUN" = true ]]; then
|
||||
log_info "DRY-RUN abgeschlossen - keine Dateien wurden geändert"
|
||||
else
|
||||
log_success "Guideline-Erstellung erfolgreich abgeschlossen!"
|
||||
echo ""
|
||||
echo "📋 Nächste Schritte:"
|
||||
echo "1. Bearbeiten Sie die neue Guideline: $TARGET_FILE"
|
||||
echo "2. Aktualisieren Sie die Metadaten manuell:"
|
||||
echo " - $META_DIR/versions.json"
|
||||
echo " - $META_DIR/cross-refs.json"
|
||||
echo "3. Führen Sie eine vollständige Validierung aus:"
|
||||
echo " - .junie/scripts/validate-links.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
# Script ausführen
|
||||
main "$@"
|
||||
@@ -1,174 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Pre-commit Hook für Meldestelle Guidelines Validierung
|
||||
# Installation: ln -s ../../.junie/scripts/pre-commit-guidelines.sh .git/hooks/pre-commit
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔍 Pre-commit Guidelines Validation..."
|
||||
echo "======================================"
|
||||
|
||||
# Prüfe ob Guidelines-Änderungen vorliegen
|
||||
GUIDELINES_CHANGED=$(git diff --cached --name-only | grep -E '\.junie/(guidelines|scripts)/' || true)
|
||||
|
||||
if [[ -z "$GUIDELINES_CHANGED" ]]; then
|
||||
echo "ℹ️ Keine Guidelines-Änderungen erkannt - überspringe Validierung"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "📝 Geänderte Guidelines-Dateien:"
|
||||
echo "$GUIDELINES_CHANGED" | sed 's/^/ - /'
|
||||
echo ""
|
||||
|
||||
# Arbeitsverzeichnis sicherstellen
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
# Temporäres Verzeichnis für Staging-Area-Dateien
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf $TEMP_DIR" EXIT
|
||||
|
||||
# Staging-Area-Dateien extrahieren für Validierung
|
||||
echo "🔄 Extrahiere Staging-Area-Dateien..."
|
||||
echo "$GUIDELINES_CHANGED" | while read file; do
|
||||
if [[ -n "$file" ]]; then
|
||||
mkdir -p "$TEMP_DIR/$(dirname "$file")"
|
||||
git show ":$file" > "$TEMP_DIR/$file" 2>/dev/null || {
|
||||
# Neue Dateien (noch nicht im Repository)
|
||||
if [[ -f "$file" ]]; then
|
||||
cp "$file" "$TEMP_DIR/$file"
|
||||
fi
|
||||
}
|
||||
fi
|
||||
done
|
||||
|
||||
# 1. YAML-Syntax Schnellprüfung
|
||||
echo "📋 Prüfe YAML-Syntax..."
|
||||
yaml_errors=0
|
||||
find "$TEMP_DIR/.junie/guidelines" -name "*.md" -not -path "*/_archived/*" 2>/dev/null | while read file; do
|
||||
if [[ -f "$file" ]]; then
|
||||
# YAML-Header extrahieren
|
||||
yaml_content=$(sed -n '/^---$/,/^---$/p' "$file" | head -n -1 | tail -n +2)
|
||||
if [[ -n "$yaml_content" ]]; then
|
||||
echo "$yaml_content" | python3 -c "import yaml, sys; yaml.safe_load(sys.stdin)" 2>/dev/null || {
|
||||
echo " ❌ YAML-Fehler in $(basename "$file")"
|
||||
yaml_errors=$((yaml_errors + 1))
|
||||
}
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $yaml_errors -gt 0 ]]; then
|
||||
echo "❌ $yaml_errors YAML-Syntax-Fehler gefunden"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. Erforderliche Metadaten prüfen
|
||||
echo "🏷️ Prüfe erforderliche Metadaten..."
|
||||
metadata_errors=0
|
||||
find "$TEMP_DIR/.junie/guidelines" -name "*.md" -not -path "*/_archived/*" -not -name "README.md" 2>/dev/null | while read file; do
|
||||
if [[ -f "$file" ]]; then
|
||||
filename=$(basename "$file")
|
||||
missing_fields=()
|
||||
|
||||
if ! grep -q "guideline_type:" "$file"; then
|
||||
missing_fields+=("guideline_type")
|
||||
fi
|
||||
if ! grep -q "ai_context:" "$file"; then
|
||||
missing_fields+=("ai_context")
|
||||
fi
|
||||
if ! grep -q "last_updated:" "$file"; then
|
||||
missing_fields+=("last_updated")
|
||||
fi
|
||||
|
||||
if [[ ${#missing_fields[@]} -gt 0 ]]; then
|
||||
echo " ❌ $filename fehlt: ${missing_fields[*]}"
|
||||
metadata_errors=$((metadata_errors + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $metadata_errors -gt 0 ]]; then
|
||||
echo "❌ $metadata_errors Metadaten-Fehler gefunden"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3. JSON-Konfigurationsdateien validieren
|
||||
echo "🔧 Prüfe JSON-Konfiguration..."
|
||||
json_errors=0
|
||||
find "$TEMP_DIR/.junie/guidelines/_meta" -name "*.json" 2>/dev/null | while read file; do
|
||||
if [[ -f "$file" ]]; then
|
||||
jq empty "$file" 2>/dev/null || {
|
||||
echo " ❌ JSON-Syntax-Fehler in $(basename "$file")"
|
||||
json_errors=$((json_errors + 1))
|
||||
}
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $json_errors -gt 0 ]]; then
|
||||
echo "❌ $json_errors JSON-Syntax-Fehler gefunden"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4. Aktuelles Datum in last_updated prüfen
|
||||
echo "📅 Prüfe Datum-Aktualität..."
|
||||
current_date=$(date +%Y-%m-%d)
|
||||
date_warnings=0
|
||||
find "$TEMP_DIR/.junie/guidelines" -name "*.md" -not -path "*/_archived/*" 2>/dev/null | while read file; do
|
||||
if [[ -f "$file" ]]; then
|
||||
filename=$(basename "$file")
|
||||
if grep -q "last_updated:" "$file"; then
|
||||
file_date=$(grep "last_updated:" "$file" | cut -d'"' -f2 2>/dev/null || echo "")
|
||||
if [[ "$file_date" != "$current_date" && -n "$file_date" ]]; then
|
||||
echo " ⚠️ $filename hat Datum $file_date (heute: $current_date)"
|
||||
date_warnings=$((date_warnings + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $date_warnings -gt 0 ]]; then
|
||||
echo "⚠️ $date_warnings Guidelines haben veraltete Daten (wird toleriert)"
|
||||
fi
|
||||
|
||||
# 5. Script-Berechtigungen prüfen (nur bei geänderten Scripts)
|
||||
SCRIPT_CHANGES=$(echo "$GUIDELINES_CHANGED" | grep -E '\.junie/scripts/.*\.sh$' || true)
|
||||
if [[ -n "$SCRIPT_CHANGES" ]]; then
|
||||
echo "⚙️ Prüfe Script-Berechtigungen..."
|
||||
script_errors=0
|
||||
echo "$SCRIPT_CHANGES" | while read script; do
|
||||
if [[ -f "$script" && ! -x "$script" ]]; then
|
||||
echo " ❌ $script ist nicht ausführbar"
|
||||
script_errors=$((script_errors + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $script_errors -gt 0 ]]; then
|
||||
echo "❌ $script_errors Script-Berechtigungsfehler"
|
||||
echo "💡 Lösung: chmod +x <script-name>"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 6. Schnelle Link-Validierung (nur bei verfügbarem validate-links.sh)
|
||||
if [[ -x ".junie/scripts/validate-links.sh" ]]; then
|
||||
echo "🔗 Schnelle Link-Validierung..."
|
||||
if ! ./.junie/scripts/validate-links.sh --quick --staged 2>/dev/null; then
|
||||
echo "⚠️ Link-Validierung fehlgeschlagen (wird toleriert im Pre-commit)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Erfolgreiche Validierung
|
||||
echo ""
|
||||
echo "✅ Pre-commit Guidelines Validation erfolgreich!"
|
||||
echo " - YAML-Syntax: OK"
|
||||
echo " - Metadaten: OK"
|
||||
echo " - JSON-Konfiguration: OK"
|
||||
echo " - Script-Berechtigungen: OK"
|
||||
if [[ $date_warnings -gt 0 ]]; then
|
||||
echo " - Datum-Warnings: $date_warnings (toleriert)"
|
||||
fi
|
||||
echo ""
|
||||
echo "🚀 Commit kann fortgesetzt werden..."
|
||||
|
||||
exit 0
|
||||
+106
-307
@@ -1,343 +1,142 @@
|
||||
#!/bin/bash
|
||||
|
||||
# validate-links.sh - Automatisierte Link-Validierung für Meldestelle Guidelines
|
||||
# Version: 1.0.0
|
||||
# Autor: Junie AI-Assistant
|
||||
# Datum: 2025-09-15
|
||||
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Bestimme das Projekt-Root-Verzeichnis
|
||||
# validate-links.sh - Link-Validierung für Projektdokumentation (`docs/**`).
|
||||
# Zweck: Guardrail für die neue Doku-Strategie (Single Source of Truth = `docs/`).
|
||||
# Hinweis: Das frühere Guidelines-System (`.junie/guidelines/**`) ist entfernt.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# Wechsle ins Projekt-Root für korrekte relative Pfade
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
GUIDELINES_DIR=".junie/guidelines"
|
||||
META_DIR="$GUIDELINES_DIR/_meta"
|
||||
CROSS_REFS_FILE="$META_DIR/cross-refs.json"
|
||||
SCRIPTS_DIR=".junie/scripts"
|
||||
|
||||
# Farben für Output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging-Funktionen
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
# Hauptvariablen
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
QUICK_MODE=false
|
||||
|
||||
# Command-line Parameter
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--quick)
|
||||
QUICK_MODE=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
cat << 'EOF'
|
||||
Meldestelle Guidelines Link-Validierung
|
||||
case $1 in
|
||||
--quick)
|
||||
QUICK_MODE=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
cat << 'EOF'
|
||||
Docs Link-Validierung
|
||||
|
||||
USAGE:
|
||||
./validate-links.sh [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--quick Schnelle Validierung (nur kritische Checks)
|
||||
--help Diese Hilfe anzeigen
|
||||
./.junie/scripts/validate-links.sh [--quick]
|
||||
|
||||
BESCHREIBUNG:
|
||||
Validiert alle Links und Cross-Referenzen in den Guidelines basierend auf
|
||||
der cross-refs.json Matrix. Prüft:
|
||||
|
||||
- Cross-Referenzen zwischen Guidelines
|
||||
- Markdown-Links in Guidelines
|
||||
- YAML-Metadaten-Konsistenz
|
||||
- Template-Struktur-Konsistenz
|
||||
|
||||
EXIT-CODES:
|
||||
0 = Alle Validierungen erfolgreich
|
||||
1 = Fehler gefunden
|
||||
2 = Warnings gefunden (nur bei --strict)
|
||||
Prüft Markdown-Links in `docs/**/*.md` auf gebrochene relative Pfade.
|
||||
Ignoriert externe Links (http/https/mailto) sowie reine Anchors (#...).
|
||||
|
||||
OPTIONEN:
|
||||
--quick Nur schnelle Checks (zusätzlich werden harte Altlast-Pfade geprüft)
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unbekannter Parameter: $1"
|
||||
echo "Nutze --help für Hilfe"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "[ERROR] Unbekannter Parameter: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "🔍 Meldestelle Guidelines Link-Validierung"
|
||||
echo "=================================================="
|
||||
echo "Datum: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "Modus: $([ "$QUICK_MODE" = true ] && echo "Quick" || echo "Vollständig")"
|
||||
echo ""
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote
|
||||
|
||||
# Prüfe ob erforderliche Dateien existieren
|
||||
if [[ ! -f "$CROSS_REFS_FILE" ]]; then
|
||||
log_error "Cross-Referenz-Datei nicht gefunden: $CROSS_REFS_FILE"
|
||||
exit 1
|
||||
fi
|
||||
root = Path.cwd()
|
||||
docs_dir = root / "docs"
|
||||
|
||||
if [[ ! -d "$GUIDELINES_DIR" ]]; then
|
||||
log_error "Guidelines-Verzeichnis nicht gefunden: $GUIDELINES_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if not docs_dir.is_dir():
|
||||
print(f"[ERROR] docs-Verzeichnis nicht gefunden: {docs_dir}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
# 1. Cross-Referenz-Validierung
|
||||
validate_cross_references() {
|
||||
log_info "Validiere Cross-Referenzen aus cross-refs.json..."
|
||||
# Harte Altlast-Pfade, die nicht mehr im Repo vorkommen sollen
|
||||
FORBIDDEN_SUBSTRINGS = [
|
||||
"docs/00_Domain/",
|
||||
"docs/adr/",
|
||||
"docs/c4/",
|
||||
"docs/how-to/",
|
||||
"docs/reference/",
|
||||
]
|
||||
|
||||
local temp_guidelines=$(mktemp)
|
||||
local temp_refs=$(mktemp)
|
||||
md_files = sorted(docs_dir.rglob("*.md"))
|
||||
|
||||
# Alle Guidelines aus cross-refs.json extrahieren
|
||||
jq -r '.cross_references | keys[]' "$CROSS_REFS_FILE" > "$temp_guidelines" 2>/dev/null || {
|
||||
log_error "Fehler beim Lesen der cross-refs.json"
|
||||
((ERRORS++))
|
||||
return
|
||||
}
|
||||
link_pattern = re.compile(r"\]\(([^)]+)\)")
|
||||
|
||||
while IFS= read -r guideline; do
|
||||
# Special handling for README.md which is at project root
|
||||
if [[ "$guideline" == "README.md" ]]; then
|
||||
guideline_file="$PROJECT_ROOT/README.md"
|
||||
else
|
||||
guideline_file="$GUIDELINES_DIR/$guideline"
|
||||
fi
|
||||
errors = 0
|
||||
|
||||
# Prüfe ob Guideline-Datei existiert
|
||||
if [[ ! -f "$guideline_file" ]]; then
|
||||
log_error "Guideline '$guideline' in cross-refs.json aber Datei fehlt: $guideline_file"
|
||||
((ERRORS++))
|
||||
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
|
||||
fi
|
||||
|
||||
# Hole referenzierte Guidelines aus JSON
|
||||
jq -r ".cross_references[\"$guideline\"].references_to[]? // empty" "$CROSS_REFS_FILE" > "$temp_refs" 2>/dev/null
|
||||
|
||||
while IFS= read -r ref; do
|
||||
# Skip leere Zeilen
|
||||
[[ -z "$ref" ]] && continue
|
||||
|
||||
# Relativer Pfad zu absolut konvertieren
|
||||
if [[ "$ref" == /* ]]; then
|
||||
ref_file="$GUIDELINES_DIR$ref"
|
||||
elif [[ "$ref" == *"/" ]]; then
|
||||
# Directory-Referenz (z.B. technology-guides/docker/)
|
||||
ref_dir="$GUIDELINES_DIR/$ref"
|
||||
if [[ ! -d "$ref_dir" ]]; then
|
||||
log_error "Referenziertes Verzeichnis '$ref' existiert nicht: $ref_dir"
|
||||
((ERRORS++))
|
||||
fi
|
||||
continue
|
||||
else
|
||||
# Alle Referenzen sind relativ zum Guidelines-Root-Verzeichnis
|
||||
ref_file="$GUIDELINES_DIR/$ref"
|
||||
fi
|
||||
|
||||
# Normalisiere Pfad
|
||||
ref_file=$(realpath -m "$ref_file" 2>/dev/null || echo "$ref_file")
|
||||
|
||||
# Prüfe ob referenzierte Datei existiert
|
||||
if [[ ! -f "$ref_file" ]]; then
|
||||
log_error "'$guideline' referenziert '$ref', aber Datei existiert nicht: $ref_file"
|
||||
((ERRORS++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Prüfe ob der Link tatsächlich im Markdown existiert (nur im vollständigen Modus)
|
||||
if [[ "$QUICK_MODE" = false ]]; then
|
||||
ref_basename=$(basename "$ref" .md)
|
||||
# Check for link with basename in brackets OR reference path (with or without directory) in parentheses
|
||||
if ! grep -q "\[$ref_basename\]" "$guideline_file" && ! grep -qE "\([^)]*$ref\)" "$guideline_file"; then
|
||||
log_warning "'$guideline' sollte '$ref' referenzieren, aber Link fehlt im Markdown"
|
||||
((WARNINGS++))
|
||||
fi
|
||||
fi
|
||||
|
||||
done < "$temp_refs"
|
||||
|
||||
log_success "'$guideline' - Cross-Referenzen validiert"
|
||||
|
||||
done < "$temp_guidelines"
|
||||
|
||||
rm -f "$temp_guidelines" "$temp_refs"
|
||||
}
|
||||
|
||||
# 2. Markdown-Links Validierung
|
||||
validate_markdown_links() {
|
||||
if [[ "$QUICK_MODE" = true ]]; then
|
||||
log_info "Überspringe Markdown-Link-Validierung (Quick-Modus)"
|
||||
return
|
||||
fi
|
||||
|
||||
log_info "Validiere Markdown-Links in Guidelines..."
|
||||
|
||||
find "$GUIDELINES_DIR" -name "*.md" -not -path "*/_archived/*" | while read file; do
|
||||
# Relative Links extrahieren ([Text](./path/file.md))
|
||||
grep -o "\[.*\](\./[^)]*\.md)" "$file" 2>/dev/null | sed 's/.*(\.\///' | sed 's/).*//' | while read link; do
|
||||
[[ -z "$link" ]] && continue
|
||||
|
||||
# Absoluten Pfad konstruieren
|
||||
dir=$(dirname "$file")
|
||||
target_file="$dir/$link"
|
||||
target_file=$(realpath -m "$target_file" 2>/dev/null || echo "$target_file")
|
||||
|
||||
if [[ ! -f "$target_file" ]]; then
|
||||
log_error "$(basename "$file") verlinkt auf '$link', aber Ziel existiert nicht: $target_file"
|
||||
((ERRORS++))
|
||||
fi
|
||||
done
|
||||
|
||||
# Relative Links mit ../ extrahieren
|
||||
grep -o "\[.*\](\.\./[^)]*\.md)" "$file" 2>/dev/null | sed 's/.*(\.\.\///' | sed 's/).*//' | while read link; do
|
||||
[[ -z "$link" ]] && continue
|
||||
|
||||
dir=$(dirname "$file")
|
||||
target_file="$dir/../$link"
|
||||
target_file=$(realpath -m "$target_file" 2>/dev/null || echo "$target_file")
|
||||
|
||||
if [[ ! -f "$target_file" ]]; then
|
||||
log_error "$(basename "$file") verlinkt auf '../$link', aber Ziel existiert nicht: $target_file"
|
||||
((ERRORS++))
|
||||
fi
|
||||
done
|
||||
|
||||
log_success "$(basename "$file") - Markdown-Links validiert"
|
||||
done
|
||||
}
|
||||
|
||||
# 3. YAML-Metadaten Validierung
|
||||
validate_yaml_metadata() {
|
||||
log_info "Validiere YAML-Metadaten-Konsistenz..."
|
||||
|
||||
find "$GUIDELINES_DIR" -name "*.md" -not -path "*/_archived/*" -not -name "README.md" -not -name "master-guideline.md" | while read file; do
|
||||
# YAML-Header extrahieren (nur zwischen den ersten beiden --- Zeilen)
|
||||
yaml_content=$(awk '/^---$/{if(++count==2) exit} count==1 && !/^---$/{print}' "$file" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$yaml_content" ]]; then
|
||||
log_warning "'$(basename "$file")' hat keinen YAML-Header"
|
||||
((WARNINGS++))
|
||||
if is_external(target):
|
||||
continue
|
||||
if target.startswith("#"):
|
||||
continue
|
||||
fi
|
||||
|
||||
# YAML-Syntax prüfen (falls python verfügbar)
|
||||
if command -v python3 &> /dev/null; then
|
||||
echo "$yaml_content" | python3 -c "import yaml,sys; yaml.safe_load(sys.stdin)" 2>/dev/null || {
|
||||
log_error "'$(basename "$file")' hat ungültige YAML-Syntax im Header"
|
||||
((ERRORS++))
|
||||
continue
|
||||
}
|
||||
fi
|
||||
# drop angle brackets <...> used in markdown for urls with spaces
|
||||
if target.startswith("<") and target.endswith(">"):
|
||||
target = target[1:-1]
|
||||
|
||||
# Erforderliche Felder prüfen
|
||||
required_fields=("guideline_type" "scope" "audience" "last_updated" "ai_context")
|
||||
for field in "${required_fields[@]}"; do
|
||||
if ! echo "$yaml_content" | grep -q "^$field:" 2>/dev/null; then
|
||||
log_error "'$(basename "$file")' fehlt erforderliches YAML-Feld: $field"
|
||||
((ERRORS++))
|
||||
fi
|
||||
done
|
||||
target = unquote(strip_fragment_and_query(target))
|
||||
|
||||
# Dependencies validieren (nur im vollständigen Modus)
|
||||
if [[ "$QUICK_MODE" = false ]]; then
|
||||
deps=$(echo "$yaml_content" | grep "dependencies:" 2>/dev/null | sed 's/.*\[//' | sed 's/\].*//' | tr ',' '\n' | sed 's/[" ]//g' || echo "")
|
||||
for dep in $deps; do
|
||||
[[ -z "$dep" ]] && continue
|
||||
dep_file="$GUIDELINES_DIR/$dep"
|
||||
if [[ ! -f "$dep_file" ]]; then
|
||||
log_error "'$(basename "$file")' dependency '$dep' existiert nicht"
|
||||
((ERRORS++))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
# ignore absolute paths in the repo (we treat them as doc-style links; validate only if relative)
|
||||
if target.startswith("/"):
|
||||
continue
|
||||
|
||||
log_success "$(basename "$file") - Metadaten validiert"
|
||||
done
|
||||
}
|
||||
# ignore non-file targets (e.g. empty or protocol-less anchors)
|
||||
if ":" in target.split("/", 1)[0]:
|
||||
# things like "vscode:..." etc.
|
||||
continue
|
||||
|
||||
# 4. Template-Struktur Validierung
|
||||
validate_template_structure() {
|
||||
if [[ "$QUICK_MODE" = true ]]; then
|
||||
log_info "Überspringe Template-Validierung (Quick-Modus)"
|
||||
return
|
||||
fi
|
||||
# treat as file path relative to markdown file
|
||||
resolved = (f.parent / target).resolve()
|
||||
|
||||
log_info "Validiere Template-Struktur-Konsistenz..."
|
||||
# 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
|
||||
|
||||
local template_dir="$GUIDELINES_DIR/_templates"
|
||||
if [[ ! -d "$template_dir" ]]; then
|
||||
log_warning "Template-Verzeichnis nicht gefunden: $template_dir"
|
||||
((WARNINGS++))
|
||||
return
|
||||
fi
|
||||
# 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
|
||||
|
||||
# Prüfe ob neue Guidelines Template-Struktur folgen
|
||||
find "$GUIDELINES_DIR" -name "*.md" -not -path "*/_archived/*" -not -path "*/_templates/*" -not -name "README.md" | while read file; do
|
||||
# Prüfe grundlegende Template-Struktur
|
||||
if ! grep -q "^guideline_type:" "$file" 2>/dev/null; then
|
||||
log_warning "'$(basename "$file")' folgt möglicherweise nicht der Template-Struktur (fehlt guideline_type)"
|
||||
((WARNINGS++))
|
||||
fi
|
||||
if not resolved.exists():
|
||||
print(f"[ERROR] Broken link: {f} -> {target}")
|
||||
errors += 1
|
||||
|
||||
if ! grep -q "^ai_context:" "$file" 2>/dev/null; then
|
||||
log_warning "'$(basename "$file")' folgt möglicherweise nicht der Template-Struktur (fehlt ai_context)"
|
||||
((WARNINGS++))
|
||||
fi
|
||||
done
|
||||
if errors:
|
||||
print(f"[ERROR] Link-Validierung fehlgeschlagen: {errors} Fehler")
|
||||
sys.exit(1)
|
||||
|
||||
log_success "Template-Struktur validiert"
|
||||
}
|
||||
|
||||
# Hauptvalidierung ausführen
|
||||
main() {
|
||||
validate_cross_references
|
||||
validate_markdown_links
|
||||
validate_yaml_metadata
|
||||
validate_template_structure
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo "📊 Validierungs-Ergebnisse:"
|
||||
echo " Fehler: $ERRORS"
|
||||
echo " Warnungen: $WARNINGS"
|
||||
|
||||
if [[ $ERRORS -eq 0 && $WARNINGS -eq 0 ]]; then
|
||||
log_success "Alle Validierungen erfolgreich! 🎉"
|
||||
exit 0
|
||||
elif [[ $ERRORS -eq 0 ]]; then
|
||||
log_warning "Validierung abgeschlossen mit $WARNINGS Warnungen"
|
||||
exit 0
|
||||
else
|
||||
log_error "Validierung fehlgeschlagen mit $ERRORS Fehlern und $WARNINGS Warnungen"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Script ausführen
|
||||
main "$@"
|
||||
print(f"[OK] Link-Validierung erfolgreich: {len(md_files)} Markdown-Dateien geprüft")
|
||||
PY
|
||||
|
||||
Reference in New Issue
Block a user