hugin/KONZEPT.md
2026-10-27 08:33:21 +01:00

98 KiB
Raw Permalink Blame History

Hugin — Konzept für die Entwicklung

Hugin („Gedanke“) und Munin („Gedächtnis“) sind die beiden Raben Odins. Munin speichert, was Agenten wissen. Hugin legt fest, wie sie arbeiten.

Stand: 19.10.2026 · Status: Stufen 0–4 umgesetzt, Stufe 5 offen (→ 20)


Inhalt

  1. Kurzfassung
  2. Ausgangslage und Problem
  3. Ziele und Nicht-Ziele
  4. Begriffe · Namen im Code
  5. Zielwerkzeuge: Claude Code und OpenCode
  6. Das Quellformat
  7. Die Übersetzer
  8. Prüfung (Linter)
  9. Lebenszyklus und Freigabe
  10. Geltung, Abos, Pflichtpakete, Namen
  11. Verteilung: das Programm hugin
  12. MCP-Server
  13. Weboberfläche
  14. Architektur
  15. Datenmodell
  16. Sicherheit
  17. Kompatibilitätstests
  18. Betrieb
  19. Zusammenspiel mit Munin
  20. Ausbaustufen
  21. Entscheidungen und offene Fragen
  22. Quellen und was neu zu prüfen ist

1. Kurzfassung

Hugin ist ein zentrales Register für alles, was das Verhalten von KI-Agenten steuert: Skills, Hausregeln, Befehle, Subagenten und MCP-Anbindungen.

  • Autoren legen Pakete an. Jede Änderung ist eine neue, unveränderliche Version.
  • Eine zweite Person prüft und gibt frei, bevor eine Version bei den Teams ankommt.
  • Ein Paket wird einmal in einem neutralen Format geschrieben. Hugin übersetzt es für Claude Code und für OpenCode. Beide Werkzeuge werden voll unterstützt, andere nicht.
  • Die Teams holen die Pakete mit dem Kommandozeilenprogramm hugin sync in ihre Repositories. Welche Pakete in welcher Version, steht in einer Datei im Repo.
  • Server in Python (FastAPI, Jinja2, HTMX, hauseigenes CSS-Framework), CLI in Go. Konzepte wie Munin: SQLite oder PostgreSQL, Tenants, Projekte und Keys, Audit, Hilfe-System, Markdown-Export, MCP-Endpunkt. Später in ein anderes FastAPI-Projekt einbettbar (→ 14.3).

2. Ausgangslage und Problem

Teams arbeiten mit Claude Code und OpenCode. Jedes Team schreibt eigene Anweisungen (CLAUDE.md, AGENTS.md), eigene Skills und eigene Subagenten und kopiert sie von Repository zu Repository. Daraus folgt:

Problem Folge
Wildwuchs Fünf Fassungen derselben Anweisung „So nutzt du Munin“; niemand weiß, welche aktuell ist.
Keine Prüfung Ein Skill kann Agenten anweisen, Befehle auszuführen oder Daten zu verschicken. Er ist faktisch Code, wird aber nie reviewt.
Keine Verbesserung von oben Findet ein Team eine bessere Anweisung, erreicht sie die anderen nicht.
Kein Überblick Niemand weiß, welche Skills es gibt, wer sie nutzt und in welcher Fassung.
Zwei Werkzeuge Was für Claude Code geschrieben ist, funktioniert in OpenCode oft nur halb und umgekehrt. Teams pflegen doppelt oder gar nicht.

3. Ziele und Nicht-Ziele

Ziele

  1. Ein Ort für Skills, Regeln, Befehle, Subagenten und MCP-Anbindungen, mit Suche.
  2. Geprüft: Nichts erreicht die Teams ohne Freigabe durch eine zweite Person.
  3. Versioniert und nachvollziehbar: Jede Version ist unveränderlich, mit Prüfsumme, Diff zur Vorgängerversion und Audit, wer was wann freigegeben hat.
  4. Claude Code und OpenCode gleichwertig: Ein Paket, beide Werkzeuge. Die Weboberfläche zeigt für jede Version, was in welchem Werkzeug ankommt.
  5. Einfach für die Teams: Eine kleine Datei im Repo, ein Befehl hugin sync. Eigene Dateien der Teams werden nie angefasst.
  6. Rückkanal: Hugin weiß, welches Projekt welche Version nutzt, und Agenten wie Menschen können Rückmeldung zu einem Paket geben.
  7. Betrieb wie Munin: Wer Munin betreibt, kann Hugin betreiben.

Nicht-Ziele

  • Andere Werkzeuge (Codex, Cursor, Copilot …) werden nicht unterstützt. Die Übersetzer-Architektur ließe es zu, es ist aber ausdrücklich nicht geplant.
  • Keine Ausführung: Hugin führt keine Agenten aus und startet keine Skripte.
  • Kein Marktplatz für draußen: Hugin ist nur intern.
  • Keine persönlichen Pakete: Hugin schreibt nicht in Nutzerordner (~/.claude/, ~/.config/opencode/). Pakete gibt es nur öffentlich oder je Tenant bzw. Projekt.
  • Kein Ersatz für Munin: Wissen („Service X spricht nur IPv4“) gehört nach Munin. Anweisungen („Prüfe vor jedem Deploy die Changelogs“) gehören nach Hugin.
  • Keine Bearbeitung von Hand in den Repos: Was Hugin verwaltet, wird über Hugin geändert. hugin sync überschreibt Änderungen von Hand an verwalteten Dateien, nachdem es gewarnt hat.

4. Begriffe

Begriff Bedeutung
Paket Eine verwaltete Einheit, z. B. der Skill pub-munin-nutzen. Hat einen Besitzer und eine Art.
Art Was das Paket ist: skill, rules, command, subagent, mcp, hook.
Version Ein unveränderlicher Stand eines Pakets mit Semver-Nummer (1.4.0) und Prüfsumme.
Ziel Ein unterstütztes Werkzeug: claude (Claude Code) oder opencode (OpenCode).
Übersetzer Erzeugt aus dem Quellformat die Dateien für ein Ziel.
Kompatibilitätsstufe portable, translated, claude-only, opencode-only (→ Abschnitt 5.2).
Risiko text, tools, scripts, permissions (→ Abschnitt 16). Bestimmt, wie streng die Prüfung ist.
Tenant Abgeschlossener Bereich, z. B. eine Organisationseinheit. Sieht nur seine eigenen Pakete und die öffentlichen. Entspricht einem Workspace in Munin.
Geltung Wer das Paket sieht und abonnieren darf: public, tenant, project.
Kanal stable (Standard) oder test (für Teams, die Neues früh wollen).
Abo Eintrag in .hugin.yaml eines Repos: dieses Paket, dieser Kanal oder Versionsbereich.
Pflichtpaket Ein Paket, das jedes Projekt einer Geltung bekommt und nicht abbestellen kann.
Verwaltete Datei Eine Datei, die hugin sync erzeugt hat und die es pflegt. Erkennbar am Kopfkommentar und an hugin.lock.

4.1 Namen im Code

Das Konzept, die Hilfe und die Weboberfläche sind deutsch. Code, Felder, Werte, Befehle, API-Pfade und Datenbank sind englisch. Die wichtigsten Zuordnungen:

Begriff im Code Begriff im Code
Paket package Freigabe / freigeben approval / approve
Art kind Prüfer, Prüfung reviewer, review
Geltung (öffentlich, Tenant, Projekt) scope (public, tenant, project) Entwurf / einreichen draft / submit
Besitzer owner zurückgeben return
Beschreibung description veraltet / reaktivieren deprecated / reactivate
Änderung changes Kanal stabil / test channel stable / test
Ziele targets Risiko (Werkzeuge, Skripte, Rechte) risk (tools, scripts, permissions)
Zusatz extra Kompatibilität (portabel, übersetzt) compatibility (portable, translated)
Argumente arguments Pflichtpaket mandatory
Fähigkeit (lesen, schreiben) capability (read, write) Abo / abbestellt subscription / unsubscribed
Modell (schnell, gründlich, erben) model (fast, thorough, inherit) Auslieferung delivery
Kopfzeilen headers Rückmeldung feedback
Regeln, Befehl rules, command Untergrenze floor
Werkzeug tool Befund finding
Zugriff access Abgleich reconcile

CLI: hugin init, sync, check, status, login, new, validate, try, upload, search, import.


5. Zielwerkzeuge: Claude Code und OpenCode

5.1 Grundentscheidung: Wer ist der Standard?

Die naheliegende Idee wäre: „OpenCode ist Standard, Claude Code liest das mit.“ Es ist aber umgekehrt. OpenCode liest viele Claude-Formate mit (.claude/skills/, CLAUDE.md als Ersatz für AGENTS.md, ~/.claude/skills/). Claude Code liest keine OpenCode-Ordner (.opencode/…). Würde Hugin nur OpenCode-Dateien schreiben, sähe Claude Code die meisten davon nicht.

Deshalb gilt in Hugin:

Frage Entscheidung
Woran richtet sich die Strenge? An der jeweils strengeren Regel; das ist meist OpenCode, z. B. bei Namen und Feldern. Was diese Regeln erfüllt, funktioniert in beiden.
Wo liegen Skills? In .claude/skills/, weil beide Werkzeuge diesen Ort lesen. Eine Datei, kein Doppel. Nutzt ein Repo nur OpenCode, in .opencode/skills/ (→ 7.1).
Wo liegt der Rest? Je Werkzeug am eigenen Ort, erzeugt von den Übersetzern.
Was ist mit Zusatzfunktionen von Claude Code? Erlaubt, aber nur als ausdrücklicher Zusatz für das Ziel claude (→ Abschnitt 6.3). Der Kern eines Pakets bleibt portabel.

Das schränkt kaum ein. Man verzichtet im Kern nur auf die Komfortfelder von Claude Code (allowed-tools, context: fork, model im Skill …). Wer sie braucht, setzt sie im Block targets.claude. Das Paket bleibt dann für OpenCode nutzbar, nur ohne diesen Komfort.

5.2 Kompatibilitätsstufen

Stufe Bedeutung Beispiel
portable Eine erzeugte Datei, beide Werkzeuge lesen sie. Skill nur mit name und description
translated Hugin erzeugt je Werkzeug eine eigene Fassung. Subagent, Befehl mit Argumenten, MCP-Eintrag
claude-only Nutzt etwas, das es in OpenCode nicht gibt. Claude-Hook in settings.json
opencode-only Nutzt etwas, das es in Claude Code nicht gibt. OpenCode-Plugin in TypeScript

Hugin berechnet die Stufe automatisch (→ Abschnitt 8) und zeigt sie im Katalog. Ziel: fast alles portable oder translated; …-only ist die begründete Ausnahme.

5.3 Abbildung je Art

Skills

Claude Code OpenCode
Ort im Projekt .claude/skills/<name>/SKILL.md .opencode/skills/, liest auch .claude/skills/ und .agents/skills/
Ort für den Nutzer ~/.claude/skills/ ~/.config/opencode/skills/, liest auch ~/.claude/skills/
Suche im Projekt auch verschachtelt <unterordner>/.claude/skills/ wandert vom Arbeitsordner nach oben bis zur Git-Wurzel
Pflichtfelder nur description empfohlen, name wird sonst der Ordnername name und description
Name großzügig ^[a-z0-9]+(-[a-z0-9]+)*$, 1–64 Zeichen, gleich dem Ordnernamen
Beschreibung bis 1.536 Zeichen (mit when_to_use) 1–1.024 Zeichen
Zusatzfelder viele (allowed-tools, disable-model-invocation, user-invocable, context, agent, model, effort, paths, hooks, arguments, argument-hint, shell …) nur license, compatibility, metadata; Eigenes unter metadata (opencode/slash, opencode/autoinvoke wirken in OpenCode 1 nicht, → STUFE0.md)
Unbekannte Felder werden still ignoriert werden still ignoriert (Stufe 0)
Laden automatisch nach Beschreibung oder per /name; nur von Hand mit disable-model-invocation über das Werkzeug skill, nach Beschreibung, oder per /name; immer beides (Stufe 0)
Rechte allowed-tools im Skill permission.skill in opencode.json

Für Hugin: Skills sind die einfachste Art. Mit dem gemeinsamen Kern (name, description, license, compatibility, metadata) sind sie portable.

Regeln (Hausanweisungen)

Claude Code OpenCode
Hauptdatei CLAUDE.md AGENTS.md, sonst CLAUDE.md als Ersatz
Weitere Dateien einbinden @path/to/file.md in der CLAUDE.md instructions in opencode.json: Pfade, Globs, auch URLs
AGENTS.md ab 2.1.277, aber nur wenn es keine CLAUDE.md gibt nativ; OpenCode 2 liest nur noch AGENTS.md, nicht mehr CLAUDE.md

