f2634a7a0c
Contract-verification approach: skill references/ files are curated extracts (umlaut-transliterated, partial), so a content hash would false-positive. Script verifies each extract declares an existing plugin-shared master via its 'Master version:' footer and that any named block resolves. Router full-mirror refs report MISSING-FOOTER (informative, not drift). Test run: 22 checked, 0 drift, exit 0.
117 lines
4.2 KiB
Bash
Executable File
117 lines
4.2 KiB
Bash
Executable File
#!/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/<name>/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/<datei>.md` [ (Block `<xml_tag>`) ]
|
|
# den die kuratierten Auszuege tragen. Das Script prueft diesen Vertrag:
|
|
# 1. Master-Pfad aus dem Footer existiert -> sonst DRIFT (broken pointer)
|
|
# 2. Optionaler "(Block `<tag>`)"-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 `<xml_tag>`)
|
|
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
|