#!/usr/bin/env bash set -euo pipefail # drift-check.sh — Reference-drift detector for idea2self-skills # ============================================================================= # ZWECK # plugin-shared/ haelt die Master-Versionen der geteilten Referenzen # (kommunikationsregeln.md, export-formate.md, methodik.md, recherche-regeln.md, # artefakt-pipeline.md + templates/). Jeder Skill unter # skills//references/ traegt KURATIERTE AUSZUEGE daraus — KEINE # 1:1-Kopien. Die Auszuege sind bewusst umformuliert (Umlaut-Transliteration # ue/oe/ae, gekuerzte Bloecke, skill-spezifische Zusatz-Bloecke). Dieses Script # erkennt Drift, ohne diese gewollten Abweichungen als Fehler zu melden. # # GEWAEHLTER ANSATZ — Contract-Verifikation (NICHT Content-Diff) # Ein naiver Hash- oder Section-Content-Vergleich wuerde wegen der # Umformulierung (Umlaute, gekuerzte/umbenannte Bloecke) bei JEDER Datei # Drift melden — unbrauchbar. Auch der Substring-pro-Zeile-Fallback scheitert, # weil schon die Umlaut-Transliteration (z.B. "Huerden" im Auszug vs "Hürden" # im Master) jede Zeile zum Fehlschlag bringt. # # Der einzige verlaessliche, maschinen-pruefbare Vertrag ist der Footer # **Master version:** `plugin-shared/.md` [ (Block ``) ] # den die kuratierten Auszuege tragen. Das Script prueft diesen Vertrag: # 1. Master-Pfad aus dem Footer existiert -> sonst DRIFT (broken pointer) # 2. Optionaler "(Block ``)"-Verweis existiert im Master -> sonst DRIFT # 3. Skill-Ref OHNE Footer -> MISSING-FOOTER (informativ, # zaehlt NICHT als Drift; die klarheit-mit-idea2self-Refs sind bewusste # Voll-Mirror ohne Footer-Vertrag). # # Damit faengt das Script die echte, handlungsrelevante Drift ab (Master # umbenannt/geloescht, referenzierter Block aus Master entfernt) ohne # Tausende False-Positives durch die gewollte Umformulierung. # # NUTZUNG # bash plugin-shared/drift-check.sh # Exit 0 = kein Drift, Exit 1 = Drift gefunden. # ============================================================================= shopt -s nullglob SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(dirname "$SCRIPT_DIR")" # plugin-shared liegt direkt unter Repo-Root command -v rg >/dev/null 2>&1 || true # rg optional; wir nutzen Bash-native Tools SKILLS_DIR="$ROOT/skills" if [[ ! -d "$SKILLS_DIR" ]]; then echo "ERROR: skills directory not found at $SKILLS_DIR" >&2 exit 1 fi checked=0 drift=0 # Sammle alle Skill-Reference-Dateien defensiv. ref_files=() if [[ -d "$SKILLS_DIR" ]]; then while IFS= read -r f; do ref_files+=("$f") done < <(find "$SKILLS_DIR" -path '*/references/*.md' -type f 2>/dev/null | sort) fi for ref in "${ref_files[@]}"; do [[ -e "$ref" ]] || continue rel="${ref#"$ROOT"/}" checked=$((checked + 1)) # Footer-Zeile extrahieren (erste Zeile mit "Master version:"). footer="$(grep -i "Master version" "$ref" 2>/dev/null | head -1 || true)" if [[ -z "$footer" ]]; then echo "MISSING-FOOTER $rel: kein 'Master version:'-Footer (Voll-Mirror? informativ, kein Drift)" continue fi # Master-Pfad aus Backticks ziehen: erstes `plugin-shared/...md`. master_rel="$(printf '%s\n' "$footer" \ | grep -oE 'plugin-shared/[A-Za-z0-9._/-]+\.md' | head -1 || true)" if [[ -z "$master_rel" ]]; then echo "DRIFT $rel: Footer vorhanden, aber kein plugin-shared/...md-Pfad parsbar" drift=$((drift + 1)) continue fi master_abs="$ROOT/$master_rel" if [[ ! -f "$master_abs" ]]; then echo "DRIFT $rel: Master-Pfad existiert nicht: $master_rel" drift=$((drift + 1)) continue fi # Optionalen Block-Verweis pruefen: (Block ``) block="$(printf '%s\n' "$footer" \ | grep -oE 'Block `<[A-Za-z0-9_]+>`' | head -1 || true)" if [[ -n "$block" ]]; then tag="$(printf '%s\n' "$block" | grep -oE '<[A-Za-z0-9_]+>' | head -1 || true)" if [[ -n "$tag" ]]; then if ! grep -qF "$tag" "$master_abs" 2>/dev/null; then echo "DRIFT $rel: referenzierter Block $tag fehlt im Master $master_rel" drift=$((drift + 1)) continue fi fi fi echo "OK $rel (master: $master_rel)" done echo "Checked $checked files, $drift drift(s)" if [[ "$drift" -gt 0 ]]; then exit 1 fi exit 0