Für Hugin: Hugin schreibt Regeln in eigene Dateien unter .hugin/rules/ und bindet sie ein, ohne die Inhalte der Teams anzufassen:

  • in der CLAUDE.md über einen verwalteten Block mit @-Verweisen,
  • in opencode.json über einen Eintrag .hugin/rules/*.md in instructions.

Einzelheiten → Abschnitt 7.2.

Befehle (Slash-Commands)

Claude Code OpenCode
Ort .claude/commands/<name>.md (weiter unterstützt) oder als Skill .opencode/commands/<name>.md
Felder wie bei Skills, außer name und paths description, agent, subtask, model
Alle Argumente $ARGUMENTS $ARGUMENTS
Einzelne Argumente $0, $1 … ab 0, oder benannt über arguments + $name $1, $2 … ab 1
Shell-Ausgabe einfügen !`befehl` !`befehl`
Datei einfügen @path @path

Stolperfalle: Derselbe Text mit $1 meint in Claude Code das zweite, in OpenCode das erste Argument. Hugin erlaubt im Quellformat deshalb nur $ARGUMENTS oder benannte Argumente, die der Übersetzer umschreibt.

Subagenten

Claude Code OpenCode
Ort .claude/agents/*.md .opencode/agents/*.md
Name Feld name Dateiname
Pflicht name, description description
Werkzeuge tools (Erlaubtliste), disallowedTools permission: edit, bash, webfetch … je allow, ask, deny (tools ist veraltet)
Modell sonnet, opus, haiku, fable, volle ID oder inherit provider/model-id
Art immer Subagent primary, subagent, all
Eigenes permissionMode, skills, mcpServers, memory, isolation, effort … temperature, steps, hidden, color, top_p …

Für Hugin: Subagenten sind immer translated. Das Quellformat beschreibt sie neutral über Fähigkeitsklassen und Modellklassen (→ Abschnitt 7.4).

MCP-Anbindungen

Claude Code OpenCode
Datei .mcp.json opencode.json, Schlüssel mcp
Entfernter Server "type": "http", "url": …, "headers": {…} "type": "remote", "url": …, "headers": {…}
Umgebungsvariablen ${VAR} {env:VAR}
MCP-Werkzeuge ja ja
MCP-Prompts und Ressourcen ja in der Doku nicht erwähnt

Für Hugin: MCP-Pakete sind translated. Keys stehen nie im Paket, nur der Name der Umgebungsvariablen. Hugin verlässt sich nur auf MCP-Werkzeuge.

Hooks, Plugins, Rechte

Claude Code OpenCode
Mechanismus Hooks als Shell-Befehle in .claude/settings.json Plugins in JS/TS (.opencode/plugins/ oder npm), Ereignisse wie tool.execute.before
Rechte permissions in .claude/settings.json permission in opencode.json

Für Hugin: grundverschieden, also nicht übersetzbar. Hooks und Plugins gibt es nur als claude-only- bzw. opencode-only-Pakete. Zwei Pakete, die dasselbe tun, lassen sich als Paar verknüpfen (→ Abschnitt 6.5). Rechte verwaltet Hugin in der ersten Fassung gar nicht; sie bleiben Sache der Teams.

5.4 Werkzeugversionen als Untergrenze

Beide Werkzeuge entwickeln sich schnell. Viele Funktionen, auf die Hugin baut, gibt es erst ab einer bestimmten Version. Beispiele:

  • dass OpenCode .claude/skills/ mitliest,
  • die metadata-Schlüssel opencode/slash und opencode/autoinvoke,
  • benannte Argumente (arguments) in Claude Code.

Hat ein Entwickler eine ältere Version, kommt der Skill zwar an, wird aber nicht gefunden oder verhält sich anders. Das ist ein stiller Fehler, der schwer zu finden ist.

Untergrenze heißt: Hugin legt fest, welche Version mindestens installiert sein muss, z. B. „Claude Code ≥ 2.x, OpenCode ≥ 1.y“.

  • Die Untergrenze steht in der Hugin-Konfiguration; der Plattform-Admin pflegt sie.
  • hugin sync und hugin check lesen die installierten Versionen (claude --version, opencode --version) und warnen, wenn eine zu alt ist. Mit tool_check: strict in .hugin.yaml (oder --strict) bricht sync stattdessen ab.
  • Ein Paket kann eine höhere Untergrenze verlangen, wenn es eine neuere Funktion nutzt (Feld min_version im Manifest).
  • Der Linter kennt eine Tabelle „Feld oder Funktion → ab Version“ und setzt die Untergrenze eines Pakets automatisch.
  • Die Untergrenze wird nur angehoben, wenn Hugin eine neue Funktion wirklich braucht.

Die konkreten Versionsnummern stehen in 5.5.

5.5 Untergrenzen (Stand 18.10.2026)

Ermittelt aus dem Changelog von Claude Code und den Release-Notes von OpenCode. Aktuell: Claude Code 2.1.302, OpenCode 1.19.3 und OpenCode 2.

Claude Code

Funktion, die Hugin nutzt ab
Skills (.claude/skills/) 2.0.20
disallowedTools in Subagenten 2.0.30
Skills in Unterordnern (<unterordner>/.claude/skills/) 2.1.6
effort in Skills und Befehlen 2.1.80
paths als YAML-Liste 2.1.84
Beschreibung bis 1.536 Zeichen 2.1.105
volle Modell-IDs in Subagenten (model: claude-…) 2.1.74
benannte Argumente (arguments, $name) nicht im Changelog; wirken in 2.1.303 (Stufe 0), ältere Versionen ungeprüft
AGENTS.md wird gelesen, wenn es keine CLAUDE.md gibt 2.1.277 (für Hugin nicht nötig)

Untergrenze Claude Code: 2.1.105, vorläufig. Sie steigt, falls die benannten Argumente später kamen. Prüfen: ältere Version installieren und TOOLS=claude scripts/tool-smoke-test.sh.

OpenCode 1

Funktion, die Hugin nutzt ab
Kopfzeilen für MCP-Server 0.3.18 (zuverlässig ab 1.0.138)
Agent Skills 1.0.186
liest .claude/skills/ 1.0.208
permission statt tools in Agenten 1.1.1
Claude-Kompatibilität abschaltbar 1.1.7
liest .agents/skills/ 1.1.50
Frontmatter mit CRLF (Windows) 1.2.11
metadata-Schlüssel opencode/slash, opencode/autoinvoke wirken nicht (1.19.3, Stufe 0); Skills sind immer per /name aufrufbar

Untergrenze OpenCode 1: 1.2.11, vorläufig. Die Windows-Korrektur ist der Grund.

OpenCode 2 (seit 11.10.2026, Wechsel mit opencode upgrade --major; beide Fassungen heißen opencode)

Thema Auswirkung auf Hugin
Skills liest weiter .claude/skills/ und .agents/skills/; name ist nur noch Anzeige, die ID kommt aus dem Pfad; opencode/autoinvoke und disable-model-invocation werden verstanden, opencode/slash ist nicht dokumentiert
CLAUDE.md wird nicht mehr gelesen, nur AGENTS.md. Für Hugin unkritisch, weil Regeln über instructions eingebunden werden (→ 7.2)
instructions in opencode.json unverändert
Agenten und Befehle neue Felder (permissions als geordnete Liste, bash → shell, subtask → subagent); alte Frontmatter wird automatisch übersetzt
MCP Server stehen unter mcp.servers, enabled wird zu disabled. Ob die alte Form noch gelesen wird, ist offen → Stufe 0
Plugins V1-Plugins laufen nicht. Betrifft plugin-Pakete (Stufe 5d) und später hook-Pakete; ungeprüft

Folge: Hugin unterstützt vorerst OpenCode 1 und 2. Die Übersetzer schreiben Agenten und Befehle im Format von OpenCode 1, das beide verstehen. Für MCP (und später Plugins) braucht es vielleicht eine eigene Fassung für OpenCode 2; das ist ungeprüft, solange OpenCode 2 nicht getestet ist (STUFE0.md, Frage 10). Das CLI meldet dafür die erkannte Hauptversion (opencode@1 oder opencode@2) an /bundle.


6. Das Quellformat

Ein Paket ist ein Ordner. Darin liegen das Manifest hugin.yaml und der Inhalt. So kann ein Autor ein Paket lokal mit seinem Werkzeug entwickeln und dann hochladen.

6.1 Ordnerstruktur

pub-munin-nutzen/
├── hugin.yaml          # Manifest: wer, was, welche Version, welche Ziele
├── SKILL.md            # Inhalt (bei skill); Frontmatter erzeugt Hugin
├── referenz/
│   └── beispiele.md    # Hilfsdateien, werden mitverteilt
└── skripte/
    └── pruefen.sh      # Skripte: Risiko „skripte“, strengere Prüfung

Je Art heißt die Inhaltsdatei anders:

Art Inhaltsdatei Bemerkung
skill SKILL.md plus beliebige Hilfsdateien
rules RULES.md reiner Text
command COMMAND.md Prompt-Vorlage mit Platzhaltern
subagent AGENT.md Systemprompt des Subagenten
mcp keine alles steht im Manifest
hook claude/… oder opencode/… Inhalt je Werkzeug, nicht übersetzt

In der Inhaltsdatei steht keine Frontmatter. Alle Angaben stehen im Manifest, die Übersetzer erzeugen daraus die Frontmatter je Werkzeug. So gibt es genau eine Stelle für Metadaten.

6.2 Das Manifest

# hugin.yaml
id: pub-munin-nutzen          # = Name in beiden Werkzeugen, Regeln → 8
kind: skill
version: 1.4.0
owner: team-plattform        # Projekt, das das Paket pflegt
scope: public            # public | tenant | project
description: >
  Wann und wie du Munin nutzt: vor neuer Arbeit recall, nach Erkenntnissen
  remember. Nutze diesen Skill immer, wenn du in einem Projekt mit Munin arbeitest.
license: intern                  # optional
min_version:                 # optional, sonst die allgemeine Untergrenze
  claude: ""
  opencode: ""
changes: >                    # Pflicht ab Version 2: was ist neu?
  Hinweis auf das neue Werkzeug „related“ ergänzt.
targets: [claude, opencode]       # Standard: beide

Hugin setzt selbst, der Autor setzt sie nicht:

  • risk (vom Linter ermittelt, → 16)
  • compatibility (vom Linter ermittelt, → 5.2)
  • checksum
  • Status, Freigabe, Kanal

6.3 Zusätze je Werkzeug

Was nur ein Werkzeug kann, steht in einem eigenen Block. Der Kern bleibt portabel.

extra:
  claude:
    allowed-tools: [mcp__munin__recall, mcp__munin__remember]
    paths: ["**/*.go"]

In OpenCode ist jeder Skill auch per /name aufrufbar; opencode/slash ist nicht nötig. Einen Skill nur von Hand aufrufbar zu machen, geht in OpenCode 1 nicht: opencode/autoinvoke und disable-model-invocation werden übergangen (geprüft mit 1.19.3, → STUFE0.md). In Claude Code wirkt disable-model-invocation: true.

  • Der Linter kennt für jedes Werkzeug die erlaubten Felder. Unbekannte lehnt er ab.
  • Zusätze können die Kompatibilitätsstufe ändern. Beispiel: context: fork bei Claude Code verlangt eine eigene Fassung je Werkzeug → translated.

6.4 Beispiele je Art

Befehl mit benannten Argumenten

id: pub-release-notiz
kind: command
version: 1.0.0
owner: team-plattform
scope: public
description: Schreibt eine Release-Notiz für eine Version.
arguments: [version, zielgruppe]   # benannt, Reihenfolge = Position
<!-- COMMAND.md -->
Schreibe eine Release-Notiz für Version {{version}}, gedacht für {{zielgruppe}}.
Lies dazu die Commits seit dem letzten Tag:
!`git log --oneline $(git describe --tags --abbrev=0)..HEAD`

{{name}} ist der neutrale Platzhalter von Hugin. Daneben ist nur $ARGUMENTS erlaubt.

Subagent

id: pub-sicherheits-review
kind: subagent
version: 2.1.0
owner: team-sicherheit
scope: public
description: >
  Prüft Änderungen auf Sicherheitsprobleme. Nach Änderungen an
  Authentifizierung, Eingabeverarbeitung oder Kryptografie einsetzen.
capability: read            # read | write | shell | web; kombinierbar als Liste
model: thorough           # fast | standard | thorough | inherit
extra:
  claude:
    effort: high
  opencode:
    mode: subagent
    temperature: 0.1

MCP-Anbindung

id: pub-munin-anbindung
kind: mcp
version: 1.0.0
owner: team-plattform
scope: public
description: Bindet Munin als MCP-Server an.
server:
  name: munin
  url: https://munin.intern/mcp
  headers:
    Authorization: "Bearer {{env:MUNIN_KEY}}"   # neutral, nie ein echter Key

6.5 Paare

Zwei Pakete, die dasselbe für verschiedene Werkzeuge tun, typischerweise Hooks:

id: pub-prod-schutz-claude
kind: hook
targets: [claude]
pair: pub-prod-schutz-opencode

Hugin zeigt Paare zusammen an, warnt, wenn ein Projekt nur eine Hälfte abonniert hat und beide Werkzeuge nutzt, und verlangt bei einer neuen Version der einen Hälfte eine Bestätigung, dass die andere noch passt.


7. Die Übersetzer

Ein Übersetzer je Ziel und Art. Ein Übersetzer ist eine reine Funktion: Version + Hugin-Konfiguration → Liste von (Pfad, Inhalt). Er schreibt nichts selbst. Das hat zwei Vorteile:

  • Es gibt sie nur einmal, im Server (→ 14.2). Das CLI wendet nur ihr Ergebnis an, die Weboberfläche zeigt damit vorher, was im Repo ankommt.
  • Übersetzer sind einfach mit Tabellentests zu prüfen: Eingabe → erwartete Dateien.

7.1 Skills

  • portable: eine Datei .claude/skills/<id>/SKILL.md mit Frontmatter aus name, description, license, metadata, dazu die Hilfsdateien. Beide Werkzeuge lesen sie.
  • Mit Zusätzen für Claude: OpenCode übergeht unbekannte Felder wie allowed-tools still (Stufe 0, Frage 1). Die Felder kommen mit in dieselbe Datei, die Stufe bleibt portable. Folge: Felder, die das Verhalten ändern, wirken nur in Claude Code, etwa disable-model-invocation (Linter-Hinweis H023).
  • Ausnahme OPENCODE_DISABLE_CLAUDE_CODE oder …_SKILLS: Hat ein Team den Claude-Support in OpenCode abgeschaltet, liest OpenCode .claude/skills/ nicht. Dafür gibt es in .hugin.yaml die Einstellung opencode_reads_claude: false. Hugin schreibt die Skills dann zusätzlich nach .opencode/skills/.
  • Nur OpenCode (tools ohne claude): Skills nur nach .opencode/skills/, nichts unter .claude/. Der Server leitet das aus den Werkzeugen der Anfrage ab. Grund: .opencode/skills/ ist der eigene Ort von OpenCode und hängt nicht davon ab, ob der Claude-Support an ist; ein .claude/-Ordner in einem Repo ohne Claude Code verwirrt nur. Unter OpenCode 1.19.3 verdeckte zudem ein globaler Skill (~/.config/opencode/skills/) einen gleichnamigen aus .claude/skills/ im Repo; ab 1.19.4 gewinnt an beiden Orten der Skill im Repo (geprüft mit opencode debug skill).

