chore(plugin): add plugin-shared drift-check script
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.
This commit is contained in:
Executable
+116
@@ -0,0 +1,116 @@
|
|||||||
|
#!/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
|
||||||
Reference in New Issue
Block a user