#!/usr/bin/env bash # Stufe 0 (→ KONZEPT.md 20): Verhalten von OpenCode prüfen, ohne Modellaufruf. # Nutzt nur `opencode debug …` und kostet keine Tokens. Ergebnisse → STUFE0.md. # # scripts/stage0.sh # mit dem installierten opencode # OPENCODE=opencode2 scripts/stage0.sh set -euo pipefail OC="${OPENCODE:-opencode}" T="$(mktemp -d)" trap 'rm -rf "$T"' EXIT cd "$T" && git init -q mkdir -p .claude/skills/s0-unbekannt .claude/skills/s0-doppelt .opencode/skills/s0-doppelt \ .claude/skills/s0-slash-ohne .claude/skills/s0-slash-false \ .claude/commands .claude/agents .opencode/commands .opencode/agents .hugin/rules printf -- '---\nname: s0-unbekannt\ndescription: Claude-Felder\nallowed-tools: [Read]\npaths: ["**/*.go"]\neffort: high\n---\nx\n' \ > .claude/skills/s0-unbekannt/SKILL.md printf -- '---\nname: s0-doppelt\ndescription: aus .claude/skills\n---\nA\n' > .claude/skills/s0-doppelt/SKILL.md printf -- '---\nname: s0-doppelt\ndescription: aus .opencode/skills\n---\nB\n' > .opencode/skills/s0-doppelt/SKILL.md printf -- '---\nname: s0-slash-ohne\ndescription: ohne metadata\n---\nx\n' > .claude/skills/s0-slash-ohne/SKILL.md printf -- '---\nname: s0-slash-false\ndescription: slash false\nmetadata:\n opencode/slash: "false"\n---\nx\n' \ > .claude/skills/s0-slash-false/SKILL.md printf -- '---\ndescription: aus .claude/commands\n---\nx\n' > .claude/commands/s0-claude-befehl.md printf -- '---\nname: s0-claude-agent\ndescription: aus .claude/agents\n---\nx\n' > .claude/agents/s0-claude-agent.md printf -- '---\ndescription: aus .opencode/commands\n---\nTu $1\n' > .opencode/commands/s0-befehl.md printf -- '---\ndescription: aus .opencode/agents\nmode: subagent\npermission:\n edit: deny\n bash: deny\n webfetch: deny\n---\nx\n' \ > .opencode/agents/s0-agent.md printf '# Regel\n' > .hugin/rules/s0.md cat > opencode.json <<'EOF' { // JSONC: Kommentar und Komma am Ende "instructions": [".hugin/rules/*.md"], "mcp": { "s0": { "type": "remote", "url": "http://127.0.0.1:9/mcp", "enabled": true, "headers": { "Authorization": "Bearer {env:S0_KEY}" } } }, } EOF # Über eine Pipe schneidet opencode lange Ausgaben ab; deshalb erst in Dateien json() { python3 -c 'import json,sys; r=open(sys.argv[1]).read(); print(json.dumps(json.loads(r[min(i for i in (r.find("{"), r.find("[")) if i >= 0):])))' "$1"; } echo "OpenCode: $($OC --version)" $OC debug skill --pure >"$T/.skills" 2>/dev/null S0_KEY=geheim $OC debug config --pure >"$T/.config" 2>/dev/null SKILLS=$(json "$T/.skills") CONFIG=$(json "$T/.config") python3 - "$SKILLS" "$CONFIG" <<'EOF' import json, sys skills = [s for s in json.loads(sys.argv[1]) if s["name"].startswith("s0-")] config = json.loads(sys.argv[2]) names = [s["name"] for s in skills] def report(question, ok, text): print(f"{'✓' if ok else '✗'} {question}: {text}") report("1 unbekannte Felder", "s0-unbekannt" in names, "Skill geladen" if "s0-unbekannt" in names else "Skill fehlt") duplicates = [s for s in skills if s["name"] == "s0-doppelt"] report("2 gleicher Name in .claude und .opencode", len(duplicates) == 1, f"{len(duplicates)} Fassung(en): " + ", ".join(s["description"] for s in duplicates)) commands = list((config.get("command") or {})) agents = list((config.get("agent") or {})) report("3 liest .claude/commands nicht", "s0-claude-befehl" not in commands, f"Befehle: {commands}") report("3 liest .claude/agents nicht", "s0-claude-agent" not in agents, f"Agenten: {[a for a in agents if a.startswith('s0')]}") report("6 JSONC erlaubt", config.get("instructions") == [".hugin/rules/*.md"], f"instructions: {config.get('instructions')}") mcp = (config.get("mcp") or {}).get("s0") or (config.get("mcp") or {}).get("servers", {}).get("s0") # Ab 1.19 zeigt debug config Header nur maskiert; dann belegt der Rauchtest {env:VAR} masked = bool(mcp) and "***" in json.dumps(mcp) report("10 MCP alte Form, {env:VAR}", bool(mcp) and ("geheim" in json.dumps(mcp) or masked), json.dumps(mcp) + (" (Header maskiert, {env:VAR} prüft tool-smoke-test.sh)" if masked else "")) EOF # Slash-Befehle: dieselbe Liste, die das TUI bei „/“ anbietet, liefert der Server unter /command PORT="${PORT:-47123}" $OC serve --pure --port "$PORT" >"$T/.serve" 2>&1 & SERVE=$! trap 'kill $SERVE 2>/dev/null; wait $SERVE 2>/dev/null; rm -rf "$T"' EXIT for _ in $(seq 60); do curl -fsS "http://127.0.0.1:$PORT/command" >"$T/.commands" 2>/dev/null && break; sleep 0.25; done python3 - "$T/.commands" <<'EOF2' import json, sys try: commands = {c["name"]: c.get("source") for c in json.load(open(sys.argv[1]))} except Exception as e: sys.exit(f"✗ 8 Slash-Befehle: /command nicht lesbar ({e})") def report(question, ok, text): print(f"{'✓' if ok else '✗'} {question}: {text}") report("8 Skill ohne metadata ist Slash-Befehl", "s0-slash-ohne" in commands, commands.get("s0-slash-ohne", "fehlt")) # Erwartet (1.19.3): opencode/slash wirkt nicht, auch „false“ blendet nicht aus report("8 opencode/slash: \"false\" wirkt nicht", "s0-slash-false" in commands, "weiter in der Liste" if "s0-slash-false" in commands else "ausgeblendet: opencode/slash wirkt") EOF2 echo echo "Mit Modellaufruf in scripts/tool-smoke-test.sh: 4, 5, 8 (benannte Argumente), opencode/autoinvoke"