7.2 Regeln

Erzeugte Dateien:

.hugin/rules/pub-hausregeln.md
.hugin/rules/team-alpha-go-stil.md

Einbindung für Claude Code: Hugin pflegt in der CLAUDE.md einen verwalteten Block und legt die Datei an, falls es sie nicht gibt:

<!-- hugin:begin — von hugin sync verwaltet, nicht von Hand ändern -->
@.hugin/rules/pub-hausregeln.md
@.hugin/rules/team-alpha-go-stil.md
<!-- hugin:end -->

Einbindung für OpenCode: Hugin ergänzt in opencode.json den Eintrag ".hugin/rules/*.md" in instructions, falls er fehlt. Andere Einträge bleiben unberührt.

Ergebnisse aus Stufe 0 (STUFE0.md):

  • OpenCode liest bei fehlender AGENTS.md zwar die CLAUDE.md, löst deren @-Verweise aber nicht auf. Die Regeln kommen also nicht doppelt an; eine eigene AGENTS.md ist nicht nötig.
  • opencode.json darf Kommentare (JSONC) enthalten. Das CLI erhält sie beim Ändern.

7.3 Befehle

Claude Code OpenCode
Datei .claude/commands/<id>.md .opencode/commands/<id>.md
{{name}} wird zu $name, dazu arguments: [...] in der Frontmatter $<position ab 1>
$ARGUMENTS bleibt bleibt

Beispiel {{version}} (erstes Argument): Claude $version, OpenCode $1.

.claude/commands/ statt eines Skills, weil OpenCode Skills aus .claude/skills/ mitliest. Ein Befehl als Skill käme dort als zusätzlicher, vom Modell automatisch aufrufbarer Skill an. Geprüft (STUFE0.md): OpenCode 1 liest .claude/commands/ nicht.

7.4 Subagenten

Fähigkeitsklassen:

Klasse Claude Code (tools) OpenCode (permission)
read Read, Grep, Glob edit: deny, bash: deny, webfetch: deny, websearch: deny
write + Edit, Write edit: allow
shell + Bash bash: ask (bewusst nicht allow)
web + WebFetch, WebSearch webfetch: allow, websearch: allow

Die Werkzeug- und Rechtenamen sind in Stufe 0 geprüft und stehen als Tabelle im Code (TOOLS und PERMISSIONS in den Übersetzern). Ändern sie sich, ändert man nur diese Tabelle.

Modellklassen: Der Plattform-Admin legt die Zuordnung in der Hugin-Konfiguration fest:

models:
  claude:
    fast: haiku
    standard: sonnet
    thorough: opus
  opencode:
    fast: anthropic/claude-haiku-5-5
    standard: anthropic/claude-sonnet-5-5
    thorough: anthropic/claude-opus-5-5

inherit wird in Claude Code zu model: inherit, in OpenCode fehlt model einfach. Eine neue Modellgeneration bedeutet: Zuordnung ändern, hugin sync aufrufen, fertig. An den Paketen ändert sich nichts.

Erzeugte Dateien: .claude/agents/<id>.md mit name, description, tools, model und den Zusätzen; .opencode/agents/<id>.md mit description, mode (Standard subagent), permission, model und den Zusätzen. Geprüft (STUFE0.md): OpenCode 1 liest .claude/agents/ nicht mit; es gibt keine Doppel.

7.5 MCP-Anbindungen

  • Claude Code: Hugin ergänzt in .mcp.json unter mcpServers.<name> den Eintrag "type": "http", url, headers. Aus {{env:VAR}} wird ${VAR}.
  • OpenCode: Hugin ergänzt in opencode.json unter mcp.<name> den Eintrag "type": "remote", url, headers, "enabled": true. Aus {{env:VAR}} wird {env:VAR}.
  • Hugin ändert nur Schlüssel, die es selbst angelegt hat (vermerkt in hugin.lock). Gibt es schon einen gleichnamigen Server, den Hugin nicht angelegt hat, bricht sync mit einer klaren Meldung ab, statt ihn zu überschreiben.

7.6 Hooks

Keine Übersetzung. Der Inhalt aus claude/ bzw. opencode/ wird übernommen:

  • Claude Code: Hook-Einträge in .claude/settings.json, nur in Schlüsseln, die Hugin verwaltet; dazu das Skript unter .hugin/hooks/<id>/.
  • OpenCode: Plugin-Datei unter .opencode/plugins/<id>.ts.

Hooks haben immer mindestens das Risiko scripts (→ 16).


8. Prüfung (Linter)

Der Linter läuft beim Hochladen jeder Version auf dem Server und lokal mit hugin validate. Er liefert Fehler (Version wird nicht angenommen) und Hinweise. Jede Regel hat eine Kennung, damit Meldungen und Doku zusammenpassen.

Kennung Prüft Stufe
H001 id passt auf ^[a-z0-9]+(-[a-z0-9]+)*$, 1–64 Zeichen Fehler
H002 id beginnt mit dem Präfix der Geltung (→ 10.4) Fehler
H003 description 1–1.024 Zeichen, nicht mehr der Platzhalter aus Vorlage oder Import Fehler
H004 version ist Semver und größer als die letzte Fehler
H005 changes ist ab der zweiten Version gesetzt Fehler
H006 die Inhaltsdatei der Art ist vorhanden (SKILL.md, RULES.md …) Fehler
H007 die Inhaltsdatei hat keine Frontmatter Fehler
H008 Art hook erst ab Stufe 5 Fehler
H010 keine nummerierten Platzhalter ($0, $1 …) im Inhalt Fehler
H011 alle {{name}} sind in arguments deklariert und umgekehrt Fehler
H012 keine werkzeugspezifischen Variablen im Inhalt (${CLAUDE_SKILL_DIR} …) Fehler
H020 Felder in extra.claude und extra.opencode kennt das Werkzeug für diese Art Fehler
H021 Werkzeugnamen im Text (Read, Bash, read, bash …) Hinweis: neutral formulieren, z. B. „lies die Datei“ statt „nutze Read“
H022 Verweise auf Hilfsdateien sind relativ und existieren Fehler
H023 Felder ohne Wirkung in OpenCode 1: „nur von Hand“ (disable-model-invocation, opencode/autoinvoke) und opencode/slash (→ STUFE0.md) Hinweis
H024 auffällige Anweisungen an den Agenten, z. B. „ignoriere vorherige Anweisungen“ (Prompt-Injection, → 16.2) Hinweis
H025 unsichtbare Unicode-Zeichen (Zero-Width, Bidi, Tag-Zeichen): Prüfer sehen sie nicht, das Modell schon Fehler
H030 Skripte: Typ erkannt (sh, ps1, py …) Hinweis bei nur einem Betriebssystem
H031 kein Geheimnis im Paket (Muster für Keys, Passwörter, Tokens) Fehler
H032 {{env:…}} statt echter Werte in MCP-Kopfzeilen Fehler
H033 ein mcp-Paket hat einen Block server Fehler
H034 capability eines Subagenten: nur read, write, shell, web Fehler
H035 model eines Subagenten: nur fast, standard, thorough, inherit Fehler
H040 Risiko ermitteln (→ 16) –
H041 Kompatibilitätsstufe ermitteln (→ 5.2) –
H042 Mindestversion aus genutzten Feldern ermitteln (→ 5.4); als Hinweis gemeldet, in der Version gespeichert und im Bundle als Hinweis ausgeliefert Hinweis
H043 Risiko scripts oder permissions nur mit persönlichem Key (hp_…, → 14.5) Fehler
H050 Paar: die andere Hälfte existiert und hat die passende Gegenrichtung Fehler

Die Kompatibilitätsstufe wird nicht geraten. Die Übersetzer laufen probehalber: Kommt für beide Ziele dieselbe Datei am selben Ort heraus, ist das Paket portable.


9. Lebenszyklus und Freigabe

9.1 Status einer Version

Entwurf ──einreichen──▶ in Prüfung ──freigeben──▶ freigegeben ──▶ veraltet
   ▲                        │
   └───────zurückgeben──────┘ (mit Kommentar)
  • Entwurf: Nur Autor und Besitzer sehen ihn. Er ist änderbar, bekommt aber bei jeder Änderung eine neue Prüfsumme.
  • In Prüfung: Ab hier unveränderlich. Prüfer sehen Diff, Vorschau je Werkzeug, Linter-Ergebnis und Risiko.
  • Freigegeben: Erreicht die Teams beim nächsten hugin sync.
  • Veraltet: Bleibt abrufbar, damit hugin.lock immer funktioniert. sync meldet Veraltetes und schlägt die Nachfolgeversion vor.
  • Daneben kann ein Paket insgesamt veraltet sein, mit Hinweis auf einen Nachfolger.

9.2 Wer darf freigeben

Risiko Freigaben Wer
text 1 ein Prüfer der Geltung, nicht der Autor
tools 1 wie oben
scripts 2 zwei Prüfer, keiner davon der Autor
permissions 2 wie oben, einer davon Admin der Geltung

Öffentliche Pakete gibt ein Prüfer frei, den der Plattform-Admin dafür benannt hat.

Voraussetzung: Für das Vier-Augen-Prinzip muss Hugin Personen unterscheiden können, nicht nur Projekte (→ 14.5). Bis Stufe 4 gilt eine vereinfachte Regel: eine Freigabe mit einem Prüfer-Key, der vom Autor-Key getrennt vergeben ist.

9.3 Versionen und Kanäle

  • Semver:
    • Major: Argumente, Name oder Verhalten ändern sich so, dass Nutzer etwas anpassen müssen.
    • Minor: Ergänzungen.
    • Patch: Tippfehler und Klarstellungen.
  • Kanal stable: neueste freigegebene Version.
  • Kanal test: Eine freigegebene Version kann als test markiert werden. Sie erreicht nur Abos auf @test und wird später auf stable hochgestuft.
  • Abos auf Versionsbereiche: @^1 (alles 1.x), @1.4.0 (fest). Bei @stable gilt: Major-Sprünge kommen nicht automatisch. sync meldet sie, der Nutzer bestätigt mit hugin sync --major.

10. Geltung, Abos, Pflichtpakete, Namen

10.1 Geltung

Zwei Ebenen: eine öffentliche für alle und darunter die Tenants, die voneinander nichts sehen. Innerhalb eines Tenants kann ein Paket zusätzlich auf ein Projekt beschränkt sein.

Geltung Wer sieht und abonniert Wer pflegt
public alle Tenants Plattform-Team und benannte Autoren
tenant alle Projekte dieses Tenants, sonst niemand Projekte des Tenants
project nur dieses Projekt das Projekt selbst

Ein Tenant sieht also genau: die öffentlichen Pakete und seine eigenen. Das gilt für alle Zugänge gleich: Weboberfläche, API, /bundle, MCP-Suche und Export (→ 15.1). Ein Paket kann nicht für ausgewählte Tenants freigegeben werden; wer das braucht, legt es im jeweiligen Tenant an oder macht es öffentlich.

10.2 Abos

Abos stehen im Repository (.hugin.yaml, → 11.2), nicht auf dem Server. Damit sind sie versioniert und im Code-Review sichtbar. Der Server erfährt sie über die Nutzungsmeldung von sync.

10.3 Pflichtpakete

Plattform-Admin (für öffentliche Pakete, dann in allen Tenants) und Tenant-Admin (für seinen Tenant) können Pakete als Pflicht markieren. hugin sync holt sie immer, auch wenn sie in .hugin.yaml fehlen, und verweigert ein Abbestellen. Gedacht für wenige Dinge, etwa Sicherheitsregeln und die Munin-Anbindung.

10.4 Namensräume

Weder Claude Code noch OpenCode kennen außerhalb von Plugins Namensräume für Skills und Agenten. Feste Präfixe verhindern Kollisionen mit den eigenen Dateien der Teams:

Geltung Präfix Beispiel
public pub- pub-munin-nutzen
tenant <tenant-slug>- vertrieb-angebot-pruefen
project <projekt-slug>- shop-api-release

Tenant-Slugs dürfen nicht pub, hugin, admin oder api heißen; das wird beim Anlegen eines Tenants geprüft.

Das Präfix gehört zur id und zählt zu den 64 Zeichen. Tenant- und Projekt-Slugs sollten deshalb kurz sein.


11. Verteilung: das Programm hugin

Ein einzelnes Go-Programm ohne Abhängigkeiten für macOS, Linux und Windows. Es ist für zwei Gruppen gedacht: Teams (abonnieren, abgleichen) und Autoren (entwickeln, prüfen, hochladen).

11.1 Befehle

