356 lines
22 KiB
Bash
Executable file
356 lines
22 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
# Rauchtest mit den echten Werkzeugen (→ KONZEPT.md 17.2): Server starten,
|
||
# Testpakete mit Erkennungswörtern veröffentlichen, per `hugin sync` in ein
|
||
# leeres Repo installieren und Claude Code und OpenCode fragen. Dazu die
|
||
# Verhaltensfragen 4, 5 und 8 aus Stufe 0 sowie opencode/autoinvoke (→ STUFE0.md).
|
||
#
|
||
# Dazu: Installiert ein Agent ein Paket auf Bitte richtig, also über .hugin.yaml und
|
||
# hugin sync, wie es der MCP-Endpunkt von Hugin vorgibt, statt Dateien selbst zu schreiben?
|
||
# Und bricht er ab, statt Dateien selbst zu schreiben, wenn hugin nicht im PATH ist?
|
||
#
|
||
# Braucht Zugänge zu beiden Werkzeugen und kostet Tokens (rund 30 Modellaufrufe).
|
||
# Ergebnis: COMPATIBILITY.md im Wurzelordner.
|
||
#
|
||
# scripts/tool-smoke-test.sh
|
||
# TOOLS=opencode scripts/tool-smoke-test.sh # nur ein Werkzeug
|
||
# CLAUDE_MODEL=sonnet OPENCODE_MODEL=anthropic/claude-haiku-5-5 scripts/tool-smoke-test.sh
|
||
# OPENCODE=opencode2 OUT=/tmp/c.md scripts/tool-smoke-test.sh
|
||
# KEEP=1 scripts/tool-smoke-test.sh # Arbeitsordner und Logs bleiben
|
||
set -euo pipefail
|
||
|
||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||
WORK="$(mktemp -d)"
|
||
PORT="${PORT:-8766}"
|
||
URL="http://127.0.0.1:$PORT"
|
||
OUT="${OUT:-$ROOT/COMPATIBILITY.md}"
|
||
TOOLS="${TOOLS:-claude opencode}"
|
||
OC="${OPENCODE:-opencode}"
|
||
CLAUDE_MODEL="${CLAUDE_MODEL:-haiku}"
|
||
OPENCODE_MODEL="${OPENCODE_MODEL:-}"
|
||
TIMEOUT="${TIMEOUT:-180}" # Sekunden je Aufruf
|
||
TRIES="${TRIES:-2}" # Modelle antworten nicht immer gleich
|
||
export HUGIN_DATABASE_URL="sqlite:///$WORK/hugin.db"
|
||
export HUGIN_ADMIN_TOKEN="tool-smoke-admin"
|
||
ADMIN=(-H "Authorization: Bearer $HUGIN_ADMIN_TOKEN")
|
||
RESULTS="$WORK/results.tsv"
|
||
mkdir -p "$WORK/logs" && : > "$RESULTS"
|
||
|
||
cleanup() {
|
||
[[ -n "${SERVER:-}" ]] && kill "$SERVER" 2>/dev/null || true
|
||
if [[ -z "${KEEP:-}" ]]; then rm -rf "$WORK"; else echo "Arbeitsordner: $WORK"; fi
|
||
}
|
||
trap cleanup EXIT
|
||
|
||
uses() { [[ " $TOOLS " == *" $1 "* ]]; }
|
||
uses claude && ! command -v claude >/dev/null && { echo "claude fehlt (oder TOOLS setzen)"; exit 2; }
|
||
uses opencode && ! command -v "$OC" >/dev/null && { echo "$OC fehlt (oder TOOLS setzen)"; exit 2; }
|
||
|
||
api() { curl -fsS -H "Content-Type: application/json" "$@"; }
|
||
key() { python3 -c 'import json,sys; print(json.load(sys.stdin)["key"])'; }
|
||
|
||
echo "== Server"
|
||
(cd "$ROOT/server" && uv run -q hugin-server migrate)
|
||
(cd "$ROOT/server" && exec uv run -q hugin-server run --host 127.0.0.1 --port "$PORT") >"$WORK/server.log" 2>&1 &
|
||
SERVER=$!
|
||
for _ in $(seq 50); do curl -fsS "$URL/readyz" >/dev/null 2>&1 && break; sleep 0.2; done
|
||
curl -fsS "$URL/readyz" >/dev/null
|
||
|
||
api "${ADMIN[@]}" "$URL/api/v1/admin/tenants" -d '{"slug":"plattform","name":"Plattform"}' >/dev/null
|
||
api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/projects" -d '{"slug":"kern","name":"Kern"}' >/dev/null
|
||
AUTHOR=$(api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/keys" -d '{"kind":"project","name":"autor","project":"kern","roles":["pub_author"]}' | key)
|
||
REVIEWER=$(api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/keys" -d '{"kind":"project","name":"pruefer","project":"kern","roles":["pub_reviewer"]}' | key)
|
||
READER=$(api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/keys" -d '{"kind":"project","name":"leser","project":"kern"}' | key)
|
||
# Lokaler Server hat Risiko „Skripte“: persönlicher Key, zwei Freigaben von Personen
|
||
# Plugin hat Risiko „Rechte“: eine der zwei Freigaben vom Tenant-Admin
|
||
person() { api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/people" -d "{\"name\":\"$1\",\"mail\":\"$1@smoke.test\",\"project\":\"kern\",\"roles\":$2}" | key; }
|
||
PERSON_AUTHOR=$(person autor '["pub_author"]'); PERSON_REVIEWER=$(person pruefer '["pub_reviewer"]')
|
||
PERSON_REVIEWER2=$(person pruefer2 '["pub_reviewer"]'); PERSON_ADMIN=$(person admin '["pub_reviewer","tenant_admin"]')
|
||
|
||
# MCP-Paket zeigt auf diesen Hugin selbst; so lässt sich auch die Verbindung prüfen
|
||
PACKAGES="$WORK/packages"
|
||
cp -R "$ROOT/scripts/tool-smoke/packages" "$PACKAGES"
|
||
mkdir -p "$PACKAGES/pub-smoke-mcp/1.0.0"
|
||
cat > "$PACKAGES/pub-smoke-mcp/1.0.0/hugin.yaml" <<YAML
|
||
id: pub-smoke-mcp
|
||
kind: mcp
|
||
version: 1.0.0
|
||
owner: kern
|
||
scope: public
|
||
description: Bindet den Hugin des Rauchtests als MCP-Server an.
|
||
server:
|
||
name: hugin-smoke
|
||
url: $URL/mcp
|
||
headers:
|
||
Authorization: "Bearer {{env:HUGIN_SMOKE_KEY}}"
|
||
YAML
|
||
|
||
publish() {
|
||
local zip="$WORK/package.zip" base="$URL/api/v1/packages/$1/versions/1.0.0"
|
||
rm -f "$zip"; (cd "$PACKAGES/$1/1.0.0" && zip -qr "$zip" .)
|
||
curl -fsS -H "Authorization: Bearer $AUTHOR" -H "Content-Type: application/zip" --data-binary "@$zip" "$URL/api/v1/drafts" >/dev/null
|
||
api -H "Authorization: Bearer $AUTHOR" -X POST "$base/submit" >/dev/null
|
||
api -H "Authorization: Bearer $REVIEWER" "$base/approve" -d '{"comment":"Rauchtest"}' >/dev/null
|
||
}
|
||
# publish_example <paket> <prüfer…>: Beispielpaket aus contract/ mit Annas persönlichem Key
|
||
publish_example() {
|
||
local zip="$WORK/package.zip" id=$1 base="$URL/api/v1/packages/$1/versions/1.0.0"; shift
|
||
rm -f "$zip"; (cd "$ROOT/contract/examples/packages/$id/1.0.0" && zip -qr "$zip" .)
|
||
curl -fsS -H "Authorization: Bearer $PERSON_AUTHOR" -H "Content-Type: application/zip" --data-binary "@$zip" "$URL/api/v1/drafts" >/dev/null
|
||
api -H "Authorization: Bearer $PERSON_AUTHOR" -X POST "$base/submit" >/dev/null
|
||
for r in "$@"; do api -H "Authorization: Bearer $r" "$base/approve" -d '{"comment":"Rauchtest"}' >/dev/null; done
|
||
}
|
||
|
||
echo "== Pakete und Repo"
|
||
IDS=(pub-smoke-skill pub-smoke-manual pub-smoke-rules pub-smoke-command pub-smoke-agent pub-smoke-mcp)
|
||
for id in "${IDS[@]}"; do publish "$id"; done
|
||
# Lokaler MCP-Server (Python, nur Standardbibliothek): hugin sync installiert ihn nach
|
||
# HUGIN_MCP_HOME, die Werkzeuge starten ihn über hugin mcp run
|
||
publish_example pub-zeit-lokal "$PERSON_REVIEWER" "$PERSON_REVIEWER2"; IDS+=(pub-zeit-lokal)
|
||
# OpenCode-Plugin mit eigenem Werkzeug; Claude Code bekommt davon nichts
|
||
publish_example pub-specht-plugin "$PERSON_REVIEWER" "$PERSON_ADMIN"; IDS+=(pub-specht-plugin)
|
||
export HUGIN_MCP_HOME="$WORK/mcp" ZEIT_ZONE_HINWEIS=smoke
|
||
(cd "$ROOT/cli" && go build -o "$WORK/bin/hugin" ./cmd/hugin)
|
||
ln -s "$WORK/bin/hugin" "$WORK/hugin"
|
||
# PATH ohne hugin (für „Ohne hugin nichts schreiben“): alle Ordner weglassen, in denen eines liegt
|
||
NO_HUGIN_PATH=$(IFS=:; for d in $PATH; do [[ -x "$d/hugin" ]] || printf '%s:' "$d"; done); NO_HUGIN_PATH=${NO_HUGIN_PATH%:}
|
||
export PATH="$WORK/bin:$PATH" # für Agenten, die hugin sync selbst aufrufen
|
||
REPO="$WORK/repo"; mkdir -p "$REPO"; cd "$REPO"; git init -q
|
||
printf '# Rauchtest\n' > CLAUDE.md
|
||
export HUGIN_KEY="$READER" HUGIN_SMOKE_KEY="$READER"
|
||
"$WORK/hugin" init --server "$URL" --project kern >/dev/null
|
||
# Werkzeuge fest eintragen: im leeren Repo erkennt `auto` nur Claude (wegen CLAUDE.md)
|
||
sed -i.alt -e "s/^packages: \[\]/packages: [$(IFS=,; echo "${IDS[*]}")]/" \
|
||
-e "s/^tools: auto/tools: [$(echo $TOOLS | sed 's/ /, /g')]/" .hugin.yaml && rm -f .hugin.yaml.alt
|
||
"$WORK/hugin" sync
|
||
|
||
# Ausführen mit Zeitlimit; Modellaufrufe ohne Lesewerkzeuge, damit die Antwort
|
||
# aus dem geladenen Kontext kommt und nicht aus den Dateien im Repo
|
||
limit() { perl -e 'alarm shift; exec @ARGV' "$TIMEOUT" "$@" </dev/null; }
|
||
# Projekt-MCP-Server aus .mcp.json ohne Rückfrage zulassen; sonst stehen sie auf „Pending approval“
|
||
claude_ask() {
|
||
limit claude -p "$1" --model "$CLAUDE_MODEL" --settings '{"enableAllProjectMcpServers":true}' \
|
||
--disallowedTools "Read,Grep,Glob,Bash,WebFetch,WebSearch" --allowedTools "mcp__hugin-smoke__search_packages,mcp__zeit__zeit"
|
||
}
|
||
OC_DENY='{"permission":{"read":"deny","grep":"deny","glob":"deny","list":"deny","bash":"deny","webfetch":"deny","websearch":"deny"}}'
|
||
opencode_run() { OPENCODE_CONFIG_CONTENT="$OC_DENY" limit "$OC" run --pure ${OPENCODE_MODEL:+-m "$OPENCODE_MODEL"} "$@"; }
|
||
# --pure lädt keine Plugins; für das Plugin ohne (dann laden auch eigene globale Plugins)
|
||
opencode_plugins() { OPENCODE_CONFIG_CONTENT="$OC_DENY" limit "$OC" run ${OPENCODE_MODEL:+-m "$OPENCODE_MODEL"} "$@"; }
|
||
|
||
# Kann das Werkzeug überhaupt ein Modell aufrufen (Zugang, Anbieter)? Sonst
|
||
# zählen seine Modellprüfungen als „nicht geprüft“ statt als Fehler.
|
||
model_ok() {
|
||
local log="$WORK/logs/$1-preflight.log"
|
||
"${@:2}" >"$log" 2>&1 && grep -q "BEREIT" "$log" && return 0
|
||
echo " ! $1 kann kein Modell aufrufen, Modellprüfungen übersprungen:"
|
||
grep -m1 -i "error" "$log" | sed 's/\x1b\[[0-9;]*m//g; s/^/ /' || tail -n1 "$log"
|
||
return 1
|
||
}
|
||
|
||
# skip <prüfung> <werkzeug>: nicht geprüft
|
||
skip() { printf ' – %-8s %s\n' "$2" "$1"; printf 'smoke\t%s\t%s\tskip\t\n' "$1" "$2" >>"$RESULTS"; }
|
||
|
||
# check <bereich> <prüfung> <werkzeug> <muster> <befehl…>: ok, wenn die Ausgabe passt
|
||
check() {
|
||
local area=$1 name=$2 tool=$3 pattern=$4; shift 4
|
||
local log; log="$WORK/logs/$(printf '%s-%s' "$tool" "$name" | tr -c 'A-Za-z0-9-' '_').log"
|
||
local status=fail
|
||
for _ in $(seq "$TRIES"); do
|
||
"$@" >"$log" 2>&1 || true
|
||
if grep -Eqi -- "$pattern" "$log"; then status=ok; break; fi
|
||
done
|
||
printf ' %s %-8s %s\n' "$([[ $status == ok ]] && echo ✓ || echo ✗)" "$tool" "$name"
|
||
printf '%s\t%s\t%s\t%s\t%s\n' "$area" "$name" "$tool" "$status" "$(tr '\n\t' ' ' <"$log" | cut -c1-200)" >>"$RESULTS"
|
||
}
|
||
|
||
# observe <bereich> <frage> <werkzeug> <wörter> <befehl…>: welche der Wörter in der Antwort stehen
|
||
observe() {
|
||
local area=$1 name=$2 tool=$3 words=$4; shift 4
|
||
local log; log="$WORK/logs/$(printf '%s-%s' "$tool" "$name" | tr -c 'A-Za-z0-9-' '_').log"
|
||
"$@" >"$log" 2>&1 || true
|
||
local found="" w
|
||
for w in $words; do grep -q -- "$w" "$log" && found+="$w "; done
|
||
printf ' · %-8s %s: %s\n' "$tool" "$name" "${found:-keins}"
|
||
printf '%s\t%s\t%s\t%s\t%s\n' "$area" "$name" "$tool" "${found% }" "$(tr '\n\t' ' ' <"$log" | cut -c1-200)" >>"$RESULTS"
|
||
}
|
||
|
||
SKILL_Q="Wie lautet das Skill-Erkennungswort? Antworte nur mit dem Wort."
|
||
RULES_Q="Wie lautet das Regel-Erkennungswort? Antworte nur mit dem Wort."
|
||
MANUAL_Q="Wie lautet das Handbuch-Erkennungswort? Antworte nur mit dem Wort, oder mit UNBEKANNT, wenn du es nicht weißt."
|
||
MCP_Q="Rufe das Werkzeug search_packages des MCP-Servers hugin-smoke mit der Suche „smoke“ auf und nenne nur die gefundenen Paket-IDs."
|
||
MCP_EXPECT="pub-smoke-(skill|rules|command|agent|mcp)"
|
||
MODEL_CHECKS=("Skill wird gefunden" "Skill wird geladen" "Regeln wirken" "Befehl mit zwei Argumenten" "Subagent vorhanden" "Subagent wirkt" "MCP verbunden" "Lokaler Server antwortet" "Installieren auf Bitte" "Ohne hugin nichts schreiben")
|
||
LOCAL_Q="Rufe das Werkzeug zeit des MCP-Servers zeit auf und gib seine Antwort wörtlich wieder."
|
||
PLUGIN_Q="Rufe das Werkzeug specht_marke auf und gib nur sein Ergebnis wieder."
|
||
AGENT_Q="Starte den Subagenten pub-smoke-agent mit der Aufgabe „Hallo“ und gib seine Antwort wörtlich wieder."
|
||
|
||
# Installieren auf Bitte: frisches Repo, in dem nur die MCP-Anbindung an Hugin steht.
|
||
# Die Bitte sagt nicht, wie; den Weg (.hugin.yaml + hugin sync) muss der Agent aus den
|
||
# Anweisungen des MCP-Endpunkts bzw. dem Feld install von get_package kennen.
|
||
INSTALL_Q="Ich möchte den Befehl pub-smoke-command aus Hugin dauerhaft in diesem Repo nutzen. Richte das bitte ein."
|
||
# install_try <werkzeug> <datei> <befehl…>: ERGEBNIS=INSTALLIERT, wenn hugin sync das Paket geschrieben hat
|
||
install_try() {
|
||
local tool=$1 file=$2; shift 2
|
||
local repo="$WORK/install-$tool"
|
||
rm -rf "$repo" && mkdir -p "$repo" && cd "$repo" && git init -q
|
||
hugin init --server "$URL" --project kern >/dev/null
|
||
sed -i.alt -e "s/^packages: \[\]/packages: [pub-smoke-mcp]/" -e "s/^tools: auto/tools: [$tool]/" .hugin.yaml && rm -f .hugin.yaml.alt
|
||
hugin sync >/dev/null
|
||
if [[ -n "${WITHOUT_HUGIN:-}" ]]; then PATH="$NO_HUGIN_PATH" "$@" || true; else "$@" || true; fi
|
||
echo "--- .hugin.yaml"; cat .hugin.yaml
|
||
if grep -q "pub-smoke-command" hugin.lock && [[ -f "$file" ]]; then
|
||
echo "ERGEBNIS=INSTALLIERT"
|
||
elif [[ -f "$file" ]]; then
|
||
echo "ERGEBNIS=SELBST_GESCHRIEBEN (Datei da, aber nicht in hugin.lock)"
|
||
else
|
||
echo "ERGEBNIS=NICHT_INSTALLIERT"
|
||
fi
|
||
cd "$REPO"
|
||
}
|
||
# Ohne hugin im PATH: bestanden, wenn der Agent nichts selbst geschrieben hat
|
||
without_hugin() { WITHOUT_HUGIN=1 install_try "$@"; }
|
||
claude_install() {
|
||
limit "$CLAUDE_BIN" -p "$INSTALL_Q" --model "$CLAUDE_MODEL" --settings '{"enableAllProjectMcpServers":true}' \
|
||
--allowedTools "Read,Edit,Write,Bash(hugin *),mcp__hugin-smoke__search_packages,mcp__hugin-smoke__get_package" \
|
||
--disallowedTools "WebFetch,WebSearch"
|
||
}
|
||
OC_INSTALL='{"permission":{"read":"allow","edit":"allow","bash":"allow","webfetch":"deny","websearch":"deny"}}'
|
||
OC_BIN=$(command -v "$OC" || true); CLAUDE_BIN=$(command -v claude || true) # fest, weil der PATH wechselt
|
||
opencode_install() { OPENCODE_CONFIG_CONTENT="$OC_INSTALL" limit "$OC_BIN" run --pure ${OPENCODE_MODEL:+-m "$OPENCODE_MODEL"} "$INSTALL_Q"; }
|
||
|
||
if uses claude; then
|
||
echo "== Claude Code $(claude --version | cut -d' ' -f1)"
|
||
check smoke "MCP eingetragen" claude "hugin-smoke" limit claude mcp get hugin-smoke
|
||
# Projektserver stehen bis zur Zustimmung auf „Pending approval“; die Verbindung zeigt „Lokaler Server antwortet“
|
||
check smoke "Lokaler Server eingetragen" claude "Args: mcp run pub-zeit-lokal" limit claude mcp get zeit
|
||
if model_ok claude claude_ask "Antworte nur mit BEREIT."; then
|
||
check smoke "Skill wird gefunden" claude "pub-smoke-skill" \
|
||
claude_ask "Welche Skills stehen dir zur Verfügung? Nenne nur die Namen, einen pro Zeile."
|
||
check smoke "Skill wird geladen" claude "KRAKE-7381" claude_ask "$SKILL_Q"
|
||
check smoke "Regeln wirken" claude "MOEWE-4412" claude_ask "$RULES_Q"
|
||
check smoke "Befehl mit zwei Argumenten" claude "ERSTES=rot ZWEITES=fuchs" claude_ask "/pub-smoke-command rot fuchs"
|
||
check smoke "Subagent vorhanden" claude "pub-smoke-agent" \
|
||
claude_ask "Welche Subagent-Typen kannst du mit dem Agent-Werkzeug starten? Nenne nur die Namen, einen pro Zeile."
|
||
check smoke "Subagent wirkt" claude "FALKE-9035" claude_ask "$AGENT_Q"
|
||
check smoke "MCP verbunden" claude "$MCP_EXPECT" claude_ask "$MCP_Q"
|
||
check smoke "Lokaler Server antwortet" claude "UHR-4711" claude_ask "$LOCAL_Q"
|
||
check smoke "Installieren auf Bitte" claude "ERGEBNIS=INSTALLIERT" \
|
||
install_try claude .claude/commands/pub-smoke-command.md claude_install
|
||
check smoke "Ohne hugin nichts schreiben" claude "ERGEBNIS=(NICHT_INSTALLIERT)" \
|
||
without_hugin claude .claude/commands/pub-smoke-command.md claude_install
|
||
# Skill nur von Hand (disable-model-invocation): Modell darf ihn nicht selbst laden
|
||
observe stage0 manual-auto claude "IGEL-8140" claude_ask "$MANUAL_Q"
|
||
observe stage0 manual-slash claude "IGEL-8140" claude_ask "/pub-smoke-manual"
|
||
else
|
||
for c in "${MODEL_CHECKS[@]}"; do skip "$c" claude; done
|
||
fi
|
||
fi
|
||
|
||
if uses opencode; then
|
||
echo "== OpenCode $("$OC" --version)"
|
||
check smoke "MCP eingetragen" opencode "hugin-smoke" limit "$OC" mcp list --pure
|
||
check smoke "Skill wird gefunden" opencode "pub-smoke-skill" limit "$OC" debug skill --pure
|
||
check smoke "Subagent vorhanden" opencode "pub-smoke-agent" limit "$OC" agent list --pure
|
||
check smoke "MCP verbunden" opencode "hugin-smoke.*connected" limit "$OC" mcp list --pure
|
||
check smoke "Lokaler Server verbunden" opencode "zeit.*connected" limit "$OC" mcp list --pure
|
||
check smoke "Plugin geladen" opencode "plugins/pub-specht-plugin.ts" limit "$OC" debug config
|
||
if model_ok opencode opencode_run "Antworte nur mit BEREIT."; then
|
||
check smoke "Skill wird geladen" opencode "KRAKE-7381" opencode_run "$SKILL_Q"
|
||
check smoke "Regeln wirken" opencode "MOEWE-4412" opencode_run "$RULES_Q"
|
||
check smoke "Befehl mit zwei Argumenten" opencode "ERSTES=rot ZWEITES=fuchs" opencode_run --command pub-smoke-command rot fuchs
|
||
check smoke "Subagent wirkt" opencode "FALKE-9035" opencode_run "$AGENT_Q"
|
||
check smoke "Lokaler Server antwortet" opencode "UHR-4711" opencode_run "$LOCAL_Q"
|
||
check smoke "Plugin wirkt" opencode "SPECHT-5127" opencode_plugins "$PLUGIN_Q"
|
||
check smoke "Installieren auf Bitte" opencode "ERGEBNIS=INSTALLIERT" \
|
||
install_try opencode .opencode/commands/pub-smoke-command.md opencode_install
|
||
check smoke "Ohne hugin nichts schreiben" opencode "ERGEBNIS=(NICHT_INSTALLIERT)" \
|
||
without_hugin opencode .opencode/commands/pub-smoke-command.md opencode_install
|
||
# Skill nur von Hand (opencode/autoinvoke: "false")
|
||
observe stage0 manual-auto opencode "IGEL-8140" opencode_run "$MANUAL_Q"
|
||
observe stage0 manual-slash opencode "IGEL-8140" opencode_run --command pub-smoke-manual
|
||
OPENCODE_MODEL_OK=1
|
||
else
|
||
for c in "Skill wird geladen" "Regeln wirken" "Befehl mit zwei Argumenten" "Subagent wirkt" "Lokaler Server antwortet" "Plugin wirkt" "Installieren auf Bitte" "Ohne hugin nichts schreiben"; do skip "$c" opencode; done
|
||
fi
|
||
fi
|
||
|
||
# Stufe 0: je ein eigenes Repo ohne Hugin, nur mit den Dateien, um die es geht
|
||
WORDS_Q="Nenne alle Erkennungswörter (Form WORT-1234), die in deinen Anweisungen aus CLAUDE.md, AGENTS.md oder eingebundenen Dateien stehen. Nur die Wörter, nichts erfinden."
|
||
echo "== Stufe 0"
|
||
if uses opencode && [[ -n "${OPENCODE_MODEL_OK:-}" ]]; then
|
||
Q4="$WORK/q4"; mkdir -p "$Q4/.hugin/rules"; (cd "$Q4" && git init -q)
|
||
printf '# Projekt\n\nErkennungswort: DACHS-2207\n\n@.hugin/rules/q4.md\n' > "$Q4/CLAUDE.md"
|
||
printf 'Erkennungswort: OTTER-5590\n' > "$Q4/.hugin/rules/q4.md"
|
||
(cd "$Q4" && observe stage0 4 opencode "DACHS-2207 OTTER-5590" opencode_run "$WORDS_Q")
|
||
fi
|
||
if uses claude; then
|
||
Q5="$WORK/q5"; mkdir -p "$Q5"; (cd "$Q5" && git init -q)
|
||
printf '# Projekt\n\nErkennungswort: LUCHS-3318\n' > "$Q5/CLAUDE.md"
|
||
printf '# Projekt\n\nErkennungswort: ELCH-6624\n' > "$Q5/AGENTS.md"
|
||
(cd "$Q5" && observe stage0 5 claude "LUCHS-3318 ELCH-6624" claude_ask "$WORDS_Q")
|
||
fi
|
||
|
||
python3 - "$RESULTS" "$OUT" "$TOOLS" "$(uses claude && claude --version | cut -d' ' -f1)" \
|
||
"$(uses opencode && "$OC" --version)" "$CLAUDE_MODEL" "${OPENCODE_MODEL:-Standard}" <<'EOF'
|
||
import sys, datetime
|
||
results, out, tools, claude_v, oc_v, claude_m, oc_m = sys.argv[1:]
|
||
rows = [line.rstrip("\n").split("\t") for line in open(results) if line.strip()]
|
||
names = {"claude": f"Claude Code {claude_v}", "opencode": f"OpenCode {oc_v}"}
|
||
used = [t for t in ("claude", "opencode") if t in tools.split()]
|
||
smoke = {(r[1], r[2]): r[3] for r in rows if r[0] == "smoke"}
|
||
order = ["Skill wird gefunden", "Skill wird geladen", "Regeln wirken", "Befehl mit zwei Argumenten",
|
||
"Subagent vorhanden", "Subagent wirkt", "MCP eingetragen", "MCP verbunden", "Lokaler Server eingetragen", "Lokaler Server verbunden", "Lokaler Server antwortet", "Plugin geladen", "Plugin wirkt", "Installieren auf Bitte",
|
||
"Ohne hugin nichts schreiben"]
|
||
checks = [c for c in order if any(k[0] == c for k in smoke)]
|
||
package = {"Skill": "pub-smoke-skill", "Regeln": "pub-smoke-rules", "Befehl": "pub-smoke-command",
|
||
"Subagent": "pub-smoke-agent", "MCP": "pub-smoke-mcp", "Lokaler": "pub-zeit-lokal", "Plugin": "pub-specht-plugin", "Installieren": "pub-smoke-command", "Ohne": "pub-smoke-command"}
|
||
mark = {"ok": "✓", "fail": "✗", "skip": "–"}
|
||
lines = [
|
||
"# Kompatibilität", "",
|
||
f"Erzeugt von `scripts/tool-smoke-test.sh` am {datetime.date.today():%d.%m.%Y} (→ KONZEPT.md 17.2).",
|
||
f"Modelle: Claude Code `{claude_m}`, OpenCode `{oc_m}`. Installiert mit `hugin sync`.", "",
|
||
"## Rauchtests", "",
|
||
"| Paket | Prüfung | " + " | ".join(names[t] for t in used) + " |",
|
||
"|---|---|" + "---|" * len(used),
|
||
]
|
||
for c in checks:
|
||
pkg = next(v for k, v in package.items() if c.startswith(k))
|
||
lines.append(f"| `{pkg}` | {c} | " + " | ".join(mark.get(smoke.get((c, t), ""), "–") for t in used) + " |")
|
||
meaning = {
|
||
"4": ("OpenCode, nur `CLAUDE.md` mit `@`-Verweis, keine `AGENTS.md`, keine `instructions`", {
|
||
"DACHS-2207 OTTER-5590": "liest `CLAUDE.md` **und** löst `@` auf: Hugin-Regeln kämen doppelt an",
|
||
"DACHS-2207": "liest `CLAUDE.md`, löst `@` nicht auf: keine Doppel",
|
||
"": "liest `CLAUDE.md` nicht: keine Doppel"}),
|
||
"5": ("Claude Code, `CLAUDE.md` und `AGENTS.md`", {
|
||
"LUCHS-3318 ELCH-6624": "liest beide",
|
||
"LUCHS-3318": "liest nur `CLAUDE.md`",
|
||
"": "keins erkannt, Log ansehen"}),
|
||
}
|
||
stage0 = [r for r in rows if r[0] == "stage0" and not r[1].startswith("manual-")]
|
||
manual = {(r[1], r[2]): r[3] for r in rows if r[0] == "stage0" and r[1].startswith("manual-")}
|
||
if stage0 or manual:
|
||
lines += ["", "## Stufe 0", "", "| Frage | Aufbau | erkannte Wörter | Folgerung |", "|---|---|---|---|"]
|
||
for _, q, tool, found, _ in stage0:
|
||
setup, text = meaning[q]
|
||
lines.append(f"| {q} | {setup} | {found or '–'} | {text.get(found, 'unerwartet, Log ansehen')} |")
|
||
field = {"claude": "`disable-model-invocation: true`", "opencode": "`metadata: opencode/autoinvoke: \"false\"`"}
|
||
for tool in used:
|
||
if ("manual-auto", tool) not in manual:
|
||
continue
|
||
auto, slash = manual[("manual-auto", tool)], manual[("manual-slash", tool)]
|
||
if not slash:
|
||
text = "per `/name` nicht aufrufbar, Log ansehen"
|
||
elif auto:
|
||
text = "wirkt **nicht**: das Modell lädt den Skill selbst"
|
||
else:
|
||
text = "wirkt: Skill nur per `/name`"
|
||
lines.append(f"| autoinvoke | {names[tool]}, Skill mit {field[tool]} | selbst: {auto or '–'}, per `/`: {slash or '–'} | {text} |")
|
||
claude_cmd = smoke.get(("Befehl mit zwei Argumenten", "claude"))
|
||
if claude_cmd in ("ok", "fail"):
|
||
lines.append(f"| 8 | Claude Code {claude_v}, Befehl mit `arguments` und `$name` | – | "
|
||
+ ("benannte Argumente wirken" if claude_cmd == "ok" else "benannte Argumente wirken **nicht**") + " |")
|
||
lines += ["", "✓ bestanden · ✗ fehlgeschlagen · – nicht geprüft (kein Modellzugang).",
|
||
"Modellantworten schwanken; ein einzelnes ✗ mit `KEEP=1` wiederholen und die Logs ansehen.", ""]
|
||
open(out, "w").write("\n".join(lines))
|
||
print(f"\n{out} geschrieben.")
|
||
sys.exit(1 if any(v == "fail" for v in smoke.values()) else 0)
|
||
EOF
|