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

1909 lines
98 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](#1-kurzfassung)
2. [Ausgangslage und Problem](#2-ausgangslage-und-problem)
3. [Ziele und Nicht-Ziele](#3-ziele-und-nicht-ziele)
4. [Begriffe](#4-begriffe) · [Namen im Code](#41-namen-im-code)
5. [Zielwerkzeuge: Claude Code und OpenCode](#5-zielwerkzeuge-claude-code-und-opencode)
6. [Das Quellformat](#6-das-quellformat)
7. [Die Übersetzer](#7-die-übersetzer)
8. [Prüfung (Linter)](#8-prüfung-linter)
9. [Lebenszyklus und Freigabe](#9-lebenszyklus-und-freigabe)
10. [Geltung, Abos, Pflichtpakete, Namen](#10-geltung-abos-pflichtpakete-namen)
11. [Verteilung: das Programm `hugin`](#11-verteilung-das-programm-hugin)
12. [MCP-Server](#12-mcp-server)
13. [Weboberfläche](#13-weboberfläche)
14. [Architektur](#14-architektur)
15. [Datenmodell](#15-datenmodell)
16. [Sicherheit](#16-sicherheit)
17. [Kompatibilitätstests](#17-kompatibilitätstests)
18. [Betrieb](#18-betrieb)
19. [Zusammenspiel mit Munin](#19-zusammenspiel-mit-munin)
20. [Ausbaustufen](#20-ausbaustufen)
21. [Entscheidungen und offene Fragen](#21-entscheidungen-und-offene-fragen)
22. [Quellen und was neu zu prüfen ist](#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
```yaml
# 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.
```yaml
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
```yaml
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
```
```markdown
<!-- 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
```yaml
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
```yaml
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:
```yaml
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:
```markdown
<!-- 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:
```yaml
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)
```yaml
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)
```yaml
# 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:
```markdown
---
# 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.
5. Handänderungen an verwalteten Dateien suchen → bei Befund abbrechen.
6. Schreiben, Abbestelltes entfernen, `hugin.lock` neu schreiben.
7. 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.
8. 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`:
```json
{ "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 |
```json
{
"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:
```html
<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).