Befehl Für Tut
hugin init --server … --project … Teams legt .hugin.yaml an, mit tools: auto (Werkzeuge werden bei jedem sync an den Dateien im Repo erkannt)
hugin sync Teams holt die abonnierten und die Pflichtpakete, übersetzt, schreibt, aktualisiert hugin.lock
hugin sync --major Teams erlaubt Major-Sprünge
hugin check Teams ändert nichts; meldet neue Versionen, veraltete Pakete, Handänderungen an verwalteten Dateien, zu alte Werkzeugversionen. Rückgabewert ≠ 0 bei Befund, gut für Skripte
hugin status Teams listet Abos, installierte Versionen und Kompatibilität
hugin search [text] [--kind …] beide sucht im Katalog in ID und Beschreibung
hugin new <art> <id> Autoren legt einen Paketordner mit Manifest-Gerüst an
hugin validate [ordner] Autoren schickt den Ordner an den Server zum Prüfen (ohne Speichern); Linter-Ergebnis und Vorschau der erzeugten Dateien je Werkzeug
hugin try [ordner] Autoren installiert den Entwurf in das aktuelle Repo, ohne hugin.lock zu ändern, zum Ausprobieren mit Claude Code und OpenCode. Führt .hugin/trial.lock; hugin try --remove räumt wieder auf
hugin upload [ordner] Autoren lädt als Entwurf hoch
hugin import --prefix <p> Autoren wandelt vorhandene Dateien des Repos in Paketordner um, OpenCode zuerst: .opencode/, opencode.json, AGENTS.md, danach .claude/, .mcp.json, CLAUDE.md. Werkzeugeigene Felder (z. B. permission) bleiben samt Reihenfolge in extra; feste Werte in MCP-Kopfzeilen werden zu {{env:…}}; lokale MCP-Server, Plugins und Symlinks bleiben draußen. Ändert im Repo nichts; Übersicht in IMPORT.md
hugin login beide speichert den Key im Schlüsselbund des Betriebssystems, sonst Umgebungsvariable HUGIN_KEY

11.2 .hugin.yaml im Repository (wird eingecheckt)

server: https://hugin.intern
project: shop                       # Projekt-Slug in Hugin
tools: auto                     # auto | [claude] | [opencode] | [claude, opencode]
opencode_reads_claude: true         # false, wenn OPENCODE_DISABLE_CLAUDE_CODE gesetzt ist
tool_check: warn            # warn | strict (bricht bei zu alten Werkzeugen ab)
packages:
  - pub-munin-nutzen@stable
  - pub-sicherheits-review@^2
  - shop-api-release@test
unsubscribed: []                      # hier stehen nur Nicht-Pflichtpakete

tools: auto erkennt die Werkzeuge an vorhandenen Dateien (.claude/, CLAUDE.md, .mcp.json für Claude Code; .opencode/, opencode.json, AGENTS.md für OpenCode). Gibt es keine Hinweise, erzeugt Hugin für beide. Liegt nur eine CLAUDE.md im Repo, gilt nur Claude Code; wer beide nutzt, trägt sie dann fest ein.

11.3 hugin.lock (wird eingecheckt)

# Von hugin sync erzeugt. Nicht von Hand ändern.
packages:
  pub-munin-nutzen:
    version: 1.4.0
    checksum: sha256:3f1c…
    files:
      .claude/skills/pub-munin-nutzen/SKILL.md: sha256:a91e…
      .claude/skills/pub-munin-nutzen/referenz/beispiele.md: sha256:77b0…
  pub-munin-anbindung:
    version: 1.0.0
    checksum: sha256:c02d…
    keys:
      .mcp.json: [mcpServers.munin]
      opencode.json: [mcp.munin]

Damit weiß sync:

  • welche Dateien und JSON-Schlüssel ihm gehören,
  • ob jemand eine verwaltete Datei von Hand geändert hat (Prüfsumme weicht ab),
  • was beim Abbestellen zu löschen ist.

11.4 Verwaltete Dateien

  • Markdown-Dateien bekommen einen Kopfkommentar. Bei Dateien mit Frontmatter steht er als YAML-Kommentar in der Frontmatter, damit die Datei gültig bleibt:

    ---
    # Verwaltet von Hugin (pub-munin-nutzen 1.4.0). Änderungen über hugin.intern.
    name: pub-munin-nutzen
    description: …
    ---
    
  • CLAUDE.md: nur der Block zwischen hugin:begin und hugin:end.

  • JSON-Dateien: nur die Schlüssel aus hugin.lock. Reihenfolge und Formatierung der übrigen Inhalte bleiben erhalten (→ prüfen bei JSONC, 7.2).

  • Handänderung entdeckt: sync zeigt den Diff und bricht ab. Mit --overwrite setzt es die Datei zurück.

11.5 Ablauf von hugin sync

  1. .hugin.yaml lesen, Werkzeuge erkennen, Werkzeugversionen prüfen (Warnung).
  2. Beim Server POST /api/v1/bundle aufrufen: abonnierte und Pflichtpakete gegen Kanäle und Bereiche → konkrete Versionen, übersetzt für die erkannten Werkzeuge.
  3. Für Versionen, deren Prüfsumme nicht in hugin.lock steht, die übersetzten Operationen für die erkannten Werkzeuge laden (→ 14.2). Prüfsummen kontrollieren.
  4. Handänderungen an verwalteten Dateien suchen → bei Befund abbrechen.
  5. Schreiben, Abbestelltes entfernen, hugin.lock neu schreiben.
  6. Nutzung an den Server melden: Projekt, Repo-Kennung, Werkzeuge mit Versionen, installierte Paketversionen. Die Repo-Kennung ist die Git-Remote-URL (git remote get-url origin), vereinheitlicht: ohne Zugangsdaten, ohne .git, SSH- und HTTPS-Form angeglichen (git.intern/shop/api). Ohne Git-Remote: der Pfad des Repos relativ zum Benutzerordner.
  7. Zusammenfassung ausgeben: neu, aktualisiert, entfernt, Hinweise.

Schlägt Schritt 2 oder 3 fehl (kein Netz), ändert sync nichts. Ein halber Zustand darf nie entstehen.


12. MCP-Server

Hugin bietet einen MCP-Endpunkt, wie Munin. Agenten in beiden Werkzeugen erreichen ihn, weil beide MCP-Werkzeuge unterstützen. Er dient der Suche und dem Rückkanal, nicht der Verteilung. Verteilt wird über hugin sync, weil nur installierte Skills die eingebaute Skill-Logik der Werkzeuge nutzen.

Werkzeug Tut
search_packages „Gibt es schon einen Skill für X?“: sucht in ID und Beschreibung freigegebener Pakete (→ 13, Suche)
get_package liefert den Inhalt einer Version, z. B. damit der Agent einen Skill einmalig nutzt, ohne ihn zu installieren
report_feedback Rückmeldung zu einem Paket: unklar, falsch, veraltet, Verbesserungsvorschlag; mit Version und Werkzeug
list_subscriptions was dieses Projekt abonniert hat und ob es Neues gibt

Kein Werkzeug zum Schreiben oder Freigeben. Pakete entstehen nur über Autoren und Prüfer.


13. Weboberfläche

Vom Server gerendert mit Jinja2 und HTMX, gestaltet mit dem hauseigenen CSS-Framework (static/css-framework/). Kein JS-Framework, kein eigener Build-Schritt.

  • Seiten bauen auf dem Basis-Template des Frameworks auf (Sidebar-Layout, Topnav, Theme-Umschalter).
  • HTMX für Teilaktualisierungen: Filter und Suche im Katalog, Tabs für Diff und Vorschau, Freigabe-Dialoge, Formularprüfung (422 → Fragment mit Fehlern). Ein Endpunkt liefert bei HX-Request nur das Fragment, sonst die ganze Seite.
  • Komponenten kommen nur aus dem Framework (components.css, Tokens aus tokens.css). Fehlt etwas, wird es im Framework ergänzt, nicht in Hugin.
  • CSP streng, ohne Inline-Skripte und Inline-Styles. Das Framework muss dafür angepasst werden (→ 21.2).
  • Online-Hilfe unter /help, auch ohne Anmeldung: Themen je Zielgruppe als Markdown, Suche, Kontext-Links von den Arbeitsseiten. Die Tabelle der Linter-Regeln entsteht aus dem Code (hugin_core.linter.RULES).
Seite Inhalt
Katalog alle sichtbaren Pakete; Filter nach Art, Geltung, Kompatibilität, Risiko; Suche
Paket Beschreibung, Besitzer, Versionen, Kanäle, Nutzung (wie viele Projekte, welche Versionen), Rückmeldungen
Version Inhalt, Diff zur Vorgängerversion, Vorschau je Werkzeug (die erzeugten Dateien nebeneinander), Linter-Ergebnis, Risiko, Freigaben
Prüfungen Posteingang der Prüfer: was wartet auf mich?
Mein Projekt Abos laut letzter Meldung, veraltete Versionen, Pakete mit neuen Versionen, Werkzeugversionen der Teams
Überblick (Tenant) wie bei Munin: was zu tun ist, z. B. Prüfungen offen, Projekte auf veralteten Versionen, Pakete ohne Nutzung
System (Plattform) wie bei Munin, plus Modellzuordnung, Werkzeug-Untergrenzen, Pflichtpakete
Projekte, Keys, Tenants wie bei Munin
Hilfe für Teams, Autoren, Prüfer, Admins

Suche (Katalog, hugin search, MCP): einfache Textsuche (LIKE, ohne Groß- und Kleinschreibung) über ID und Beschreibung, dazu die Filter. Keine Volltextsuche: Der Katalog bleibt klein, und die Beschreibung ist ohnehin so geschrieben, dass sie sagt, wofür ein Paket da ist. Reicht das später nicht, lässt sich Volltext im Store nachrüsten, ohne Schnittstellen zu ändern.

Bearbeiten in der Weboberfläche ist bewusst schlicht: Manifest und Textdateien direkt editieren; mehrere Dateien als ZIP hochladen. Der bevorzugte Weg für Autoren ist lokal mit hugin new, hugin validate, hugin try und hugin upload.


14. Architektur

14.1 Überblick

┌──────────────── Entwickler-Rechner ────────────────┐
│  Repo: .hugin.yaml, hugin.lock, erzeugte Dateien   │
│  hugin (CLI, Go) ──────┐                           │
│  Claude Code / OpenCode ──┐                        │
└────────────────────────┼──┼────────────────────────┘
                         │  │ MCP (suchen, Rückmeldung)
              HTTP-API   │  │
┌────────────────────────▼──▼────────────────────────┐
│ Hugin-Server (Python, FastAPI)                     │
│  Weboberfläche (Jinja2, HTMX) · HTTP-API · MCP ·   │
│  Linter · Übersetzer · Auflösung · Audit · Export  │
│            │                                       │
│      SQLite oder PostgreSQL                        │
└────────────────────────────────────────────────────┘

14.2 Technik und Aufteilung

Teil Technik
Server Python 3.12+, FastAPI, Uvicorn; Projekt und Abhängigkeiten mit uv
Modelle, Prüfung Pydantic v2 (Manifest, API-Schemas)
Datenbank SQLAlchemy 2 + Alembic; SQLite für kleinen Betrieb, PostgreSQL sonst
Weboberfläche Jinja2, HTMX, CSS-Framework (→ 13)
MCP offizielles MCP-Python-SDK, in FastAPI unter /mcp eingehängt
CLI Go, eine Binärdatei ohne Abhängigkeiten für macOS, Linux und Windows
Tests pytest (Server), go test (CLI)

Übersetzer und Linter nur im Server

Server und CLI sind in verschiedenen Sprachen geschrieben. Linter und Übersetzer gibt es deshalb nur einmal, im Server. Das CLI übersetzt nicht selbst.

Server CLI
Manifest lesen, Linter, Risiko, Kompatibilität Werkzeuge und ihre Versionen erkennen
Auflösung: Abos + Pflicht + Kanäle → Versionen .hugin.yaml lesen, hugin.lock schreiben
Übersetzer → Liste von Operationen Operationen anwenden, Handänderungen erkennen
Vorschau in der Weboberfläche Schlüsselbund, Anmeldung

Ein Übersetzer liefert keine fertigen Repo-Dateien, sondern Operationen, die das CLI anwendet. So bleiben Dateien der Teams unberührt, auch wenn der Server sie nie sieht:

Operation Bedeutung Beispiel
file Datei mit diesem Inhalt anlegen oder ersetzen .claude/skills/pub-munin-nutzen/SKILL.md
block verwalteten Block in einer Datei setzen hugin-Block in CLAUDE.md
json Schlüssel in einer JSON-Datei setzen mcpServers.munin in .mcp.json

Folge: hugin validate und hugin try brauchen den Server (POST /api/v1/validate, → 14.6). Für ein internes Werkzeug ist das vertretbar; hugin sync mit unveränderter hugin.lock arbeitet wie bisher ohne Übersetzung.

Verhältnis zu Munin

Munin ist in Go geschrieben; Code lässt sich nicht übernehmen. Übernommen werden die Konzepte: Tenants (in Munin: Workspaces), Projekte, Keys mit Rollen, Audit, Hilfe-System mit Zielgruppen, Überblicks- und Systemseite, Markdown-Export. Sie werden in Python neu gebaut.

14.3 Paketstruktur und Einbettung

hugin/
├── server/                    # Python, ein uv-Projekt
│   ├── pyproject.toml
│   ├── src/
│   │   ├── hugin_core/        # ohne FastAPI und DB, einzeln nutzbar
│   │   │   ├── package/       # Quellformat: Manifest (Pydantic), Ordner ↔ Version
│   │   │   ├── linter/        # Regeln H001 ff.
│   │   │   ├── translators/   # je Ziel und Art; reine Funktionen → Operationen
│   │   │   │   ├── claude/
│   │   │   │   └── opencode/
│   │   │   └── resolution/    # Abos + Pflicht + Kanäle → konkrete Versionen
│   │   └── hugin_app/         # FastAPI-Schicht
│   │       ├── store/         # SQLAlchemy-Modelle und Zugriffe
│   │       ├── auth/          # Keys, Personen, Rollen, Sitzungen, CSRF
│   │       ├── audit/
│   │       ├── api/           # /api/v1
│   │       ├── mcp/           # MCP-Werkzeuge
│   │       ├── web/           # Routen der Weboberfläche
│   │       ├── docs/          # Online-Hilfe
│   │       ├── export/
│   │       ├── migrations/    # Alembic
│   │       ├── templates/
│   │       └── static/        # CSS-Framework (aus dist/), htmx
│   └── tests/
├── cli/                       # Go
│   ├── cmd/hugin/
│   └── internal/
│       ├── api/               # Client für /api/v1
│       ├── reconcile/         # Operationen anwenden, hugin.lock
│       ├── tools/             # Erkennung, Versionsprüfung
│       ├── keystore/          # Schlüsselbund des Betriebssystems
│       └── importer/          # hugin import
├── static/css-framework/      # Quelle des CSS-Frameworks
└── scripts/

