#!/usr/bin/env bash #------------------------------------------------------------------------------- ## @file kb_index.sh ## @brief Erzeugt ai/context/knowledge/INDEX.md aus dem Frontmatter aller Einträge ## und prüft die Pflichtfelder (id, title, type, status, updated, owner) ## @author ## @owner ## @assist KI-unterstützt (Claude), geprüft von ## @version 1.0.0 (07.10.2026) ## ## @usage kb_index.sh [Optionen] ## @options ## @OPTIONS@ ## @exit 0 = ok · 1 = Fehler / Index veraltet (--check) · 2 = Aufruffehler #------------------------------------------------------------------------------- set -Eeuo pipefail SCRIPT_PATH="$(readlink -f -- "${BASH_SOURCE[0]}")" SCRIPT_DIR="${SCRIPT_PATH%/*}" SCRIPT_NAME="${SCRIPT_PATH##*/}" # shellcheck source=_functions.sh source "${SCRIPT_DIR}/_functions.sh" || { echo "FEHLER: ${SCRIPT_DIR}/_functions.sh fehlt" >&2; exit 1; } B_OPTS+=( "c|check|flag|0|nur prüfen: Pflichtfelder ok und INDEX.md aktuell?" "k|kbdir|value:DIR|../ai/context/knowledge|Wurzel der Knowledge-DB (relativ = zum Script)" ) b_init "$@" set -- "${ARGS[@]}" REQUIRED=(id title type status updated owner) ## @brief Liest einen Frontmatter-Wert (einzeilig) aus einer Markdown-Datei ## @param $1 Datei ## @param $2 Schlüssel ## @return 0; Wert auf stdout (leer, wenn nicht vorhanden) fm_get() { awk -v key="$2" ' NR == 1 && $0 != "---" { exit } NR > 1 && $0 == "---" { exit } NR > 1 { k = $0; sub(/:.*/, "", k) if (k == key) { v = $0; sub(/^[^:]*:[ \t]*/, "", v); sub(/[ \t]+#.*$/, "", v); gsub(/^"|"$/, "", v); print v; exit } }' "$1" } ## @brief Baut den Index-Inhalt und meldet fehlende Pflichtfelder ## @param $1 KB-Wurzel ## @return 0 ok, 1 mindestens ein Eintrag unvollständig; Markdown auf stdout build_index() { local kb="$1" f rel dir last_dir="" key val rc=0 n=0 local -A ids=() printf '# Knowledge-Index\n\n' printf '> Automatisch erzeugt von `bin/kb_index.sh` – nicht von Hand bearbeiten.\n' printf '> Format der Einträge: [README.md](README.md)\n' while IFS= read -r f; do rel="${f#"$kb"/}"; dir="${rel%%/*}" [[ "$dir" == "$rel" ]] && dir="." for key in "${REQUIRED[@]}"; do [[ -n "$(fm_get "$f" "$key")" ]] || { berror "${rel}: Pflichtfeld '${key}' fehlt"; rc=1; } done val="$(fm_get "$f" id)" if [[ -n "$val" ]]; then [[ -n "${ids[$val]:-}" ]] && { berror "${rel}: id '${val}' doppelt (auch ${ids[$val]})"; rc=1; } ids[$val]="$rel" fi if [[ "$dir" != "$last_dir" ]]; then printf '\n## %s\n\n| id | Titel | Status | Stand | Tags |\n|---|---|---|---|---|\n' "$dir" last_dir="$dir" fi printf '| %s | [%s](%s) | %s | %s | %s |\n' "$val" "$(fm_get "$f" title)" "$rel" \ "$(fm_get "$f" status)" "$(fm_get "$f" updated)" "$(fm_get "$f" tags | tr -d '[]')" (( ++n )) done < <(find "$kb" -type f -name '*.md' ! -name 'README.md' ! -name 'INDEX.md' ! -name '_*' | LC_ALL=C sort) (( n )) || printf '\n_(noch keine Einträge)_\n' bdebug "${n} Einträge gelesen" return "$rc" } ## @brief Hauptablauf ## @return 0 ok, 1 Fehler / veraltet main() { (( $# == 0 )) || bdie "Keine Positionsargumente erwartet" 2 local kb rc=0 new kb="$(readlink -f -- "$(b_abspath "$OPTION_KBDIR")")" [[ -d "$kb" ]] || { berror "KB-Verzeichnis fehlt: $kb"; return 1; } b_tmpdir new="${B_TMP_DIR}/INDEX.md" build_index "$kb" > "$new" || rc=1 if (( OPTION_CHECK )); then if ! cmp -s "$new" "${kb}/INDEX.md"; then berror "INDEX.md ist veraltet – bin/kb_index.sh ausführen"; rc=1 fi (( rc )) || becho "Knowledge-DB ok" return "$rc" fi if cmp -s "$new" "${kb}/INDEX.md"; then becho "INDEX.md unverändert" else brun cp -- "$new" "${kb}/INDEX.md" becho "INDEX.md aktualisiert" fi return "$rc" } main "$@"