hugin_core darf nichts aus hugin_app importieren (geprüft mit import-linter).

Einbettung in ein anderes Projekt

Hugin soll später in ein anderes FastAPI-Projekt eingebaut werden können. Dafür:

  • Kern einzeln nutzbar: hugin_core hat keine Abhängigkeit zu FastAPI oder DB.
  • Router statt fester App: hugin_app stellt Router bereit (api_router(), web_router(), mcp_app()). Die eigenständige App setzt sie nur zusammen; ein anderes Projekt hängt sie mit include_router unter einem Präfix ein.
  • Auth austauschbar: Die Router fragen Identität und Rollen über eine Schnittstelle ab (FastAPI-Dependency). Hugin bringt die Umsetzung mit Keys und Personen mit (→ 14.5); ein Gastprojekt kann seine eigene einsetzen.
  • Datenbank teilbar: Tabellen mit Präfix hugin_ oder eigenes PostgreSQL-Schema, eigene Alembic-Versionstabelle. Die Sitzung kommt als Dependency vom Gastprojekt.
  • Templates überschreibbar: Jinja2 sucht zuerst in den Templates des Gastprojekts, dann in denen von Hugin; das Layout (base.html) lässt sich so ersetzen.
  • Pfade nur über url_for, nie fest im Template, damit ein Präfix funktioniert.

14.4 Rollen

Rolle Hängt an Darf
read Projekt-Key Katalog, sync, Nutzung melden, Rückmeldung geben; typisch für das CLI und die Agenten
author Person (ab Stufe 4), vorher Projekt-Key Entwürfe für Pakete seines Projekts anlegen und einreichen
reviewer Person (ab Stufe 4), vorher Projekt-Key Versionen in seiner Geltung freigeben oder zurückgeben, nicht die eigenen
Tenant-Admin bis Stufe 4 Tenant-Key (ht_), danach Person Projekte und Keys im Tenant anlegen, Pflichtpakete im Tenant, ab Stufe 4 Personen anlegen und Prüfer benennen
Plattform-Admin wie bei Munin Tenants anlegen, Modellzuordnung, Untergrenzen, öffentliche Pflichtpakete, Prüfer für öffentliche Pakete

14.5 Identität: Maschinen und Personen

Das Problem

In Munin hängt der Zugang an Keys, und ein Key gehört zu einem Projekt, nicht zu einem Menschen. Den Projekt-Key nutzen der Agent und oft mehrere Leute im Team gemeinsam. Munin weiß also, mit welchem Key etwas geschah, aber nicht, welcher Mensch es war. Für Wissenseinträge reicht das: Ist etwas falsch, korrigiert man es.

Bei Hugin reicht es nicht. Ein Paket ist Lieferkette (→ 16), und die Freigabe ist der eigentliche Schutz. Sie muss zwei Dinge leisten:

  1. Vier Augen: Der Autor darf nicht selbst freigeben. Bei riskanten Paketen stimmen zwei verschiedene Menschen zu.
  2. Verantwortung: Im Audit steht „freigegeben von Anna Schmidt am 12.11.“, nicht „freigegeben mit Key shop-pruefer“. Fragt die Revision, wer ein Skript in 50 Repos verteilt hat, braucht es einen Namen.

Mit Projekt-Keys geht beides nicht sicher:

  • Hat Anna den Autor- und den Prüfer-Key, reicht sie ein und gibt sich selbst frei.
  • Teilen sich drei Leute einen Prüfer-Key, zählen zwei Freigaben als vier Augen, obwohl es ein Mensch gewesen sein kann.

Wer Person sein muss

Handlung Wer Identität
hugin sync, Suche, Nutzung melden CLI, Agent Projekt-Key genügt
Rückmeldung zu einem Paket Agent oder Mensch Projekt-Key genügt; Quelle wird vermerkt
Entwurf hochladen, einreichen Autor Person
Freigeben, zurückgeben Prüfer Person
Pflichtpakete, Prüfer benennen, Personen verwalten Tenant-Admin Person
Modellzuordnung, Untergrenzen, öffentliche Pflichtpakete Plattform-Admin HUGIN_ADMIN_TOKEN (→ 16.3), im Audit als „Plattform-Admin“

Grundsatz: Maschinen nutzen Projekt-Keys, Menschen in der Freigabekette eine eigene Identität.

Möglichkeiten

Persönliche Keys SSO (OIDC: Keycloak, Entra ID)
Wie Tenant-Admin legt Personen mit Name und Mail an; jede bekommt einen eigenen Key (hp_…) für Web und CLI Anmeldung mit dem Firmenkonto; Gruppen aus dem Verzeichnis werden zu Rollen
Vorteile keine neue Infrastruktur, Muster wie Munin, schnell gebaut echte Identität, Austritte wirken automatisch, keine Keys für Menschen
Nachteile Identität nur so gut wie die Vergabe; weitergegebene Keys hebeln sie aus; Austritte von Hand sperren Abhängigkeit vom Identity Provider (IdP), Abstimmung mit der IT; für Munin bewusst zurückgestellt

Entscheidung

  1. Stufe 1–3: ohne Personen. Eine Freigabe; Autor- und Prüfer-Key werden getrennt vergeben. Die Schwäche steht offen in der Doku. Das genügt für den Start mit wenigen Paketen vom Risiko text, etwa pub-munin-nutzen. Pakete mit Risiko scripts oder permissions nimmt Hugin bis Stufe 4 nicht an.
  2. Stufe 4: persönliche Keys.
    • Rollen author und reviewer hängen an der Person, je Projekt oder Tenant.
    • Vier Augen werden erzwungen: Prüfer ≠ Autor; bei scripts und permissions zwei verschiedene Personen.
    • Im Audit stehen Namen.
    • Persönliche Keys laufen ab (Standard 180 Tage) und tauchen wie bei Munin im Überblick auf („läuft bald ab“, „seit 90 Tagen ungenutzt“). So fallen vergessene Keys ausgeschiedener Personen auf.
    • Wer eine Person sperrt, behält ihre bisherigen Freigaben im Audit; nur neue Handlungen sind ausgeschlossen.
  3. Später: SSO als zweiter Anmeldeweg für dieselben Personen, als gemeinsames Thema mit Munin. Munin hätte auch etwas davon: Im Audit stünde dann, wer in der Weboberfläche einen Eintrag geändert hat.

Damit SSO später ohne Umbau andockt, gibt es die Tabelle people (→ 15) von Stufe 1 an, mit einem Feld external_id für die spätere Kennung aus dem IdP. Bis Stufe 4 bleibt sie leer.

Anmeldung mit persönlichem Key

  • Weboberfläche: Anmeldeseite wie bei Munin. Die Sitzung trägt die Person; alle Handlungen im Audit nennen sie.
  • CLI: hugin login speichert den persönlichen Key im Schlüsselbund. hugin upload nutzt ihn; hugin sync nutzt weiterhin den Projekt-Key aus HUGIN_KEY bzw. dem Schlüsselbund-Eintrag des Projekts.
  • Agenten bekommen nie einen persönlichen Key. Ein Agent kann über MCP keine Entwürfe einreichen oder freigeben (→ 12).

14.6 HTTP-API für das CLI

Schlicht, JSON, versioniert unter /api/v1/:

Methode und Pfad Zweck
POST /api/v1/bundle Abos auflösen und ausliefern: ZIP mit hugin-bundle.json und Dateien, gleiches Format wie 14.7
GET /api/v1/packages/{id}/versions/{v} Version im Quellformat als ZIP (für Autoren und Export)
POST /api/v1/validate Paketordner prüfen und übersetzen, ohne zu speichern (hugin validate)
POST /api/v1/trial?tools=claude:2.1.302,opencode:1.19.3 Bundle für einen ungespeicherten Paketordner (hugin try)
GET /api/v1/config Modellzuordnung, Untergrenzen, Pflichtpakete
POST /api/v1/usage Meldung von sync
GET /api/v1/search?q= Katalogsuche
POST /api/v1/drafts Upload eines Entwurfs (Rolle author)

Authentifizierung über Authorization: Bearer <key> (→ 16.3). Lesende Endpunkte und usage nehmen Projekt-Keys; drafts verlangt ab Stufe 4 einen persönlichen Key (→ 14.5).

14.7 Auslieferung für Integrationen

Eine weitere Integration holt Pakete ohne das Go-CLI ab: ein PowerShell-Skript unter Windows. Windows PowerShell 5.1 ist vorausgesetzt, PowerShell 7 ist nicht verfügbar. Umsetzung nach dem CLI (→ 20, Stufe 2).

Präfix und Key

  • Alle Endpunkte unter /api/v1/delivery/.
  • Eigener Integrations-Key (hi_…, → 16.3), neben Projekt-Key und persönlichem Key: gilt für genau einen Tenant, optional auf Projekte eingeschränkt; Rolle delivery (nur lesen, ausliefern, Nutzung melden; kein Hochladen, keine Freigabe, kein MCP); einzeln widerrufbar, läuft ab; im Audit mit dem Namen der Integration; mit Rate-Limit.

Endpunkte

Methode und Pfad Zweck
GET /status Dienst ok, Hugin-Version, Gültigkeit und Geltung des Keys, Werkzeug-Untergrenzen
GET /packages Übersicht der freigegebenen Pakete: Art, aktuelle Version je Kanal, Kompatibilität, Risiko; Filter kind, channel, changed_since
GET /packages/{id} Details und Versionen
POST /bundle Auslieferung als ZIP (siehe unten)
POST /usage Meldung nach der Installation, optional (→ 11.5, Schritt 7)
GET /client.ps1 Referenz-Skript für die Installation, von Hugin gepflegt
POST /resolve Auflösen ohne Übersetzung, für Plattformen (siehe unten)
GET /packages/{id}/versions/{version}/source Eine lieferbare Version im Quellformat als ZIP
POST /feedback Rückmeldung zu einem Paket, wie report_feedback über MCP

Plattformen als Abnehmer

Neben Claude Code, OpenCode und dem PowerShell-Skript nutzt eine Plattform Hugin, die Pakete selbst abbildet, statt Dateien für ein Werkzeug zu schreiben: der AI Workspace Bifröst (Regeln in den System-Prompt, Skills über eigene Tools, Befehle als Slash-Befehle im Chat). Sie braucht keine übersetzten Bundles, sondern:

  • POST /resolve: gleiche Anfrage wie /bundle ohne tools und options (packages, unsubscribed, mandatory, major, installed). Antwort je Paket: id, kind, version, checksum, risk, channel, mandatory, deprecated, successor, description, arguments; dazu major_available. Gleiche Auflösung wie beim Bundle (→ 9.3), Pflichtpakete eingeschlossen.
  • GET /packages/{id}/versions/{version}/source: hugin.yaml und Inhaltsdateien als ZIP (wie der Download, → 14.6), deterministisch; Kopfzeile X-Hugin-Checksum mit der Prüfsumme der Version. Die Plattform schreibt Version und Prüfsumme fest und prüft beim Laden ihre gespeicherte Kopie.
  • POST /feedback: package, version, kind (unclear, wrong, deprecated, suggestion), text, client (z. B. ai-workspace). Quelle agent, im Tenant des Keys; der Agent der Plattform hat keinen Projekt-Key und damit kein MCP.
  • Nutzung meldet die Plattform wie andere Integrationen über POST /usage, mit dem Client-Namen ai-workspace/<workspace> als repo.

Was die Plattform aus Risiko und Art macht (z. B. keine Skills mit Skripten), entscheidet sie selbst; Hugin-Inhalte verleihen dort keine Rechte.

Das Format ist verbindlich in contract/ festgelegt; Server und Clients testen gegen dieselben Dateien:

Datei Inhalt
contract/bundle-request.schema.json Anfrage an /bundle
contract/hugin-bundle.schema.json hugin-bundle.json im ZIP
contract/examples/packages/ Beispielpakete im Quellformat
contract/examples/bundles/ goldene Bundles, vom Server erzeugt (pytest --update), vom CLI angewendet

Anfrage an /bundle:

{ "packages": ["pub-munin-nutzen@stable", "pub-sicherheits-review@^2"],
  "tools": { "claude": "2.1.302", "opencode": "1.19.3" },
  "mandatory": true,
  "installed": { "pub-munin-nutzen": "1.3.0" } }

Aus den Werkzeugversionen leitet der Server die Ziele ab (claude, opencode@1, opencode@2). Aus installed berechnet er, was zu entfernen ist. ETag und If-None-Match → 304, wenn sich nichts geändert hat.

Inhalt des ZIP

hugin-bundle.json          # Installationsanleitung
files/.claude/skills/pub-munin-nutzen/SKILL.md
files/…

Ein Bundle beschreibt den vollständigen Soll-Zustand der angefragten Pakete. Die Operationen werden der Reihe nach angewendet:

Operation Bedeutung
remove Datei entfernen, die eine frühere Version angelegt hat
json_remove JSON-Schlüssel bzw. Listeneintrag entfernen
file Datei aus files/ schreiben, mit Prüfsumme
json JSON-Schlüssel setzen (mode: set) oder Eintrag in einer Liste sicherstellen (mode: list_item); Schlüssel als Liste, z. B. ["mcpServers", "munin"]
block verwalteten Block setzen; leerer content entfernt ihn
{
  "format": 1,
  "generated": "2026-10-18T12:00:00Z",
  "tools": ["claude", "opencode@1"],
  "packages": [{ "id": "pub-munin-nutzen", "version": "1.4.0", "checksum": "sha256:…",
               "kind": "skill", "risk": "text", "compatibility": "portable", "mandatory": false }],
  "operations": [
    { "op": "file", "package": "pub-munin-nutzen", "path": ".claude/skills/pub-munin-nutzen/SKILL.md",
      "source": "files/.claude/skills/pub-munin-nutzen/SKILL.md", "sha256": "sha256:…" },
    { "op": "json", "package": "pub-munin-anbindung", "file": ".mcp.json",
      "key": ["mcpServers", "munin"], "value": { "…": "…" } },
    { "op": "json", "file": "opencode.json", "key": ["instructions"],
      "mode": "list_item", "value": ".hugin/rules/*.md" },
    { "op": "block", "file": "CLAUDE.md", "block": "hugin",
      "content": "@.hugin/rules/pub-hausregeln.md\n" }
  ],
  "hints": [
    { "kind": "env_variable", "name": "MUNIN_KEY", "package": "pub-munin-anbindung" }
  ]
}

package fehlt bei gemeinsamen Einträgen, die Hugin für mehrere Pakete pflegt. create (optional bei json und block) verweist auf eine fertige Datei für den Fall, dass die Zieldatei noch nicht existiert.

Vorgaben für PowerShell 5.1

Punkt Vorgabe
Encoding UTF-8 ohne BOM schreiben: [IO.File]::WriteAllText($pfad, $text, (New-Object Text.UTF8Encoding $false)). Nie Set-Content/Out-File (schreiben in 5.1 nicht UTF-8 ohne BOM)
Zeilenenden LF wie geliefert, nicht nach CRLF wandeln
JSON ConvertTo-Json immer mit -Depth 20 (sonst still abgeschnitten); formatiert die Datei um und verliert Kommentare. Deshalb json nur für Dateien ohne Kommentare, sonst Abbruch mit Hinweis
Prüfsummen Get-FileHash -Algorithm SHA256 gegen sha256 im Bundle
ZIP Expand-Archive in einen leeren Temp-Ordner, erst nach erfolgreicher Prüfung kopieren
TLS vor dem ersten Aufruf [Net.ServicePointManager]::SecurityProtocol = 'Tls12'
HTTP Invoke-RestMethod/Invoke-WebRequest mit -UseBasicParsing
Zustand eigene Lock-Datei wie hugin.lock (→ 11.3), damit Handänderungen erkannt und Abbestelltes entfernt werden kann

Das Format hugin-bundle.json ist versioniert (format). Das Go-CLI nutzt denselben Endpunkt /bundle, damit es nur einen Auslieferungsweg gibt.


15. Datenmodell

Eine Datenbank (SQLite oder PostgreSQL) für alle Tenants. Jede Tabelle mit Tenant-Bezug hat eine Spalte tenant_id. Öffentliche Pakete haben tenant_id = NULL. Bei Einbettung in ein anderes Projekt tragen alle Tabellen das Präfix hugin_ (→ 14.3); unten ist es weggelassen.

Plattform

Tabelle Wichtige Spalten
tenants id, slug (kurz, Präfix für Paket-IDs), name, locked_since, created
projects id, tenant_id, slug, name, created
keys id, kind (project / integration / tenant / person), tenant_id, project_id (bei project), person_id (bei person), name, roles, projects (Einschränkung bei integration, leer = alle), key_hash, prefix (erste Zeichen zur Anzeige), expires_at, last_used, revoked_since, created, created_by
settings key, value (JSON): Modellzuordnung, Untergrenzen der Werkzeuge
sessions id (SHA-256 des Cookie-Tokens), key_id (leer = Plattform-Admin), csrf, created, expires_at, revoked_since, ip, admin_fingerprint (Admin-Sitzung endet, wenn HUGIN_ADMIN_TOKEN wechselt) — Sitzungen der Weboberfläche, serverseitig widerrufbar (→ 16.3)
people id, tenant_id, name, mail, external_id (leer bis SSO), locked_since, created, created_by
people_roles person_id, role (author, reviewer, tenant_admin), tenant_id, project_id (leer = ganzer Tenant)

people und people_roles werden von Stufe 1 an angelegt, aber erst ab Stufe 4 genutzt (→ 14.5). Persönliche Keys stehen in keys mit kind = person.

Pakete

Tabelle Wichtige Spalten
packages id (Text, z. B. pub-munin-nutzen), tenant_id (NULL = öffentlich), kind, scope, owner_project, deprecated_since, successor, created
mandatory_packages package_id, tenant_id (NULL = in allen Tenants), set_by, time
versions package_id, version, status, channel, manifest (JSON), checksum, risk, compatibility, min_version_claude, min_version_opencode, linter (JSON), author_person, author_key, created, submitted, approved
contents checksum (Schlüssel), content (BLOB), size
files version_id, path, checksum → contents
approvals version_id, reviewer_person, reviewer_key, decision (approve / return), comment, time

contents speichert jeden Dateiinhalt nur einmal; unveränderte Hilfsdateien kosten bei einer neuen Version keinen Platz.

…_person ist bis Stufe 4 leer, dann steht dort nur der Key. Ab Stufe 4 ist …_person Pflicht; die Regeln „Prüfer ≠ Autor“ und „zwei verschiedene Personen“ werden über diese Spalten geprüft.

Paket-IDs sind über alle Tenants eindeutig. Das ergibt sich aus dem Präfix (→ 10.4) und wird zusätzlich per Unique-Index erzwungen.

Rückkanal und Audit

Tabelle Wichtige Spalten
usage tenant_id, project_id, key_id, repo (Kennung aus Git-Remote, Pfad oder Client-Name), package_id, version, tools (JSON: Werkzeug → Version), last
feedbacks tenant_id, package_id, version, source (agent / human), tool, kind, text, status, time
audit_events id, time, tenant_id (NULL = Plattform), actor_kind (person / key / admin), actor_id, action (z. B. version.approved), target_kind, target_id, data (JSON), ip

Rückmeldungen zu einem öffentlichen Paket tragen den Tenant der Meldenden. Der Besitzer des Pakets sieht den Text, aber nicht, aus welchem Tenant er kommt; der Plattform-Admin sieht beides.

Versionen und Dateien werden nie geändert oder gelöscht, nur ihr Status wechselt. Ausnahme: Entwürfe, die nie eingereicht wurden.

15.1 Trennung der Tenants

Mit einer gemeinsamen Datenbank hängt die Trennung allein am Code. Deshalb:

  • Ein Zugriffsweg: Routen greifen nie direkt auf Tabellen zu, sondern über die Store-Schicht. Jede Funktion dort verlangt einen Access (Tenant, Projekt, Rollen) und filtert selbst: tenant_id = :tenant OR tenant_id IS NULL für Pakete, tenant_id = :tenant für alles andere.
  • Der Zugriff kommt aus dem Key oder der Sitzung, nie aus einem Parameter der Anfrage. Ein Tenant-Slug in der URL dient nur der Anzeige und wird gegen den Zugriff geprüft.
  • Fremde Objekte gibt es nicht: Eine ID aus einem anderen Tenant liefert 404, nicht 403, damit sich nicht erraten lässt, was existiert.
  • Öffentliche Pakete sind für Tenants nur lesbar. Ändern, Freigeben und Pflicht setzen darf nur, wer öffentliche Pakete pflegt (→ 10.1, 14.4).
  • Optional in PostgreSQL: Row-Level-Security als zweite Sicherung; die Anwendung setzt dazu je Anfrage SET LOCAL hugin.tenant_id.
  • Test: Eine Testsuite legt zwei Tenants mit gleich benannten Inhalten an und ruft jeden Endpunkt (Web, API, /bundle, MCP, Export) mit dem Key des einen auf. Nichts aus dem anderen darf auftauchen. Neue Endpunkte werden automatisch erfasst (Routen aus der FastAPI-App gelesen); ein Endpunkt ohne Testfall lässt den Test fehlschlagen.

16. Sicherheit

Ein Paket ist Lieferkette: Wer ein weit verbreitetes Paket ändern kann, steuert viele Agenten in vielen Repositories.

16.1 Risiko-Einstufung (automatisch)

Risiko Wann
text nur Anweisungen, keine Skripte, keine Rechte
tools verweist auf MCP-Werkzeuge oder enthält !`befehl` in Befehlen
scripts enthält ausführbare Dateien oder ist ein Hook
permissions gewährt Rechte: allowed-tools, permission mit allow, Fähigkeitsklasse shell oder write

Das höchste zutreffende Risiko gilt. Es bestimmt die Zahl der Freigaben (→ 9.2).

16.2 Maßnahmen

  • Prüfsummen auf Version und Datei; sync lehnt bei Abweichung ab.
  • Unveränderliche Versionen. Was freigegeben ist, kann nicht still ersetzt werden.
  • Audit aller Statuswechsel, Freigaben, Pflicht-Markierungen und Konfigurationsänderungen.
  • Keine Geheimnisse in Paketen (Linter H031, H032).
  • Skripte werden in der Weboberfläche vollständig angezeigt, nie nur verlinkt.
  • Nur verwaltete Dateien: sync schreibt nur unter den bekannten Pfaden und nur Schlüssel aus hugin.lock. Pfade im Paket werden gegen „aus dem Repo ausbrechen“ geprüft (.., absolute Pfade, Symlinks und unter Windows Junctions, nichts unter .git/). Dateinamen, die nicht auf allen Systemen gehen (Punkt oder Leerzeichen am Ende, <>:"|?*, CON, NUL …), lehnt schon der Upload ab.
  • Uploads begrenzt: Request-Bodies bis HUGIN_MAX_UPLOAD_MB (Standard 40), geprüft vor der Anmeldung; Pakete bis 32 MB entpackt, 4 MB je Datei, 500 Dateien.
  • Key nur an den eigenen Server: Ist HUGIN_SERVER gesetzt, sendet das CLI einen Key aus HUGIN_KEY nur an diesen Server, auch wenn ein Pull Request server: in .hugin.yaml ändert. In CI daher beide Variablen setzen. Im Schlüsselbund ist der Key ohnehin an die Adresse gebunden.
  • Prompt-Injection im Inhalt: Prüfer sehen den Text, wie der Agent ihn sieht. Hinweis im Linter bei auffälligen Mustern, z. B. „ignoriere vorherige Anweisungen“ oder versteckte Unicode-Zeichen.
  • Später: Signatur der Versionen, damit das CLI auch einem kompromittierten Server nicht blind vertraut.

16.3 Keys, Anmeldung, Sitzungen

Art Präfix Gehört zu Zweck
Projekt-Key hk_ Projekt eines Tenants CLI (sync, check), Agenten über MCP
Tenant-Key ht_ Tenant Verwaltung des Tenants (Rolle tenant_admin) bis Stufe 4; vergibt nur der Plattform-Admin; danach durch Personen ersetzt
Integrations-Key hi_ Tenant, optional auf Projekte eingeschränkt Auslieferung an Integrationen (→ 14.7), nur unter /api/v1/delivery/; übrige API, MCP und Weboberfläche lehnen ihn ab
Persönlicher Key hp_ Person (ab Stufe 4) Web und CLI für Autoren, Prüfer, Admins
Admin-Token – Plattform Umgebungsvariable HUGIN_ADMIN_TOKEN; nur für die Plattform-Verwaltung
  • Format: Präfix + 32 Byte Zufall, Base32 ohne Füllzeichen. Der Key wird nur einmal beim Anlegen angezeigt.
  • Speicherung: nur SHA-256 des Keys (Keys sind lang und zufällig, ein langsamer Hash ist nicht nötig) plus die ersten acht Zeichen zur Anzeige. Vergleich in konstanter Zeit.
  • Übertragung: Authorization: Bearer <key>, nie in der URL.
  • Ablauf: persönliche Keys 180 Tage, Integrations-Keys 365 Tage, Projekt-Keys ohne Ablauf, aber mit Hinweis im Überblick nach 90 Tagen ohne Nutzung.
  • Weboberfläche: Anmeldung mit persönlichem Key (bis Stufe 4: mit Projekt-Key der Rolle author oder reviewer). Danach eine Sitzung als signiertes, HttpOnly-, Secure-, SameSite=Lax-Cookie, 8 Stunden gültig, serverseitig widerrufbar.
  • CSRF: Token je Sitzung in jedem Formular und als Header X-CSRF-Token für HTMX-Anfragen (über hx-headers im Basis-Template). Die API mit Bearer-Key braucht kein CSRF-Token.
  • Rate-Limit je Key bzw. Sitzung (HUGIN_RATE_LIMIT, Standard 600 je Minute); nach HUGIN_FAILED_ATTEMPTS falschen Keys (Standard 20 in 5 Minuten) antwortet Hugin der IP mit 429, für API, Anmeldung und MCP. Gezählt wird je Server-Prozess; bei mehreren Pods gilt die Grenze je Pod.
  • Client-IP hinter dem Router: X-Forwarded-For gilt nur von Adressen in HUGIN_PROXY_IPS (Standard 127.0.0.1). In OpenShift auf das Netz des Routers setzen, z. B. 10.128.0.0/14; sonst sehen Sperre und Audit nur die IP des Routers, und 20 falsche Keys sperren alle. * nur, wenn der Pod ausschließlich über den Router erreichbar ist, denn sonst kann jeder seine IP vorgeben.
  • Admin-Sitzungen sind an das Admin-Token gebunden: Nach einem Wechsel von HUGIN_ADMIN_TOKEN gelten bestehende Sitzungen sofort nicht mehr.
  • Nur verschlüsselt: Das CLI sendet Keys nur über https:// oder an den eigenen Rechner, auch bei Weiterleitungen; zum Testen HUGIN_ALLOW_HTTP=1. Der PowerShell-Client warnt bei http://.
  • Kein Browser-Cache: Antworten außer /static/ mit Cache-Control: no-store (neue Keys erscheinen einmal auf einer Seite).

16.4 Content Security Policy

Strenge CSP ohne 'unsafe-inline' und ohne 'unsafe-eval', gesetzt per Middleware für jede Antwort der Weboberfläche:

default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:;
connect-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'self';
object-src 'none'

Nonces reichen nicht, weil sie bei style-Attributen nicht helfen. Deshalb wird das CSS-Framework CSP-fest gemacht. Das kommt allen Projekten zugute, die es nutzen.

Heute im Framework Künftig
onclick="toggleSidebar()" usw. data-action="toggle-sidebar" (+ data-target); ein gemeinsamer Listener in framework.js per Event-Delegation, funktioniert auch für Inhalte, die HTMX nachlädt
Inline-Skript für das Theme im <head> eigene Datei theme-init.js, im <head> ohne defer geladen (kein Aufflackern)
Toast: innerHTML mit style="color: …" Aufbau per DOM, Farbe über Klasse toast-icon-{type}, Text über textContent (behebt zugleich eine XSS-Lücke)
el.style.… für Animationen unter CSP erlaubt, sauberer als Klasse (.toast-leaving)
style="…" in Beispielen und Templates Utility-Klassen (z. B. min-w-0, Größen für Skeletons)

HTMX wird so konfiguriert:

<meta name="htmx-config"
      content='{"includeIndicatorStyles": false, "allowEval": false, "allowScriptTags": false}'>

Die Styles für .htmx-indicator kommen aus dem Framework. hx-on wird nicht verwendet (braucht eval).


17. Kompatibilitätstests

Beide Werkzeuge lassen sich ohne Oberfläche starten (claude -p …, opencode run …). Zwei Ebenen, beide als manuelle Routine (es gibt keine CI):

17.1 Übersetzer-Tests (pytest, schnell, immer)

Tabellentests je Art und Ziel: Manifest + Inhalt → erwartete Dateien. Goldene Bundles unter contract/examples/bundles/, erzeugt aus den Beispielpaketen in contract/examples/packages/. Teil von pytest; die CLI-Tests wenden dieselben Bundles an.

17.2 Rauchtests mit den echten Werkzeugen (vor Releases)

Das Skript scripts/tool-smoke-test.sh startet einen Hugin, veröffentlicht Testpakete mit Erkennungswörtern (scripts/tool-smoke/packages/), installiert sie per hugin sync in ein leeres Test-Repo und fragt beide Werkzeuge. hugin sync statt hugin try, weil try nur ein Paket auf einmal installiert. Die Modellaufrufe laufen ohne Lesewerkzeuge, damit die Antwort aus dem geladenen Kontext kommt und nicht aus den Dateien im Repo:

Prüfung Wie
Skill wird gefunden Werkzeug nach der Liste seiner Skills fragen, Name muss vorkommen
Skill wird geladen Beispiel-Anfrage aus dem Paket, Ausgabe auf Erkennungswort prüfen
Befehl mit Argumenten Befehl mit zwei Argumenten aufrufen, beide müssen richtig ankommen
Subagent vorhanden Werkzeug nach seinen Agenten fragen
Regeln wirken Frage, deren Antwort nur in den Regeln steht
MCP eingetragen claude mcp get, opencode mcp list
MCP verbunden Claude: Werkzeug search_packages des Test-Hugin aufrufen; OpenCode: Status connected

Das Ergebnis ist eine Tabelle Paket × Werkzeug × Version. Es landet als Markdown in COMPATIBILITY.md und wird vor jedem Hugin-Release und nach jedem größeren Update eines Werkzeugs neu erzeugt.

Die Rauchtests brauchen Zugänge zu beiden Werkzeugen und kosten Tokens. Sie gehören deshalb nicht in pytest oder go test.


18. Betrieb

Plattform: OpenShift. Deployment, Ingress, Datenbank und Sicherung der Datenbank verantwortet der Betrieb; sie sind nicht Teil dieses Konzepts. Hugin liefert dafür:

  • ein Container-Image ohne Zustand im Dateisystem (alles in der Datenbank), lauffähig als beliebiger Benutzer ohne Root (OpenShift-Standard),
  • Konfiguration nur über Umgebungsvariablen (HUGIN_DATABASE_URL, HUGIN_ADMIN_TOKEN, HUGIN_BASE_URL, HUGIN_DOWNLOAD_DIR …),
  • Health-Endpunkte /healthz (lebt) und /readyz (Datenbank erreichbar, Migrationen aktuell) für die Probes,
  • Migrationen als eigener Schritt (hugin-server migrate), z. B. als Job vor dem Rollout, nicht beim Start jedes Pods,
  • Logs als JSON auf stdout.

Ergänzend:

  • Sicherung: Die Dateien liegen als BLOB in der Datenbank; deren Sicherung (Betrieb) ist also vollständig.
  • Export: hugin-server export <ordner> [--commit], z. B. als CronJob in einen Git-Ordner; auch als ZIP auf der Systemseite.
  • Export: täglich alle freigegebenen Versionen im Quellformat (public/<package>/<version>/… bzw. <tenant-slug>/<package>/<version>/…). Ein Git-Repository darauf ergibt eine lesbare Historie und eine Notfall-Verteilung: Im Notfall kann man Pakete von Hand aus dem Export kopieren.
  • Systemseite: zusätzlich offene Prüfungen, Untergrenzen der Werkzeuge, Verteilung der Werkzeugversionen über alle Projekte (wer ist zu alt?).
  • Ausfall: Fällt Hugin aus, laufen alle Agenten weiter, denn die Dateien liegen im Repo. Nur sync geht nicht. Hugin ist deshalb nicht kritisch im Betrieb, anders als Munin.
  • Versionen des CLI: scripts/build-cli.sh <version> <download-ordner> baut für macOS, Linux und Windows; der Server bietet die neueste Version auf der Seite „Programm hugin“ an (HUGIN_DOWNLOAD_DIR). hugin check meldet ein CLI älter als HUGIN_CLI_MIN_VERSION als Befund und weist auf neuere Versionen hin.

19. Zusammenspiel mit Munin

  • Erstes Paket: pub-munin-nutzen (Skill), dazu pub-munin-anbindung (MCP), beide als Pflichtpakete. Damit nutzen alle Agenten Munin einheitlich und richtig. Das ist der direkteste Hebel auf die Wissensqualität in Munin.
  • Gleiche Tenants und Projekte: Ein Hugin-Tenant entspricht einem Munin-Workspace, mit denselben Slugs. In der ersten Fassung werden sie getrennt gepflegt. Ein gemeinsamer Identitäts- und Key-Dienst ist ein eigenes, späteres Thema.
  • Rückmeldungen zu pub-munin-nutzen zeigen, wo Agenten Munin falsch verstehen. Daraus entstehen Verbesserungen an Munin selbst (Werkzeugbeschreibungen, Hilfe).
  • Abgrenzung im Alltag:
    Gehört nach Munin Gehört nach Hugin
    „Die Staging-DB wird jeden Sonntag zurückgesetzt.“ „Bevor du eine Migration schreibst, prüfe in Munin, ob es Besonderheiten der DB gibt.“
    Fakten, Erkenntnisse, Entscheidungen Arbeitsweisen, Abläufe, Rollen, Werkzeug-Anbindungen

20. Ausbaustufen

Jede Stufe endet mit etwas Nutzbarem und einer Abnahme.

Umsetzungsstand (18.10.2026):

Stufe Stand
0 Machbarkeit für OpenCode 1 geklärt (STUFE0.md); offen: Untergrenze für benannte Argumente, OpenCode 2
1 Kern: Skills und Regeln umgesetzt: Server, Weboberfläche, CLI (sync, check, status)
2 Befehle, Subagenten, MCP umgesetzt, inkl. Autoren-Befehle im CLI (new, validate, try, upload)
2b Auslieferung für Integrationen umgesetzt, inkl. hugin-install.ps1; mit PowerShell 7 getestet, mit 5.1 noch nicht; für Plattformen (AI Workspace) zusätzlich resolve, source, feedback (25.10.2026)
3 Rückkanal umgesetzt: Nutzung, Rückmeldungen, MCP-Endpunkt, Überblick, Systemseite
4 Absicherung umgesetzt: Personen, persönliche Keys, vier Augen nach Risiko, Kanal test, Audit mit Namen, Rauchtest-Skript, COMPATIBILITY.md (alle Prüfungen grün in Claude Code 2.1.303 und OpenCode 1.19.3)
5 Lokale Server, Plugins, Werkzeugspezifisches 5b und 5d (ohne npm-Abhängigkeiten) umgesetzt (25.10.2026), Windows von Hand offen; geplant: 5a Windows-Prüfung, 5c fertige Programme; danach 5e Hooks, Paare, Signaturen

Stufe 0 — Machbarkeit prüfen (vor dem ersten Code)

Offene Verhaltensfragen mit den aktuellen Versionen beider Werkzeuge klären. Dazu genügen ein Test-Repo und Handarbeit.

  1. Ignoriert OpenCode unbekannte Felder in der Frontmatter einer SKILL.md (z. B. allowed-tools), oder warnt es bzw. lehnt es ab?
  2. Was tut OpenCode, wenn derselbe Skill-Name in .claude/skills/ und .opencode/skills/ liegt?
  3. Liest OpenCode .claude/commands/ oder .claude/agents/ mit?
  4. Löst OpenCode @-Verweise in einer CLAUDE.md auf, wenn es keine AGENTS.md gibt?
  5. Claude Code liest AGENTS.md ab 2.1.277, wenn es keine CLAUDE.md gibt (bestätigt). Offen: Verhalten, wenn beide da sind.
  6. Erlaubt opencode.json Kommentare (JSONC)?
  7. Genaue Werkzeugnamen (Claude) und Rechtenamen (OpenCode) für die Fähigkeitsklassen.
  8. Untergrenzen sind ermittelt (→ 5.5). Offen: ab wann Claude Code benannte Argumente (arguments, $name) kennt und ab wann OpenCode die metadata-Schlüssel opencode/slash und opencode/autoinvoke versteht.
  9. Unterstützt OpenCode MCP-Prompts und -Ressourcen? (Nur zur Information; Hugin baut nicht darauf.)
  10. OpenCode 2: Liest es MCP-Einträge in der alten Form (mcp.<name>, enabled)? Kennt es opencode/slash? Ignoriert es unbekannte Felder in der Frontmatter von Skills? Gibt es Doppel, wenn ein Skill in .claude/skills/ und .opencode/skills/ liegt (die ID kommt jetzt aus dem Pfad)?

Stand: Fragen 1, 2, 3, 6, 7 und MCP sind für OpenCode 1 geklärt, ohne Modellaufruf (scripts/stage0.sh); 4, 5 und 8 (Claude Code) mit scripts/tool-smoke-test.sh. opencode/slash und opencode/autoinvoke wirken in OpenCode 1.19.3 nicht (stage0.sh und tool-smoke-test.sh). Offen: ab welcher Version Claude Code benannte Argumente kennt, und alles zu OpenCode 2 (Frage 10).

Ergebnis: STUFE0.md mit Antworten, Versionen und den nötigen Änderungen an diesem Konzept.

Stufe 1 — Kern: Skills und Regeln

  • Projektgerüst (server/ mit uv, cli/ mit Go); Speicher, Auth, Audit, Web und Hilfe nach dem Vorbild von Munin neu bauen.
  • CSS-Framework CSP-fest machen (→ 21.2).
  • Quellformat für skill und rules, Manifest-Parser.
  • Linter (Regeln für diese Arten).
  • Übersetzer für Skills und Regeln, beide Ziele, mit Tabellentests.
  • Lebenszyklus mit einer Freigabe, noch ohne Personen (→ 14.5): Autor- und Prüfer-Key getrennt; nur Pakete mit Risiko text und tools.
  • Tabelle people schon anlegen, aber noch nicht nutzen.
  • Weboberfläche: Katalog, Paket, Version mit Diff und Vorschau je Werkzeug, Prüfungen.
  • CLI: init, sync, status, validate, upload.
  • HTTP-API.
  • Erstes Paket: pub-munin-nutzen.

Abnahme: Ein Test-Repo bekommt per hugin sync einen Skill und eine Regeldatei; beide funktionieren nachweislich in Claude Code und OpenCode.

Stufe 2 — Übersetzung: Befehle, Subagenten, MCP

  • Befehle mit benannten Argumenten.
  • Subagenten mit Fähigkeits- und Modellklassen; Modellzuordnung auf der Systemseite.
  • MCP-Anbindungen; Bearbeiten von JSON-Dateien mit verwalteten Schlüsseln.
  • pub-munin-anbindung als Paket.

Abnahme: Rauchtests (→ 17.2) für alle Arten grün in beiden Werkzeugen.

Stufe 2b — Auslieferung für Integrationen

  • Endpunkte unter /api/v1/delivery/, Integrations-Key, Bundle-Format (→ 14.7).
  • Referenz-Skript client.ps1 für Windows PowerShell 5.1.
  • Für Plattformen: Auflösen ohne Übersetzung, Quellformat je Version, Rückmeldung (→ 14.7).

Stufe 3 — Rückkanal

  • Nutzungsmeldung, Seite „Mein Projekt“, Überblick für Tenant-Admins.
  • MCP-Server (search_packages, get_package, report_feedback, list_subscriptions).
  • hugin check mit Handänderungen, neuen Versionen, Werkzeugversionen.
  • Untergrenzen der Werkzeuge.

Stufe 4 — Absicherung

  • Persönliche Keys (→ 14.5): Personen anlegen und sperren, Rollen je Projekt oder Tenant, Anmeldung in Web und CLI, Ablauf und Hinweise im Überblick.
  • Vier-Augen-Prinzip nach Risiko; ab jetzt auch Pakete mit scripts und permissions.
  • Audit mit Namen.
  • Kanäle stable und test, Versionsbereiche, --major.
  • Pflichtpakete.
  • hugin try, Rauchtest-Skript, COMPATIBILITY.md.
  • Export, Sicherung, Systemseite vollständig.

Stufe 5 — Lokale Server, Plugins, Werkzeugspezifisches

Anforderung: MCP-Server nicht nur anbinden, sondern samt Code verteilen und lokal betreiben, dazu OpenCode-Plugins. Zielplattformen der Clients sind Windows und Linux; entwickelt wird auf macOS. Windows wird von Hand geprüft (kein Windows-CI).

Geprüft mit OpenCode 1.19.4 (25.10.2026, ohne Modellaufruf):

  • Lokaler MCP-Server mit "type": "local", "command": ["hugin", "mcp", "run", "<id>"]: OpenCode findet hugin über den PATH und löst {env:NAME} in environment auf. Arbeitsordner ist der aktuelle Ordner, auch ein Unterordner des Repos.
  • Plugins: .ts-Dateien unter .opencode/plugins/ (auch .opencode/plugin/) werden geladen. OpenCode legt beim Start selbst .opencode/package.json und node_modules an.

5a Windows als Grundlage

  • Alle Pfade im Repo mit / im Bundle; das CLI wandelt beim Schreiben. Prüfen unter Windows: instructions in opencode.json, Zeilenenden, Pfade über 260 Zeichen (venv unter dem Benutzerordner).
  • scripts/windows-test.ps1 und eine Checkliste in TESTEN.md für die Prüfung von Hand: Go-Tests, hugin sync mit allen Arten, echtes OpenCode (debug skill, agent list, mcp list), lokaler Server startet.
  • hugin-install.ps1 lernt alles, was hugin sync neu lernt.

5b Lokale MCP-Server mit Python

  • Manifest: server.local mit runtime: python, entry (Modul oder Skript) und env (Namen der Umgebungsvariablen). Im Paket requirements.txt mit Hashes und der Code. Risiko mindestens scripts.
  • Ablage außerhalb des Repos: ~/.local/share/mcp/<id>/<version>/ (Windows: %USERPROFILE%\.local\share\mcp\…), änderbar mit HUGIN_MCP_HOME. Jede Version in einem eigenen Ordner; mehrere Repos teilen sie.
  • hugin sync (und hugin try) legt dort eine venv an (python -m venv; Python aus HUGIN_PYTHON, unter Windows py -3, sonst das neueste python3.x, mindestens local.python) und installiert mit pip install --require-hashes -r requirements.txt. pip liest die pip.conf (Windows pip.ini) mit dem Artifactory selbst; ohne Anmeldung.
  • In opencode.json bzw. .mcp.json nur hugin mcp run <id>, nie ein Pfad: gleich auf jedem Rechner, einzuchecken. hugin mcp run sucht das Repo nach oben, liest die Version aus hugin.lock und startet den Server aus der venv; Ein- und Ausgabe gehen durch (Windows: Kindprozess statt exec).
  • hugin check meldet eine fehlende Installation; hugin mcp prune räumt Versionen auf, die kein Repo mehr nutzt (Repos merkt sich hugin in .hugin-repos der Ablage; Ordner ohne .hugin-install.json bleiben unberührt).
  • Geprüft (macOS, OpenCode 1.19.4): Beispiel ohne Abhängigkeiten und ein Server mit dem Python-SDK mcp (30 Abhängigkeiten über pip mit Hashes) verbinden; Aufruf aus einem Unterordner bekommt die Umgebungsvariablen.

5c Fertige Programme

  • Manifest: runtime: binary mit artifacts je Plattform (windows-amd64, linux-amd64, optional darwin-arm64 zum Ausprobieren): url und sha256. Die Dateien bleiben im Artifactory, nicht in Hugin.
  • hugin sync lädt nur die passende Datei, prüft die Prüfsumme (sonst Abbruch) und legt sie in den Ordner aus 5b, unter Windows als .exe.
  • Linter: Fehler bei fehlender oder falscher Prüfsumme, Hinweis, wenn Windows oder Linux fehlt. Prüfer sehen Adressen und Prüfsummen.

5d OpenCode-Plugins

Umgesetzt (25.10.2026), zunächst ohne npm-Abhängigkeiten:

  • Art plugin, nur OpenCode: targets: [opencode] ist Pflicht (H038). Inhaltsdatei index.ts, dazu weitere .ts/.js-Dateien.
  • Im Repo: das Paket unter .opencode/plugins/<id>/, dazu die Startdatei .opencode/plugins/<id>.ts mit export * from "./<id>/index.ts". So bleiben die relativen Importe des Pakets gültig, und Hilfsdateien gelten nicht als eigene Plugins.
  • Importe nur aus dem Paket, node:…, bun und @opencode-ai/plugin (H039); das letzte trägt OpenCode selbst in .opencode/package.json ein.
  • Risiko permissions: Ein Plugin läuft in OpenCode mit allen Rechten und sieht jeden Werkzeugaufruf; zwei Freigaben, eine vom Tenant-Admin.

Geprüft mit OpenCode 1.19.4:

  • Geladen werden nur Dateien direkt in .opencode/plugins/ (und .opencode/plugin/), keine Unterordner. Jede exportierte Funktion der geladenen Datei wird aufgerufen, auch über export *; importierte Hilfsdateien nicht.
  • opencode debug config lädt die Plugins und listet sie unter plugin; --pure lädt keine. ctx.worktree ist der Wurzelordner des Repos, auch aus einem Unterordner.
  • .opencode/package.json, node_modules und .opencode/.gitignore (schließt beides aus) legt OpenCode selbst an, sobald es im Repo läuft.
  • Ein Plugin mit Syntaxfehler übergeht OpenCode ohne Meldung, auch mit --print-logs.
  • Werkzeug aus einem Plugin (tool() aus @opencode-ai/plugin) ruft das Modell auf.

Später:

  • npm-Abhängigkeiten als verwaltete Schlüssel in .opencode/package.json (aus dem Artifactory, wenn npm so eingestellt ist). Achtung: .opencode/.gitignore von OpenCode schließt package.json aus; feste Versionen ohne ^, Konflikte zwischen Paketen.
  • OpenCode 2 führt V1-Plugins nicht aus (→ 5.5): eigene Fassung nötig, sobald Teams umsteigen.

5e Danach

  • Hook-Pakete (Claude Code), Paare.
  • Signaturen.
  • Optional: native Verteilwege (Claude-Plugin-Marktplatz, OpenCode über npm oder instructions-URL), falls Teams das wollen.

21. Entscheidungen und offene Fragen

21.1 Getroffen

Entscheidung Begründung
Name Hugin Gegenstück zu Munin: Gedanke und Gedächtnis
Nur Claude Code und OpenCode, beide voll Das sind die Werkzeuge im Haus; andere sind nicht nötig
Neutrales Quellformat mit Übersetzern statt „ein Werkzeug ist Standard“ Claude Code liest keine OpenCode-Ordner; OpenCode liest Claude-Ordner. Nur Übersetzer decken beide sauber ab (→ 5.1)
Strengere Regel gewinnt, Skills liegen in .claude/skills/ eine Datei für beide, keine Doppel
Server in Python/FastAPI mit Jinja2, HTMX und CSS-Framework; CLI in Go später in ein anderes FastAPI-Projekt einbettbar; CLI bleibt eine Binärdatei ohne Abhängigkeiten
Konzepte wie Munin: SQLite/PostgreSQL, Tenants, Keys, Audit, MCP ein Betriebsmodell, bekannte Bedienung
Übersetzer nur im Server, CLI wendet Operationen an keine doppelte Umsetzung in zwei Sprachen (→ 14.2)
Auslieferung für Integrationen über /api/v1/delivery/ mit eigenem Key, ZIP + hugin-bundle.json; Client in PowerShell 5.1 Vorgabe der Integration; ein Bundle-Format für CLI und Skript (→ 14.7)
Datenbank führend, Export nach Git als Sicherung Prüfung und Freigabe in der eigenen Weboberfläche; kein Git-Server mit Review nötig
Abos im Repository, nicht auf dem Server versioniert, im Code-Review sichtbar
Verteilung über hugin sync, MCP nur für Suche und Rückmeldung nur installierte Skills nutzen die eingebaute Skill-Logik beider Werkzeuge
Maschinen mit Projekt-Keys, Menschen in der Freigabekette als Personen Vier Augen und Verantwortung im Audit brauchen Namen; sync und Agenten nicht (→ 14.5)
Personen über persönliche Keys ab Stufe 4, SSO später gemeinsam mit Munin keine neue Infrastruktur nötig; Tabelle people mit external_id hält den Weg zu SSO offen
Bis Stufe 4 nur Pakete mit Risiko text und tools ohne echte vier Augen keine Skripte und keine Rechte verteilen
Zwei Ebenen: öffentlich und Tenants; Tenants sehen nur Öffentliches und Eigenes klare Trennung ohne Freigabelisten je Paket (→ 10.1)
Eine Datenbank mit tenant_id statt einer Datenbank je Tenant einfacher mit SQLAlchemy, einbettbar in andere Projekte; Trennung zentral im Code (→ 15.1)
Strenge CSP ohne 'unsafe-inline'; das CSS-Framework wird CSP-fest gemacht Nonces helfen nicht bei style-Attributen; das Framework profitiert auch in anderen Projekten (→ 16.4)
Keine persönlichen Pakete, nur öffentlich oder je Tenant/Projekt weniger Pfade, keine Nutzerordner (→ 3)
Keine Volltextsuche in Stufe 1, LIKE über ID und Beschreibung Katalog bleibt klein; im Store nachrüstbar (→ 13)
Untergrenzen vorläufig: Claude Code 2.1.105, OpenCode 1.2.11 aus Changelogs ermittelt; Stufe 0 hat nur aktuelle Versionen geprüft (Claude Code 2.1.303, OpenCode 1.15.4 und 1.19.3). Endgültig erst nach einem Rauchtest mit den Untergrenzen selbst (→ 5.5)
OpenCode 1 und 2 werden unterstützt OpenCode 2 ist seit 11.10.2026 verfügbar, viele Teams sind noch auf 1 (→ 5.5)
Zu alte Werkzeugversion: warnen, abbrechen nur mit tool_check: strict Teams nicht blockieren, wer will, kann es erzwingen (→ 5.4)
Repo-Kennung = vereinheitlichte Git-Remote-URL; Integrationen ohne Git melden einen Client-Namen intern ohnehin bekannt, genau genug für die Übersicht (→ 11.5)
Betrieb in OpenShift, Deployment außerhalb dieses Konzepts wird vom Betreiber übernommen (→ 18)
Lokale MCP-Server starten über hugin mcp run <id>, Code unter ~/.local/share/mcp/ Pfade sind je Rechner verschieden; so bleibt opencode.json gleich und einzuchecken, und die Version kommt aus hugin.lock (→ 20, Stufe 5b)
Python über venv und pip, Artifactory aus pip.conf; Programme als Adresse und Prüfsumme so ist es im Haus eingerichtet; große Dateien bleiben im Artifactory (→ 20, Stufe 5b, 5c)
Clients: Windows und Linux, Prüfung unter Windows von Hand Zielplattformen; entwickelt wird auf macOS (→ 20, Stufe 5a)

21.2 Offen

Keine offenen Entscheidungen. Offen sind noch zwei Prüfungen (STUFE0.md): ab welcher Version Claude Code benannte Argumente kennt (davon hängt die Untergrenze ab) und das Verhalten von OpenCode 2. Dazu Stufe 5 (→ 20).


22. Quellen und was neu zu prüfen ist

Stand der Dokus: 17.10.2026. Beide Werkzeuge ändern sich schnell. Nach größeren Updates neu nachsehen und scripts/stage0.sh sowie scripts/tool-smoke-test.sh laufen lassen.

Thema Quelle
OpenCode Skills https://opencode.ai/docs/skills/
OpenCode Regeln (AGENTS.md, instructions, Claude-Kompatibilität) https://opencode.ai/docs/rules/
OpenCode Agenten https://opencode.ai/docs/agents/
OpenCode Befehle https://opencode.ai/docs/commands/
OpenCode MCP-Server https://opencode.ai/docs/mcp-servers/
OpenCode Plugins https://opencode.ai/docs/plugins/
OpenCode 2: Umstieg von V1 https://opencode.ai/v2/docs/migrate-v1/
OpenCode 2: Skills https://opencode.ai/v2/docs/skills/
OpenCode Release-Notes https://github.com/sst/opencode/releases
Claude Code Changelog https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md
Claude Code Skills https://code.claude.com/docs/en/skills
Claude Code Subagenten https://code.claude.com/docs/en/sub-agents
Claude Code Memory (CLAUDE.md, @-Import) https://code.claude.com/docs/en/memory
Claude Code MCP https://code.claude.com/docs/en/mcp
Claude Code Hooks https://code.claude.com/docs/en/hooks
Agent-Skills-Standard https://agentskills.io

Unbestätigt zum Stand dieses Dokuments (nur Berichte Dritter):

  • dass Claude Code ab 2.1.277 AGENTS.md von selbst liest, solange keine CLAUDE.md da ist.

In der Doku nicht beantwortet und deshalb in Stufe 0 selbst geprüft (STUFE0.md):

  • Fragen 1–8 aus Abschnitt 20; offen sind 9 (nur zur Information) und 10 (OpenCode 2).